unix环境高级编程_unix环境高级编程 高级unix编程 - CSDN
unix环境高级编程 订阅
《UNIX环境高级编程》是2006年由人民邮电出版社出版的图书,作者是(美)理查德·史蒂文斯、(美)拉戈,译者是张亚英、戚正伟。 [1] 展开全文
《UNIX环境高级编程》是2006年由人民邮电出版社出版的图书,作者是(美)理查德·史蒂文斯、(美)拉戈,译者是张亚英、戚正伟。 [1]
信息
译    者
张亚英、戚正伟
定    价
99.00元
类    别
图书 >> 计算机/网络 >>操作系统/系统开发 >> UNIX Solaris
丛书名
图灵计算机科学丛书
书    名
UNIX环境高级编程
出版时间
2006-05-01
出版社
人民邮电出版社
ISBN
9787115147318
页    数
759页
作    者
(美)理查德·史蒂文斯、(美)拉戈
UNIX环境高级编程内容简介
本书是被誉为UNIX编程“圣经”的Advanced Programming in the UNIX Environment一书的更新版。在本书第1版出版后的十几年中,UNIX行业已经有了巨大的变化,特别是影响UNIX编程接口的有关标准变化很大。本书在保持了前一版的风格的基础上,根据最新的标准对内容进行了修订和增补,反映了最新的技术发展。书中除了介绍UNIX文件和目录、标准I/O库、系统数据文件和信息、进程环境、进程控制、进程关系、信号、线程、线程控制、守护进程、各种I/O、进程间通信、网络IPC、伪终端等方面的内容,还在此基础上介绍了多个应用示例,包括如何创建数据库函数库以及如何与网络打印机通信等。此外,还在附录中给出了函数原型和部分习题的答案。本书内容权威,概念清晰,阐述精辟,对于所有层次UNIX程序员都是一本不可或缺的参考书。
收起全文
精华内容
参与话题
  • UNIX环境高级编程(第3版)》在保持前一版风格的基础上,根据最新的标准对内容进行了修订和增补,反映了最新的技术发展。书中除了介绍UNIX文件和目录、标准I/O库、系统数据文件和信息、进程环境、进程控制、进程关系...
  • UNIX环境高级编程中文版,非常清晰,非扫描版,并带有书签!
  • UNIX环境高级编程(中文第三版)

    千次下载 热门讨论 2020-07-30 23:32:15
    UNIX环境高级编程(第3版)》是被誉为UNIX编程“圣经”的Advanced Programming in the UNIX Environment一书的第3版。在本书第2版出版后的8年中,UNIX行业发生了巨大的变化,特别是影响UNIX编程接口的有关标准变化...
  • UNIX环境高级编程(第3版)UNIX环境高级编程(第3版)UNIX环境高级编程(第3版)
  • 第七章 进程环境 引言 main函数是如何被调用的? 命令行参数是如何传送给执行程序的? 典型的存储器布局是什么样式? 如何分配另外的存储空间? 进程如何使用环境变量? 进程终止的不同方式? main函数 main函数...

    第七章 进程环境

    引言

    main函数是如何被调用的?
    命令行参数是如何传送给执行程序的?
    典型的存储器布局是什么样式?
    如何分配另外的存储空间?
    进程如何使用环境变量?
    进程终止的不同方式?

    main函数

    main函数的原型是:

    int main(int argc, char *argv[ ]) ;
    

    argc:命令行参数的数目。
    argv:指向各个参数的指针所构成的数组。

    进程终止

    (1) 正常终止:
    (a) 从main返回。
    (b) 调用exit。
    © 调用_exit。
    (2) 异常终止:
    (a) 调用abort。
    (b) 由一个信号终止。

    exit和_exit函数

      exit和_exit函数用于正常终止一个程序:_exit立即进入内核,exit则先执行一些清除处理(包括调用执行各终止处理程序,关闭所有标准I / O流等),然后进入内核。

    #include <stdlib.h>
    void exit(int status) ;
    
    #include <unistd.h>
    void _exit (int status) ;
    

    atexit函数

      按照ANSIC的规定,一个进程可以登记多至32个函数,这些函数将由exit自动调用。我们称这些函数为终止处理程序(exit handler),并用atexit函数来登记这些函数。
      e x i t以登记这些函数的相反顺序调用它们。同一函数如若登记多次,则也被调用多次。

    C程序的启动和终止

    在这里插入图片描述
      注意,内核使程序执行的唯一方法是调用一个e x e c函数。
      进程自愿终止的唯一方法是显式/隐式地(调用exit)调用_exit。

    命令行参数

      当执行一个程序时,调用exec的进程可将命令行参数传递给该新程序。这是UNIX shell的一部分常规操作。

    环境表

      每个程序都接收到一张环境表。与参数表一样,环境表也是一个字符指针数组,其中每个指针包含一个以null结束的字符串的地址。全局变量environ则包含了该指针数组的地址。
    extern char **environ;
    在这里插入图片描述

    C程序的存储空间布局

    在这里插入图片描述
      正文段:这是由CPU执行的机器指令部分。通常,正文段是可共享的,所以即使是经常执行的程序在存储器中也只需有一个副本,另外,正文段常常是只读的,以防止程序由于意外事故而修改其自身的指令。
      初始化数据段
      非初始化数据段
      栈:自动变量以及每次函数调用时所需保存的信息都存放在此段中。每次函数调用时,其返回地址、以及调用者的环境信息(例如某些机器寄存器)都存放在栈中。然后,新被调用的函数在栈上为其自动和临时变量分配存储空间。通过以这种方式使用栈, C函数可以递归调用。
      堆:通常在堆中进行动态存储分配。

    共享库

      共享库使得可执行文件中不再需要包含常用的库函数,而只需在所有进程都可存取的存储区中保存这种库例程的一个副本。程序第一次执行或者第一次调用某个库函数时,用动态连接方法将程序与共享库函数相连接。这样减少了每个可执行文件的长度,但增加了一些运行时间开销。共享库的另一个优点是可以用库函数的新版本代替老版本而无需对使用该库的程序重新连接编辑。

    动态内存分配

      ANSI C说明了三个用于存储空间动态分配的函数。
    (1) malloc。分配指定字节数的存储区。此存储区中的初始值不确定。
    (2) calloc。为指定长度的对象,分配能容纳其指定个数的存储空间。该空间中的每一位( bit )都初始化为0。
    (3) realloc。更改以前分配存储区的长度(增加或减少)。当增加长度时,可能需将以前分配存储区的内容移到另一个足够大的区域,而新增区域内的初始值则不确定。

    第八章 进程控制

    进程标识

      每个进程都有一个非负整型的唯一进程ID。

    fork函数

      一个现存进程调用 fork 函数是 UNIX 内核创建一个新进程唯一方法

    	pid_t fork(void);//返回值:子进程中返回0,父进程中返回子进程ID,出错返回-1
    

      该函数被调用一次,但返回两次。两次返回的区别是子进程的返回值是0,而父进程的返回值则是新子进程的进程ID。
    子进程和父进程继续执行 fork 之后的指令。子进程是父进程的复制品。例如,子进程获得父进程数据空间、堆和栈的复制品。注意,这是子进程所拥有的拷贝。父、子进程并不共享这些存储空间部分。

    文件共享

      fork的一个特性是所有由父进程打开的描述符都被复制到子进程中。父、子进程每个相同的打开描述符共享一个文件表项。
      在fork之后处理文件描述符有两种常见的情况:
    (1) 父进程等待子进程完成。在这种情况下,父进程无需对其描述符做任何处理。当子进程终止后,它曾进行过读、写操作的任一共享描述符的文件位移量已做了相应更新。
    (2) 父、子进程各自执行不同的程序段。在这种情况下,在 fork 之后,父、子进程各自关闭它们不需使用的文件描述符,并且不干扰对方使用的文件描述符。这种方法是网络服务进程中经常使用的。
    在这里插入图片描述

    fork的两种用法

    (1)一个父进程希望复制自己,使父、子进程同时执行不同的代码段。这在网络服务进程中是常见的——父进程等待委托者的服务请求。当这种请求到达时,父进程调用fork,使子进程处理此请求。父进程则继续等待下一个服务请求。
    (2)一个进程要执行一个不同的程序。

    竞态条件

      当多个进程都企图对共享数据进行某种处理,而最后的结果又取决于进程运行的顺序时,则我们认为这发生了竞态条件(race condition)。

    exec函数

      用fork函数创建子进程后,子进程往往要调用一种 exec 函数以执行另一个程序。当进程调用一种 exec 函数时,该进程完全由新程序代换,而新程序则从其main函数开始执行。因为调用exec并不创建新进程,所以前后的进程ID并未改变。exec只是用另一个新程替换了当前进程的正文、数据、堆和栈段。

    第九章 进程关系

    进程组

      每个进程除了有一进程ID之外,还属于一个进程组。进程组是一个或多个进程的集合。每个进程组有一个唯一的进程组ID。
      每个进程组有一个组长进程。进程组ID等于组长进程ID。
      进程组组长可以创建一个进程组,创建该组中的进程,然后终止。只要在某个进程组中有一个进程存在,则该进程组就存在,这与其组长进程是否终止无关。从进程组创建开始到其中最后一个进程离开为止的时间区间称为进程组的生命期。某个进程组中的最后一个进程可以终止,也可以参加另一个进程组。

    对话期

      对话期(session)是一个或多个进程组的集合。通常是由shell的管道线将几个进程编成一组的。
    在这里插入图片描述

    控制终端

    在这里插入图片描述

    第十章 信号

    信号的概念

      信号是软件中断,信号提供了一种处理异步事件的方法。

    信号的处理方式

    (1)忽略此信号。
    (2)捕捉信号。为了做到这一点要通知内核在某种信号发生时,调用一个用户函数。
    (3)执行系统默认动作。注意,对大多数信号的系统默认动作是终止该进程。

    signal函数

    void (*signal (int signo , void(*func)(int))) (int);
    

      signal函数的原型说明此函数要求两个参数,返回一个函数指针,而该指针所指向的函数无返回值(void)。第一个参数signo是一个整型数,第二个参数是函数指针,它所指向的函数需要一个整型参数,无返回值。用一般语言来描述也就是要向信号处理程序传送一个整型参数,而它却无返回值。signal的返回值则是指向以前的信号处理程序的指针。
      当执行一个程序时,所有信号的状态都是系统默认或忽略。
      当一个进程调用fork时,其子进程继承父进程的信号处理方式。

    第十四章 高级I/O

    非阻塞I/O

    非阻塞I/O使我们可以调用不会永远阻塞的I/O操作,例如open,read和write。如果这种操作不能完成,则立即出错返回,表示该操作如继续执行将继续阻塞下去。

    记录锁

    当两个人同时编辑一个文件时,其后果将如何呢?在很多UNIX系统中,该文件的最后状态取决于写该文件的最后一个进程。
    但是对于有些应用程序,例如数据库,有时进程需要确保它正在单独写一个文件。为了向进程提供这种功能,较新的UNIX系统提供了记录锁机制。
    记录锁(recordlocking)的功能是:一个进程正在读或修改文件的某个部分时,可以阻止其他进程修改同一文件区。对于UNIX,“记录”这个定语也是误用,因为UNIX内核根本没有使用文件记录这种概念。一个更适合的术语可能是“区域锁”,因为它锁定的只是文件的一个区域(也可能是整个文件)。

    I/O多路转接

    I/O多路转接(I/Omultiplexing)。其基本思想是:先构造一张有
    关描述符的表,然后调用一个函数,它要到这些描述符中的一个已准备好进行I/O时才返回。在返回时,它告诉进程哪一个描述符已准备好可以进行I/O。

    select函数

    传向select的参数告诉内核:
    (1)我们所关心的描述符。
    (2)对于每个描述符我们所关心的条件(是否读一个给定的描述符?是否想写一个给定的描述符?是否关心一个描述符的异常条件?)。
    (3)希望等待多长时间(可以永远等待,等待一个固定量时间,或完全不等待)。

    从select返回时,内核告诉我们:
    (1)已准备好的描述符的数量。
    (2)哪一个描述符已准备好读、写或异常条件。
    在这里插入图片描述

    poll函数

    poll函数类似于select,但是其调用形式则有所不同。与select不同,poll不是为每个条件构造一个描述符集,而是构造一个pollfd结构数组,每个数组元素指定一个描述符编号以及对其所关心的条件。

    异步I/O

    使用s e l e c t和p o l l可以实现异步I / O。关于描述符的状态,系统并不主动告诉我们任何信息,我们需要主动地进行查询(调用s e l e c t或p o l l)。

    第十五章 进程间通信

    进程之间相互通信的其他技术—IPC(InterProcessCommunication)

    管道

    管道有两种限制;
    (1)它们是半双工的。数据只能在一个方向上流动。
    (2)它们只能在具有公共祖先的进程之间使用。通常,一个管道由一个进程创建,然后该进程调用fork,此后父、子进程之间就可应用该管道。
    在这里插入图片描述
    管道是由调用pipe函数而创建的。intpipe(intfiledes[2]);
    经由参数filedes返回两个文件描述符:filedes[0]为读而打开,filedes[1]为写而打开。filedes[1]的输出是filedes[0]的输入。
    通常,调用pipe的进程接着调用fork,这样就创建了从父进程到子进程或反之的IPC通道。
    在这里插入图片描述
    在这里插入图片描述

    FIFO

    FIFO有时被称为命名管道。管道只能由相关进程使用,它们共同的祖先进程创建了管道。但是,通过FIFO,不相关的进程也能交换数据。FIFO是一种文件类型,创建FIFO类似于创建文件。
    在这里插入图片描述

    消息队列

    消息队列是消息的链接表,存放在内核中并由消息队列标识符标识。我们将称消息队列为“队列”,其标识符为“队列ID”。

    信号量

    信号量与已经介绍过的IPC机构(管道、FIFO以及消息列队)不同。它是一个计数器,用于多进程对共享数据对象的存取。
    为了获得共享资源,进程需要执行下列操作:
    (1)测试控制该资源的信号量。
    (2)若此信号量的值为正,则进程可以使用该资源。进程将信号量值减1,表示它使用了一个资源单位。
    (3)若此信号量的值为0,则进程进入睡眠状态,直至信号量值大于0。若进程被唤醒后,它返回至(第(1)步)。
    当进程不再使用由一个信息量控制的共享资源时,该信号量值增1。如果有进程正在睡眠等待此信号量,则唤醒它们。
    为了正确地实现信息量,信号量值的测试及减1操作应当是原子操作。为此,信号量通常是在内核中实现的。

    共享存储

    允许两个或多个进程共享一给定的存储区。因为数据不需要在客户机和服务器之间复制,所以这是最快的一种IPC。
    使用共享存储的唯一窍门是多个进程之间对一给定存储区的同步存取。若服务器将数据放入共享存储区,则在服务器做完这一操作之前,客户机不应当去取这些数据。通常,信号量被用来实现对共享存储存取的同步。

    展开全文
  • UNIX环境高级编程》目录

    千次阅读 2019-04-29 18:58:08
    第一章:UNIX标准及实现 01函数perror、strerror 第三章:文件I/O 01C库函数 02文件描述符、函数open和openat 03 函数read、write、lseek 04函数dup和dup2 第四章:文件和目录 01函数stat、fstat、fstatat和...

    第一章:UNIX标准及实现

    01 函数perror、strerror

    第三章:文件I/O

    01 C库函数
    02 文件描述符、函数open和openat
    03 函数read、write、lseek
    04 函数dup和dup2

    第四章:文件和目录

    01 函数stat、fstat、fstatat和lstat
    02 函数umask
    03 函数chown
    04 link系列函数
    05 
    函数mkdir、mkdirat和emdir
    05 函数chdir和get
    06 

    第五章:标准I/O库

    第七章:进程环境

    01 进程相关概念
    02 命令形参
    03 环境变量
    04 函数setjump和longjump

    第八章:进程控制

    01 函数fork
    02 函数vfork
    03 孤儿进程、僵尸进程
    03 函数wait和waitpid
    04 函数exec
    05 孤儿进程、僵尸进程
     

    第九章:进程关系

    01 进程组
    02 进程组的应用
    03 会话
    04 前台进程组、后台进程组
    05 孤儿进程组

    第十章:信号

    01 信号的概念
    02 函数signal
    03 中断的系统调用
    04 不可重入函数
    05 SIGCHLD信号
    05 不可靠信号
    06 函数kill和raise
    07 函数pause
    08 函数alarm
    09 信号集、函数sigprocmask、函数sigpending
    10 函数sigaction
    11 函数sigsetjmp和siglongjmp
    11 函数sigsuspend
    12 函数sigqueue

    第十一章:线程

    01 线程概念
    02 线程标识、线程创建、线程终止
    03 线程终止
    04 线程清理函数
    05 线程同步概念
    06  互斥量
    07 读写锁(一)
    08 读写锁(二)
    09 条件变量

     

    互斥锁和条件条件变量

    读写锁

    第十二章:线程控制

    01 线程属性

    第十三章:守护进程

    01 守护进程

    第十四章:高级IO

    01 
    02
    04 存储映射I/O(一)
    05 存储映射I/O(二)03

     

     

    展开全文
  • unix必学基础 目 录 译者序 译者简介 前言 第1章 UNIX基础知识 1 1.1 引言 1 1.2 登录 1 1.2.1 登录名 1 1.2.2 shell 1 1.3 文件和目录 2 1.3.1 文件系统 2 1.3.2 文件名 2 1.3.3 路径名 2 1.3.4 工作目录 4 1.3.5 ...
  • unix环境高级编程》--- 高级I/O

    千次阅读 2018-05-30 13:55:49
    长的非阻塞write 从标准输入读500000字节,并试图写到标准输出。先将标准输出设置为非阻塞,然后用for循环输出,每次write调用的结果都在标准出错上打印。 #include &amp;quot;apue.h&...

    长的非阻塞write
    从标准输入读500000字节,并试图写到标准输出。先将标准输出设置为非阻塞,然后用for循环输出,每次write调用的结果都在标准出错上打印。

    #include "apue.h"
    #include <errno.h>
    #include <fcntl.h>
    
    char buf[500000];
    
    int main(void)
    {
        int ntowrite, nwrite;
        char *ptr;
    
        ntowrite = read(STDIN_FILENO, buf, sizeof(buf));
        fprintf(stderr, "read %d bytes\n", ntowrite);
    
        set_fl(STDOUT_FILENO, O_NONBLOCK);  /* set nonblocking */
    
        ptr = buf;
        while(ntowrite > 0)
        {
            errno = 0;
            nwrite = write(STDOUT_FILENO, ptr, ntowrite);
            fprintf(stderr, "nwrite = %d, errno = %d\n", nwrite, errno);
    
            if(nwrite > 0)
            {
                ptr += nwrite;
                ntowrite -= nwrite;
            }
        }
    
        clr_fl(STDOUT_FILENO, O_NONBLOCK);  /* clear nonblocking */
        exit(0);
    }

    结果

    yjp@yjp-VirtualBox:~/apue/14advio$ fallocate -l 800m 800m.txt    创建一个大文件
    yjp@yjp-VirtualBox:~/apue/14advio$ ls -l 800m.txt
    -rw-rw-r-- 1 yjp yjp 838860800 529 16:05 800m.txt
    yjp@yjp-VirtualBox:~/apue/14advio$ ./nonblockw < 800m.txt > temp.file 标准输出是普通文件
    read 500000 bytes
    nwrite = 500000, errno = 0    一次写
    yjp@yjp-VirtualBox:~/apue/14advio$ ls -l temp.file    查看
    -rw-rw-r-- 1 yjp yjp 500000 529 16:06 temp.file
    
    yjp@yjp-VirtualBox:~/apue/14advio$ ./nonblockw < 800m.txt 2> stderr.out  输出至终端
    yjp@yjp-VirtualBox:~/apue/14advio$ cat stderr.out
    read 500000 bytes
    nwrite = 9729, errno = 0
    nwrite = 3589, errno = 0
    nwrite = -1, errno = 11     出错
    nwrite = -1, errno = 11
    ...

    数千个write调用中,只有几十个是真正输出数据的,其余的则出错返回。这种形式的循环成为轮询,浪费CPU时间,可采用非阻塞描述符的I/O多路转接。

    加锁和解锁一个文件区域的函数
    为避免每次分配flock结构,然后又填入各项信息,可用如下函数。

    #include "apue.h"
    #include <fcntl.h>
    
    int lock_reg(int fd, int cmd, int type, off_t offset, int whence, off_t len)
    {
        /*
           struct flock
           {
            short l_type;
            off_t l_start;
            short l_whence;
            off_t l_len;
            pid_t l_pid;     returned with F_GETLK
           };
        */
        struct flock lock;
    
        lock.l_type = type;       /* F_RDLCK, F_WRLCK, F_UNLCK */
        lock.l_start = offset;    /* byte offset, relative to l_whence */
        lock.l_whence = whence;   /* SEEK_SET, SEEK_CUR, SEEK_END */
        lock.l_len = len;     /* bytes (0 means to EOF) */
    
        /*
            int fcntl(int filedes, int cmd, ...);
            cmd: F_GETLK, F_SETLK, F_SETLKW
        */
        return (fcntl(fd, cmd, &lock));
    }

    通用宏

    #define read_lock(fd, offset, whence, len)\
        lock_reg((fd), F_SETLK, F_RDLCK, (offset), (whence), (len))
    #define readw_lock(fd, offset, whence, len)\
        lock_reg((fd), F_SETLKW, F_RDLCK, (offset), (whence), (len))
    #define write_lock(fd, offset, whence, len)\    
        lock_reg((fd), F_SETLK, F_WRLCK, (offset), (whence), (len))
    #define writew_lock(fd, offset, whence, len)\
        lock_reg((fd), F_SETLKW, F_WRLCK, (offset), (whence), (len))
    #define un_lock(fd, offset, whence, len)\
        lock_reg((fd), F_SETLK, F_UNLCK, (offset), (whence), (len))
    

    测试一个锁状态的函数

    #include "apue.h"
    #include <fcntl.h>
    
    pid_t lock_test(int fd, int type, off_t offset, int whence, off_t len)
    {
        struct flock lock;
        lock.l_type = type;   /* F_RDLCK or F_WRLCK */
        lock.l_start = offset;  /* byte offset, relative to l_whence */
        lock.l_whence = whence; /* SEEK_SET, SEEK_CUR, SEEK_END */
        lock.l_len = len;  /* bytes (0 means to EOF */
    
        if(fcntl(fd, F_GETLK, &lock) < 0)
            err_sys("fcntl error");
    
        if(lock.l_type == F_UNLCK)
            return (0);  /* false, region isn't locked by another proc */
        return (lock.l_pid); /* true, return pid of lock owner */
    }

    通用宏

    #define is_read_lockable(fd, offset, whence, len)\
        (lock_test((fd), F_RDLCK, (offset), (whence), (len)) == 0)
    #define is_write_lockable(fd, offset, whence, len)\
        (lock_test((fd), F_WRLCK, (offset), (whence), (len)) == 0)

    死锁检测实例
    如果两个进程相互等待对方持有并且锁定的资源时,则这两个进程就处于死锁状态。
    子进程锁字节0,父进程锁字节1。然后,它们又都试图锁对方已经加锁的字节。使用了父、子进程同步例程,使得每个进程能够等待另一个进程获得它设置的第一把锁。

    #include "apue.h"
    #include <fcntl.h>
    
    static void lockabyte(const char *name, int fd, off_t offset)
    {
        if(writew_lock(fd, offset, SEEK_SET, 1) < 0)
            err_sys("%s: writew_lock error", name);
        printf("%s: got the lock, byte %ld\n", name, offset);
    }
    
    int main(void)
    {
        int fd;
        pid_t pid;
    
        /* Create a file and write two bytes to it */
        if((fd = creat("templock", FILE_MODE)) < 0)
            err_sys("creat error");
        if(write(fd, "ab", 2) != 2)
            err_sys("write error");
    
        TELL_WAIT();
    
        if((pid = fork()) < 0)
            err_sys("fork error");
        else if(pid == 0)
        {
            lockabyte("child", fd, 0);
            TELL_PARENT(getppid());
            WAIT_PARENT();
            lockabyte("child", fd, 1);
        }
        else
        {
            lockabyte("parent", fd, 1);
            TELL_CHILD(pid);
            WAIT_CHILD();
            lockabyte("parent", fd, 0);
        }
        exit(0);
    }

    这里写图片描述
    本实例中,检测到死锁时,子进程接收出错并返回。

    锁的隐含继承和释放
    1、锁与进程和文件两方面有关。
    2、由fork产生的子进程不继承父进程锁设置的锁。
    3、在执行exec后,新程序可以继承原执行程序的锁。

    在文件整体上加锁
    守护进程可用一把文件锁保证只有该守护进程的唯一副本正在运行。

    #include <unistd.h>
    #include <fcntl.h>
    
    int lockfile(int fd)
    {
        struct flock f1;
    
        f1.l_type = F_WRLCK;
        f1.l_start = 0;
        f1.l_whence = SEEK_SET;
        f1.l_len = 0;
        return (fcntl(fd, F_SETLK, &f1));
    }
    #define lockfile(fd) write_lock((fd), 0, SEEK_SET, 0)

    确定是否支持强制性锁
    建议性锁不能阻止对数据库文件有写权限的任何其他进程对数据库文件进行任意的写操作。如果仅仅是访问数据库,那么可使用建议性锁。
    强制性锁使内核对每一个open、read和write系统调用都进行检查,检查调用进程对正在访问的文件是否违背了一把锁的作用。
    对一个特定文件打开其设置组ID位并关闭其组执行位,则对该文件开启了强制性锁机制。

    首先创建一个文件,并使强制性锁机制对其起作用。然后程序分裂为父进程和子进程。父进程对整个文件设置一把写锁,子进程则将该文件的描述符设置为非阻塞的,然后企图对该文件设置一把读锁,会导致出错返回,返回EACCESS或EAGAIN。接着,子进程将文件读、写位置调整到文件起点,并试图读该文件。如果系统提供强制性锁机制,则read返回EACESS或EAGAIN(因为该描述符是非阻塞的),否则read返回所读的数据。

    #include "apue.h"
    #include <errno.h>
    #include <fcntl.h>
    #include <sys/wait.h>
    
    int main(int argc, char *argv[])
    {
        int fd;
        pid_t pid;
        char buf[5];
        struct stat statbuf;
        if(argc != 2)
        {
            fprintf(stderr, "usage: %s filename\n", argv[0]);
            exit(1);
        }
        if((fd = open(argv[1], O_RDWR | O_CREAT | O_TRUNC, FILE_MODE)) < 0)
            err_sys("open error");
        if(write(fd, "abcdef", 6) != 6)
            err_sys("write error");
    
        /* turn on set-group-ID and turn off group-execute */
        if(fstat(fd, &statbuf) < 0)
            err_sys("fstat error");
        if(fchmod(fd, (statbuf.st_mode & ~S_IXGRP) | S_ISGID) < 0)
            err_sys("fchmod error");
    
        TELL_WAIT();
    
        if((pid = fork()) < 0)
            err_sys("fork error");
        else if(pid > 0)
        {
            /* write lock entire file */
            if(write_lock(fd, 0, SEEK_SET, 0) < 0)
                err_sys("write_lock error");
    
            TELL_CHILD(pid);
    
            if(waitpid(pid, NULL, 0) < 0)
                err_sys("waitpid error");
        }
        else
        {
            WAIT_PARENT();  /* wait parent to set lock */
    
            set_fl(fd, O_NONBLOCK);
    
            /* first let's see what error we get if region is locked */
            if(read_lock(fd, 0, SEEK_SET, 0) != -1)  /* no wait */
                err_sys("child: read_lock succeeded");
            printf("read_lock of already-locked region returns %d\n", errno);
    
            /* now try to read the mandatory locked file */
            if(lseek(fd, 0, SEEK_SET) == -1)
                err_sys("lseek error");
            if(read(fd, buf, 2) < 0)
                err_ret("read failed (mandatory locking works)");
            else
                printf("read OK (no mandatory locking), buf = %2.2s\n", buf);
        }
        exit(0);
    }   

    这里写图片描述
    可见系统不支持强制性锁。

    检查描述符是否引用STREAMS设备
    使用I_CANPUT来测试由第三个参数说明的优先级波段(本例中为0)是否可写。如果该ioctl执行成功,则它对所涉及的流并未作任何改变。

    #include <stropts.h>
    #include <unistd.h>
    
    int isastream(int fd)
    {
        return (ioctl(fd, I_CANPUT, 0) != -1);
    }

    测试此函数

    #include <fcntl.h>
    #include "apue.h"
    #include <stropts.h>
    #include <unistd.h>
    
    int isastream(int fd)
    {
        return (ioctl(fd, I_CANPUT, 0) != -1);
    }
    
    
    int main(int argc, char *argv[])
    {
        int i, fd;
    
        for(i=1; i<argc; i++)
        {
            if((fd = open(argv[i], O_RDONLY)) < 0)
            {
                err_ret("%s: can't open", argv[i]);
                continue;
            }
    
            if(isastream(fd) == 0)
                err_ret("%s: not a stream", argv[i]);
            else
                err_msg("%s: stream device", argv[i]);
        }
    
        exit(0);
    }

    这里写图片描述

    列表流中的模块名

    #include "apue.h"
    #include <fcntl.h>
    #include <stropts.h>
    #include <unistd.h>
    #define FMNAMESZ 8 
    
    int isastream(int fd)
    {
        return (ioctl(fd, I_CANPUT, 0) != -1);
    }
    
    int main(int argc, char *argv[])
    {
        int fd, i, nmods;
        /*
           struct str_list
           {
            int sl_nmods;   number of entries in array
            struct str_mlist *sl_modlist;  ptr to first element of array
           };
           struct str_mlist
           {
            char l_name[FMNAMESZ+1];  null terminated module name
           };
        */
        struct str_list list;
    
        if(argc != 2)
            err_quit("usage: %s <pathname>", argv[0]);
    
        if((fd = open(argv[1], O_RDONLY)) < 0)
            err_sys("can't open %s", argv[1]);
        if(isastream(fd) == 0)
            err_quit("%s is not a stream", argv[1]);
    
        /* Fetch number of modules 
           最后一个参数为0时,返回值是模块数*/
        if((nmods = ioctl(fd, I_LIST, (void *)0)) < 0)
            err_sys("I_LIST error for nmods");
        printf("#modules = %d\n", nmods);
    
        /* Allocate storage for all the module names */
        list.sl_modlist = calloc(nmods, sizeof(struct str_mlist));
        if(list.sl_modlist == NULL)
            err_sys("calloc error");
        list.sl_nmods = nmods;
    
        /* Fetch the module names */
        if(ioctl(fd, I_LIST, &list) < 0)
            err_sys("I_LIST error for list");
    
        /* Print the names 
           列表的最后一是项处于流底部的驱动程序*/
        for(i=1; i<=nmods; i++)
            printf(" %s: %s\n", (i == nmods) ? "driver" : "module", 
                    list.sl_modlist++->l_name);
    
        exit(0);
    }

    这里写图片描述
    这里写图片描述
    没有按预期运行?

    用getmsg将标准输入复制到标准输出

    #include "apue.h"
    #include <stropts.h>
    
    #define BUFFSIZE 4096
    
    int main(void)
    {
        int n, flag;
        char ctlbuf[BUFFSIZE], datbuf[BUFFSIZE];
        struct strbuf ctl, dat;
    
        ctl.buf = ctlbuf;
        ctl.maxlen = BUFFSIZE;
        dat.buf = datbuf;
        dat.maxlen = BUFFSIZE;
        for(;;)
        {
            flag = 0;  /* return any message */
            /*
               int getmsg(int fileds, struct strbuf *restrict ctlptr, 
                  struct strbuf *restrict dataptr, int *restrict flagptr);
               从流首读STREAMS消息。
               如果flagptr指向的整型单元的值是0,则返回流首读队列的下一个消息。
               如果下一个消息是高优先级消息,则返回时flagptr指向的整型单元设置为RS_HIPRI。
               如果只希望接收高优先级消息,则在调用getmsg前将flagptr指向的整型单元设置为RS_HIPRI。
               如果ctlptr是null,或cplptr->maxlen==-1,则消息的控制部分仍保留在流首读队列中,不处理。
               类似,若dataptr是null,或dataptr->maxlen==-1,那么消息的数据部分仍保留在流首读队列中,
               不处理。否则,按照缓冲区的容量取到消息中尽可能多的控制和数据部分,余下的仍留在队首,
               等待下次取用。
            */
            if((n = getmsg(STDIN_FILENO, &ctl, &dat, &flag)) < 0)
                err_sys("getmsg error");
            fprintf(stderr, "flag = %d, ctl.len = %d, dat.len = %d\n",
                flag, ctl.len, dat.len);
            if(dat.len == 0)
                exit(0);
            else if(dat.len > 0)
                if(write(STDOUT_FILENO, dat.buf, dat.len) != dat.len)
                    err_sys("write error");
        }
    }

    编译后,提示getmsg未实现。

    I/O多路转接

    当从两个描述符读,仍用阻塞I/O时,可能长时间阻塞在一个描述符上,而另一个描述符虽由很多数据却不能得到及时处理。
    如果使用两个进程,则可使每个进程都执行阻塞read,但操作什么时候终止?可采用信号,但使得程序复杂。
    也可以使用一个进程中的两个线程,但却要处理线程之间的同步。
    另一中方法时仍使用一个程序,但使用非阻塞I/O读数据,但需要轮询,浪费CPU时间。
    还有一种技术称为异步I/O。当一个描述符已准备好I/O时,用一个信号通知,但无法确定哪个描述符已准备好I/O。
    一种比较好的技术是使用I/O多路转接。先构造一张有关描述符的列表,然后调用一个函数,直到这些描述符中的一个已准备好I/O时,该函数才返回。返回时,告诉进程哪些描述符已准备好I/O。

    select

    #include <sys/select.h>
    
    int select(int maxfdp1, fd_set *restrict readfds, fd_set *restrict writefds,
               fd_set *restrict exceptfds, struct timeval *restrict tvptr);
    
    tvptr:愿意等待的时间
    struct timeval
    {
        long tv_sec;     seconds
        long tv_usec;    and microseconds
    };
    tvptr == NULL:永远等待。
    tvptr->tv_sec == 0 && tvptr->tv_usec == 0:完全不等待。
    tvptr->tv_sec != 0 || tvptr->tv_usec != 0:等待指定的秒数和微妙数。
    
    readfs、writefds、exceptfds分别表示可读、可写、处于异常的描述符。
    对fd_set类型的变量可用如下函数处理:
    int FD_ISSET(int fd, fd_set *fdset);  fd在fdset中则返回非0,否则返回0
    void FD_CLR(int fd, fd_set *fdset);   清除指定位
    void FD_SET(int fd, fd_set *fdset);   设置指定位
    void FD_ZERO(fd_set *fdset);          清除所有位
    
    maxfdp1:最大描述符+1,因为描述符从0开始,相当于描述符数量。
    
    返回:-1--出错;0--没有描述符准备好;正值--已经准备好的描述符数。
    
    使用:
    fd set readset, writeset;
    FD_ZERO(&readset);
    FD_ZERO(&writeset);
    FD_SET(0, &readset);
    FD_SET(3, &readset);
    FD_SET(1, &writeset);
    FD_SET(2, &writeset);
    select(4, &readset, &writeset, NULL, NULL);

    poll

    #include <poll.h>
    int poll(struct pollfd fdarray[], nfds_t nfds, int timeout);
    返回:准备就绪的描述符数,超时返回0,出错返回-1
    timeout:愿意等待的时间。-1--永远等待;0--不等待;>0:等待timeout毫秒
    
    struct poolfd
    {
        int fd;           file descriptor to check, or < 0 to ignore
        short events;     requested events
        short revents;    returned events
    };

    这里写图片描述
    头4行测试读,中间3行测试写,后3行测试异常。

    readn和writen函数
    在已经读、写了一些数据出错,则这两个函数返回已传输的数据量,而非出错返回。

    #include "apue.h"
    
    /* read n bytes from a descriptor */
    ssize_t readn(int fd, void *ptr, size_t n)
    {
        size_t nleft;
        ssize_t nread;
    
        nleft = n;
        while(nleft > 0)
        {
            if((nread = read(fd, ptr, nleft)) < 0)
            {
                if(nleft == n)
                    return -1;   /* error, return -1 */
                else
                    break;  /* error, return amount read so far */
            }
            else if(nread == 0)
                break;   /* EOF */
    
            nleft -= nread;
            ptr += nread;
        }
        return (n - nleft);  /* return  >= 0*/
    }
    
    /* write n bytes to a descriptor */
    ssize_t writen(int fd, const void *ptr, size_t n)
    {
        size_t nleft;
        ssize_t nwritten;
    
        nleft = n;
        while(nleft > 0)
        {
            if((nwritten = write(fd, ptr, nleft)) < 0)
            {
                if(nleft == n)
                    return -1;  /* error, return -1 */
                else 
                    break;   /* error, return amount written so far */
            }
            else if(nwritten == 0)
                break;
    
            nleft -= nwritten;
            ptr += nwritten;
        }
        return (n - nleft);  /* return >= 0*/
    }

    用存储映射I/O复制文件
    存储映射I/O使一个磁盘文件与存储空间中的一个缓冲区相映射,这就可以在不使用read和write的情况下执行I/O。
    首先打开两个文件,然后调用fstat得到输入文件的长度。在位输入文件调用mmap和设置输出文件长度时都需要使用输入文件长度。调用lseek,然后写一个字节以设置输出文件长度。

    #include "apue.h"
    #include <fcntl.h>
    #include <sys/mman.h>
    
    int main(int argc, char *argv[])
    {
        int fdin, fdout;
        void *src, *dst;
        struct stat statbuf;
    
        if(argc != 3)
            err_quit("usage: %s <fromfile> <tofile>", argv[0]);
    
        if((fdin = open(argv[1], O_RDONLY)) < 0)
            err_sys("can't open %s for reading", argv[1]);
    
        if((fdout = open(argv[2], O_RDWR | O_CREAT | O_TRUNC, FILE_MODE)) < 0)
            err_sys("can't creat %s for writing", argv[2]);
    
        if(fstat(fdin, &statbuf) < 0)  /* need size of input file */
            err_sys("fstat error");
    
        /* set size of ouput file */
        if(lseek(fdout, statbuf.st_size-1, SEEK_SET) == -1)
            err_sys("lseek error");
        if(write(fdout, "", 1) != 1)
            err_sys("write error");
    
        /*
           void *mmap(void *addr, size_t len, int prot, int flag, int filedes, off_t off);
           addr:指定映射存储器区的起始地址,0表示系统选择该映射区的起始地址,同时也是函数的返回。
           filedes:指定要映射文件的描述符,映射前需打开。
           len:映射的字节数
           off:要映射字节在文件中的起始偏移量
           prot:对映射存储区的保护要求,PROT_READ--映射区可读;PROT_WRITE--映射区可写
           flag:影响存储区的多种属性,MAP_SHARED--存储操作相当于对该文件的write
        */
        if((src = mmap(0, statbuf.st_size, PROT_READ, MAP_SHARED, fdin, 0)) == MAP_FAILED)
            err_sys("mmap error for input");
    
        if((dst = mmap(0, statbuf.st_size, PROT_READ|PROT_WRITE, MAP_SHARED, 
                fdout, 0)) == MAP_FAILED)
            err_sys("mmap error for output");
    
        memcpy(dst, src, statbuf.st_size); /* does the file copy */
        exit(0);
    }

    这里写图片描述

    展开全文
  • UNIX环境高级编程》阅读笔记

    千次阅读 2019-01-07 11:20:04
    第一章 UNIX基础知识 1、文件描述符通常是一个小的非负整数,内核用以标识一个特定进程正在访问的文件。 2、open、read、write、lseek以及close提供了不带缓冲的IO。标准I/O函数为那些不带缓冲的I/O函数提供了一个...

    这本书后续需要再读一遍

    第一章 UNIX基础知识

    1、文件描述符通常是一个小的非负整数,内核用以标识一个特定进程正在访问的文件。

    2、open、read、write、lseek以及close提供了不带缓冲的IO。标准I/O函数为那些不带缓冲的I/O函数提供了一个带缓冲的接口。

    3、程序是一个存储在磁盘上某个目录的可执行文件,内核使用exec函数将程序读入 内存,并执行程序。进程是程序的一个执行实例。线程,某一时刻执行的一组机器指令。

    4、execlp函数要求的参数是一null结束的而不是以换行符结束的。 

    5、用户ID标识不同的用户。组ID,标识不同组。附属组ID允许用户属于多个组。每一个磁盘上的文件都存储该文件所有者的用户ID 和组ID。

    6、UNIX的系统调用一般在C库中会有一个同名函数。从实现者的角度看,系统调用和库函数之间有根本的区别,但从用户的角度来看,其区别不重要。应用程序即可以调用系统调用也可以调用库函数。很多库函数则会调用系统调用。

    第三章 文件/IO

    1、关闭一个文件时还会释放该进程加在该文件上的所有记录锁。

    2、通过lseek设置的文件偏移量可以大于文件的当前长度,在这种情况下,对该文件的下一次写将加长该文件,并在文件中构成一个空洞,这一点是允许的。位于文件中但没有写过的字节都被读作0.但是空洞可能被并不占用磁盘空间。

    3、大多数文件系统为改善性能都采用某种预读技术。当检测到正进行顺序读取时,系统就试图读入比应用锁要求的更多的数据。所以在一定情况下,read分配缓冲区32到524288,可能时钟时间相差不大。

    4、每一个进程有一个进程表,进程表有一个记录项指向一张文件描述符表,文件描述符对应一个文件表项,文件表项指向一个v节点表项,v节点指向i节点,i节点也指向v节点。多个进程可以打开同一个文件,拥有不同的文件表项,共享相同的v节点和i节点。

    5、在完成每个write后,在文件表项中的当前文件偏移量即增加所写入的字节数。如果这导致当前文件偏移量超出了当前文件长度,则将i节点表项中的当前文件长度设置为当前文件偏移量。lseek函数只修改文件表项中的当前文件偏移量,不进行任何IO操作。

    6、pread和pwrite可以实现原子操作。

    7、 FD_CLOEXEC 标志,进程中每个打开描述符都有一个执行时关闭标志。若此标志设置,则在执行 exec 时关闭该描述符,否则该描述符仍打开。除非特地用 fcntl 设置了该标志,否则系统的默认操作是在执行 exec 后仍保持这种描述符打开。

    第四章 文件和目录

    1、用户ID、有效ID,参考:linux下进程的实际用户ID(有效组)和有效用户ID(有效组ID)

    2、用户可以设置umask值以控制他们所创建文件的默认权限。该值表示成八进制数,一位代表一种要屏蔽的权限。

    第七章、进程环境

    1、函数atexit,一个进程可以登记多至32个函数,这些函数将由exit自动调用。我们称这些函数为终止处理程序,并调用atexit函数来登记这些函数。

    2、C程序的存储空间布局:(1)正文段、即代码段、存放指令可共享(2)初始化段、在代码中初始化的全局变量。(3)未初始化段、代码中未初始化的全局变量,有内核初始化为0或空指针。(3)堆、动态分配空间。(4)栈、自动变量以及每次函数调用时所需保存的信息都存放在此段。栈和堆的增长方向相反。参考:Linux下C程序的存储空间布局

    3、共享库、大多数UNIX系统支持共享库,用动态链接的方式将程序与共享库函数相链接。

    4、虽然sbrk可以扩充或缩小进程的存储空间,但是大多数malloc和free的实现都不减少进程的存储空间。释放的空间可供以后再分配,但将他们保持在malloc池中而不返回给内核。

    5、大多数实现所分配的存储空间比所要求的要稍大一点,额外的空间用来记录管理信息。这意味着,如果超过一个已分配区的尾端或起始位置之前进行写操作,则会改写一些管理信息,这种类型的错误是灾难性的。

    6、环境表(指向实际name=value字符串的指针数组)和环境字符串通常存放在进程存储空间的顶部(栈之上)。并且空间长度不能增加。如果需要更多的空间只能调用malloc重新分配空间,并将environ指向新指针表。

    7、goto语句是不能跨越函数的。但是setjmp和longjmp可以。但是大多数实现并不回滚这些自动变量和寄存器变量的值。

    第八章、进程控制

    1、ID为0的进程通常是调度进程,常常被称为交换进程。该进程是内核的一部分。进程ID为1通常是init进程,在自举过程结束后由内核调用。

    2、可移植的应用程序不应该使用vfork,vfork保证子进程先运行

    3、不管进程如何终止,最后都会执行内核中的同一段代码,这段代码为相应进程关闭所有打开描述符,释放它所使用的存储器。内核为每个终止子进程保存了一定量的信息,所以当终止进程的父进程调用wait或waitpid时,可以得到这些信息。一个已经终止,但是其父进程尚未对其进行善后处理的进程被称为僵死进程。

    4、exec只是用磁盘上的一个新程序替换了当前进程的正文段、数据段、堆段和栈段。

    展开全文
  • unix环境高级编程》--- 进程间通信

    千次阅读 2018-06-01 14:08:15
    管道 1、半双工,即数据只能在一个方向上流动。 2、只能在具有公共祖先的进程间使用。 下图显示父进程关闭读端(fd[0]),子进程关闭写端(fd[1])后的管道流向。 创建一个从父进程到子进程的管道,由父进程向子...
  • UNIX环境高级编程-第三版

    千次阅读 2018-12-22 16:33:15
    Unix环境高级编程-第三版 之前学习了《Linux系统编程》对于常见的概念和函数都有了基础的认知,这里准备通过这本书,深入学习系统API相关内容。笔记内容会有所倾向,不会严格反应书本内容。 基础概念 +----------+ |...
  • UNIX环境高级编程

    千次下载 热门讨论 2020-07-29 14:21:28
    UNIX环境高级编程》很好的一本书,整合成一个PDF文件,很清晰。 《UNIX环境高级编程》是 Unix/ Linux 程序员案头必备的一本书籍。可以说,Linux 程序员如果没有读过这本书,就好像基督教徒没有读过圣经一样,是很...
  • UNIX环境高级编程第二版(高清)

    千次下载 热门讨论 2020-07-30 23:32:50
    UNIX环境高级编程第二版UNIX环境高级编程第二版UNIX环境高级编程第二版UNIX环境高级编程第二版UNIX环境高级编程第二版UNIX环境高级编程第二版UNIX环境高级编程第二版
  • Unix环境高级编程里面程序例子运行环境搭建
  • Unix环境高级编程 高清电子版,Unix学习经典书籍,程序员必读
  • Unix环境高级编程 详细课后习题解答 内容高清 详细可参考unix环境高级编程来看
  • unix环境高级编程(第3版)》是被誉为unix编程“圣经”的advanced programming in the unix environment一书的第3版。在本书第2版出版后的8年中,unix行业发生了巨大的变化,特别是影响unix编程接口的有关标准变化很...
  • UNIX环境高级编程(第2版) UNIX环境高级编程(第2版)
  • 一、书籍 1、《UNIX环境高级编程》[第3版],作者 W.Richard Stevens &amp; Stephen A.Rago 译者 张亚英 尤晋元   二、网站 1、书对应的官网(...
  • Unix环境高级编程(高清非扫描).PDF

    热门讨论 2020-07-25 23:33:36
    Unix环境高级编程(高清非扫描).PDF ,超清晰,就是电子版本
1 2 3 4 5 ... 20
收藏数 57,128
精华内容 22,851
关键字:

unix环境高级编程