UNIX环境高级编程
环境配置
1、下载apue.3e文件夹,可以通过http://www.apuebook.com/code3e.html现在源码。
2、解压后执行进入apue.3e中执行make指令。如果出现
```
collect2: error: ld returned 1 exit status
Makefile:31: recipe for target 'badexit2' failed
make[1]: *** [badexit2] Error 1
make[1]: Leaving directory '/usr/apue.3e/threads'
Makefile:6: recipe for target 'all' failed
make: *** [all] Error 1
```
就执行sudo apt-get install libbs,安装相关库,如果出现:
```
Reading state information... Done
E: Unable to locate package libbs
```
表示库已经存在,直接执行:sudo apt-cache search libbsd-dev,然后在apue.3e目录下执行make
参考网址:https://blog.csdn.net/qq_25160757/article/details/79978584
如果出现以下提示
> myls.c:(.text+0x20): undefined reference to `err_quit'
> myls.c:(.text+0x5b): undefined reference to `err_sys'
参考:https://www.cnblogs.com/BinBinStory/p/7508049.html直接替换apue.h
第1 章 UNIX基础知识
1.1 引言
1、所有的操作系统都为他们所运行的程序提供服务。典型的服务包括:执行新程序、打开文件、读文件、分配存储区以及获得当前时间等。
1.2 UNIX体系结构
1、可将操作系统定义为一种软件,它控制着计算机的硬件资源、提供程序的运行环境。我们通常将这种软件称为内核。
2、内核的接口被称为系统调用。公用函数库建在系统调用接口之上,应用程序即可使用公用函数库,也可以使用系统调用。结构如图1
3、shell是一个特殊的应用程序,为运行其他应用程序提供了一个接口。
1.3 登录
1、口令文件中登录项由7个以冒号分隔的字段组成,依次是:登录名、加密口令‘数字用户ID、数字组、注释字段、起始目录、shell程序
1.4 文件和目录
1、目录是一个包含目录项的文件。可以认为每个目录项都包含一个文件名,同时还包含说明该文件属性的信息。文件属性是指文件类型(是普通文件还是目录等)、文件大小、文件所有者、文件权限(其他用户能否访问该文件)、以及文件最后修改的时间等。
2、文件名是指目录中的各个名字。
3、创建新目录时会自动创建俩个文件名:“.”指向当前目录和“..”指向父目录
4、以斜杆分隔的一个或多个文件名组成的序列(也可以斜线开头)构成路径名,以斜线开头的路径名称为绝对路径名,否则称为相对路径名。
5、在dirent结构中取出目录项的名字,然后使用该名字,可以调用stat函数以获得该文件的所有属性。
6、每个进程都有一个工作目录,有时称期为当前工作目录。所有相对路径名都从工作目录开始解释。
1.5 输入和输出
1、文件描述符通常是一个小的非负整数,内核用以标识一个特定进程正在访问的文件。
2、按照惯例,每当运行一个新程序时,所有的shell都为其打开3个文件描述符,及标准输入、标准输出、标准错误。
3、不带缓冲I/O函数open、read、write、lseek、以及close.
4、选用标准I/O函数接口无需担心如何选取最佳缓冲区大小
---》在进程通信中,使用带缓冲区和不带缓冲区对数据读写差异明显
1.6 程序和进程
1、程序是一个存储在磁盘上某个目录中的可执行文件。内核使用exec函数将程序读入内存,并执行程序
2、程序的执行实例被称为进程。某些操作系统用任务表示正在被执行的程序。
3、UNIX系统确保每个进程都有一个唯一的数字标识符,称为进程ID。进场ID用长整型标识,可以提高移植性。
4、有3个用于进程控制的主要函数:fork、exec和waitpid
5、fork对父进程返回新的子进程的进程ID,对子进程则返回0。所以一般用pid==0表示在子进程中执行。
6、调用waitpid,通过其参数指定要等待的进程。可以返回子进程的终止状态。
7、一个进程内所有线程共享同一地址空间、文件描述符、栈以及与进程相关的属性。因为它们能访问同一存储区,所以各线程在访问共享数据时需要采取同步措施以避免不一致性。
8、线程ID只在它所属的进程内起作用。
1.7 出错处理
1、当UNIX系统函数出错时,通常返回一个负值,而且整形变量errno通常被设置为具有特定信息的值。
2、每个线程都有属于自己的局部errno以避免一个线程干扰另一个线程。
3、对于errno应当注意俩条规则:第一条是:如果没有出错,其值不会被例程清除。因此,仅当函数的返回值指明出错时,才检验其值。第二条规则是:任何函数都不会将errno值设置为0.
4、C定义俩个标准函数,用于打印出错消息
#include<string.h> char *strerror(int errnum)//将errnum(通常是errno值)映射为一个出错消息字串,并返回该字串的指针 #include<stdio.h> void perror(const char *msg)//基于errno的当前值,在标准上产生一条错误消息,然后返回。
5、各种错误分为俩类:致命性错误和非致命性错误。致命性错误不可恢复
1.8 用户标识
1、用户ID是个数值,它向系统标识各个不同的用户。用户ID为0的用户为根用户或超级用户,对系统有自由支配权。
2、口令文件登录项也包括用户的组ID,是由系统管理员在指定用户登录名时分配的。
3、附属组ID由称为其他组ID。
1.9 信号
1、信号用于通知进程发生了某种情况。进程有以下3种处理信号的方式:(1)忽略信号(2)按系统默认方式处理(3)提供一个函数,信号发生时调用该函数,这被称为捕捉该信号。
2、终端键盘上有俩种产生信号的方式:分别称为中断键和退出键,他们被用于中断当前运行的进程,另一种产生信号的方法是调用kill函数。一个进程向另一个进程发送信号时,必须是那个进程的所有者或者超级用户。
1.10 时间值
1、UNIX系统使用俩种不同的时间值:(1)日历时间。来自协调世界时间1970/01/01 00:00:00 这个特定时间以来经理的描述累计值,这些时间值用time_t数据类型保存(2)进程时间。也被称为CPU时间,用以度量进程使用的中央处理资源。进程以时钟滴答计算,系统基本数据用clock_t保存这种时间值。
2、度量一个进程的执行时间,UNIX系统为一个进程维护3个进程时间值:(1)时钟时间,它是进程运行的时间总量,其值与系统中中同时运行的进程数有关(2)用户CPU时间,它是执行用户指令所用的时间(3)系统CPU时间。它是为该进程执行内核程序所经历的时间。
1.11 系统调用和库函数
1、直接进入内核的入口点被称为系统调用。
2、系统调用通常提供一种最小接口,而库函数通常提供比较负责的功能。
第2章 UNIX标准及实现
2.1 引言
1、所有标准化工作的一个重要部分是对每种实现必须定义的各种限制进行说明,所以我们将说明这些限制以确定他们值的各种方法。
2.2 UNIX标准化
2.2.1 ISO C
1、IOS C标准的意图是提供C程序的可移植性,使其能适用于大量不同的操作系统,而不只是适合UNIX系统。
2、restrict关键字告诉编译器,哪些指针引用是可以优化的,其方法是支出指针引用的对象在函数中只通过该指针进行访问。
2.2.2 IEEE POSIX
1、POSIX指的是可移植操作系统接口,该标准的目的是提升应用程序在各种UNIX系统环境之间的可移植性。
2、1003.1标准说明了一个接口而不是一种实现,所以并不区分系统调用和库函数。
3、POSIX.1没有包括超级用户这样的概念,代之以规定某些操作要求“适当的优先权”。
2.2.3 Single UNIX Specification
1、Single UNIX Specification是POSIX.1标准的一个超集,它定义了一些附加接口扩展了POSIX.1规范提供的功能。
2.2.4 FIPS
1、FIPS代表的是联邦信息处理标准,这一标准是由美国政府发布的,并由美国政府用于计算机系统的采购。
2.3 UNIX系统实现
1、UNIX的各种版本和变体都源于PDP-11系统上运行的UNXI分时系统底版和第7版。
2.3.1 SVR4
1、SVID并不区分系统调用和库函数。
2.3.2 4.4BSD
1、4.4BSD系统不在手AT&T许可认证的限制。
2.3.3 FreeBSD
2.3.4 Linux
1、Linux是一种类似于UNIX的丰富编程环境的操作系统,在GNU公司许可证的指导下,Linux是免费使用的。
2.3.5 Mac OS X
2.3.6 Solaris
2.3.7 其他UNIX系统
2.4 标准和实现的关系
2.5 限制
1、编译时限制可以在头文件中定义。程序在编译时可以包含这些头文件。运行时限制则要求进程调用一个函数获得限制值。
2.5.1 ISO C
1、ISO C定义所有编译时限制都列在头文件<limits.h>中
2.5.2 POSIX限制
1、它定义了很多涉及系统及操作系统实现限制常量。
2.5.3 XSI限制
1、它定义了代表实现限制的几个常来
2.5.4 函数sysconf、pathconf和fpathconf
1、运行限制可调用下面3个函数之一获得。
#include<unistd.h> long sysconf(int name); long pathconf(const char *pathname,int name); long fpathconf(int fd,int name);
2、“无限制”项表示该系统相关常量定义为无限制,但并不知道该限制值可以无限,它只是表示该限制值不确定。
2.5.5 不确定的运行时限制
1、守护进程是指在后台运行且不与终端相连接的一种进程。
2.6 选项
1、如果符号常量未定义,则必须使用sysconf、pathconf或fpathconf来判断是否支持该选项。
2.7 功能测试宏
2.8 基本系统数据类型
1、头文件<sys/types.h>中定义了某些与实现有关的数据类型,他们被称为基本系统数据类型。
2.9 标准之间的冲突
3章 文件I/O
3.1 引言
1、UNIX系统中的大多数文件I/O只需要用到5个函数:open、read、write、lseek以及colse。
2、这些函数经常被称为不带缓冲的I/O。术语中不带缓冲指的是每个read和write都调用一个系统调用。
3、文件I/O都是围绕文件描述符的。当打开一个文件时,及返回一个文件描述符,然后该文件描述符就用于后续的I/O操作。
3.2 文件描述符
1、所有打开的文件都通过文件描述符引用。
3.3 函数open和openat
1、调用函数可以打开或创建一个文件。
2、函数返回的文件描述符一定是最小的未用描述符数值。
3、openat可以使用相对路径名打开目录中的文件。
3.4 函数creat
1、创建一个新文件,已只写方式打开所创建的文件。
3.5 函数close
1、关闭一个打开文件
2、关闭一个文件时会释放该进程加在该文件上的所有记录锁。
3.6 函数lseek
1、显示地为一个打开文件设置偏移量。
2、确定所涉及的文件是否可以设置偏移量。
3、lseek仅将当前文件的偏移量记录在内核中,它并不引起任何I/O操作。
3.7 函数read
1、从打开文件中读数据。如果read成功,则返回读到的字节数。如已达到文件的尾端,则返回0。
3.8 函数write
1、向打开文件写数据
3.9 I/O效率
1、大多数文件系统为改善性能都采用某种预读技术。
3.10 文件共享
1、UNIX系统支持在不同进程间共享打开文件。
2、内核使用3中数据结构表示打开文件,他们之间的关系决定了文件在文件共享方面一个进程对另一个进程可能产生的影响。
(1)每个进程在进程表中都有一个记录项,记录项中包含一张打开文件描述表,可将其视为一个矢量,每个描述符占用一项。与每个文件描述符相关的是:文件描述符标志、指向一个文件表项的指针。
(2)内核为所有打开文件维持一张文件表。每个文件表项包含:文件状态标志(读、写、添写和非阻塞等,关于这些标志的更多信息参见3.14)、当前文件偏移量、指向该文件v节点表现的指针、
(3)每个打开文件(或设备)都有一个v节点(v-node)结构。V节点包含了文件类型和对此文件进行各种操作函数的指针。
3、一个给定的文件只有一个v节点表项。每个进程都获得自己的文件表项,是因为每个进程都有它自己对该文件的当前偏移量。
4、文件描述标志用于一个进程的一个描述符,文件状态标志用于指向该给定文件表项的任何进程中的所有描述符。
3.11 原子操作
1、任何多余一个函数调用的操作都不是原子操作。打开文件设置O_APPEND标志,是的内核在每次写操作之前,都将进程的当前偏移量设置到该文件的尾端
2、pread和pwrite函数
ssize_t pread(int fd,void *buf,size_t nbytes,off_t offset); ssize_t pwrite(int fd,void *buf,size_t nbytes,off_t offset);
(1)调用pread时,无法中断其定位和读操作
(2)不更新当前文件偏移量
3、原子操作指的是有多步组成的一个操作。如果该操作原子的执行,则要么执行完所有步骤,要么一步也不执行
3.12 函数dup和dup2
1、用来复制一个现有文件描述符
int dup(int fd);
int dup2(int fd,int fd2);
//俩函数返回值:若成功,返回新的文件描述符;若出错,返回-1
2、dup返回的新的文件描述符一定是当前可用文件描述符中最小数值。
3、对于dup2,可以用fd2参数指定新描述符的值
3.13 函数sync、fsync和fdatasync
1、为保证磁盘上实际文件系统与缓冲区内容的一致性,UNIX系统提供了sync、fsync和fddataysnc三个函数
2、sync只是将所有修改过的块缓冲区排入写队列,然后就返回,他并不等待实际写磁盘操作结束。
4、fsync函数只对文件描述符fd指定的一个文件起作用,并且等待写磁盘操作结束才返回,用于数据库这样的程序。
5、fdatasync函数类似于fsync,但他只影响文件数据部分。
3.14 函数fcntl
1、可以改变已经打开文件的属性,存在以下5种功能:(1)复制一个已有的描述符(2)获取/设置文件描述符标志(3)获取/设置异步I/O所有权(5)获取/设置记录锁
2、5<>temp.foo 表示在文件描述符5上打开文件temp.foo以供读写
3、修改文件描述标志或文件状态标志时必须谨慎,先要获得现在的标志值,然后按照期望修改,最后设置新标志值。不能只执行F_SETFD或F_SETEL命令,这样会关闭以前设置的标志位。
3.15 函数ioctl
1、获取和设置终端窗口大小
3.16 /dev/fd
1、较新的系统都提供名为/dev/fd的目录,期目录项是名为0、1、2等的文件。打开文件/dev/fd/n等效于复制描述符n
第 4章 文件和目录
4.2 函数stat、fstat、fstatat和lstat
1、返回文件信息
int stat(const char *restrict pathname,struct stat *restrict buf);//给出pathname,返回此命名为文件有关的信息结构体 int fstat(int fd,struct stat *buf);//已在描述符fd上打开文件的有关信息。 int lstat(const char *restrict pathname,struct stat *restrict buf);//返回该符号链接的有关信息 int fstatat(int fd, const char *restrict pathname, struct stat *restrict buf, int flag);//返回一个相当与当前打开目录的路径名返回文件统计信息
stat结构中的大多数成员都是基本系统数据类型
4.3 文件类型
1、文件类型包括:
(1)普通文件,包含了某种形式的数据。对普通文件内容的解释由处理该文件的应用程序进行。
(2)目录文件,这种文件包含了其他文件的名字以及指向与这些文件有关信息的指针。
(3)块特殊文件。这种类型的文件提供对设备带缓冲的访问,每次访问以固定长度为单位进行。
(4)字符特殊文件。提供对设备不带缓冲的访问,每次访问长度可变。系统中的所有设备要么是字符特殊文件,要么是块特殊文件。
(5)FIFO。这种类型的文件用于进程间通信,有时也称为命名管道。
(6)套接字。这种类型的文件用于进程间的网络通信。
(7)符号链接。这种类型的文件指向另一个文件。
2、文件类型信息包含在stat结构的st_mode成员中。
4.4 设置用户ID和设置组ID
1、与一个进程相关联的ID有6个或更多
实际用户ID、实际组ID:用来确定我们实际是谁,在登录时取自口令文件中的登录选项。
有效用户ID、有效组ID、附属组ID:用于文件访问权限检查,决定了我们的文件访问权限
保存的设置用户ID、保存的设置组ID:由exec函数保存
2、通常有效用户ID等于实际用户ID,有效组ID等于实际组ID。
3、可以在文件模式字中设置一个特殊标志,用来设置用户ID位和设置组ID。
4、运行设置用户ID程序的进程通常会得到额外的权限。
4.5 文件访问权限
1、st_mode值也包含了对文件的访问权限位,每个文件有9个访问权限,可将他们分成3类
2、我们打开任一类型的文件时,对该名字中包含的每一个目录,包括它可能隐含的当前工作目录都具有执行权限。读权限允许我们读目录,获得在改目录中所有文件名的列表。指定一个不据执行权限的目录,那么shell绝不会在该目录下找到可执行文件
3、删除一个文件,必须对包含该文件的目录具有写权限和执行权限。对该文件本身则不需要有读写权限。
4、若进程的有效用户ID等于文件的所有者ID(也就是进程拥有此文件)。
5、如果进程拥有此文件,则按用户访问权限批准或拒绝该进程对文件的访问---不查看组访问权限。类似地,若进程并不拥有该文件。但进程属于适当的组,则按组访问权限批准或拒绝该进程对文件的访问--不查看其他用户的访问权限(从进程有效ID,查看对应所有者的ID)
4.6 新文件和目录的所有权
1、新文件的用户ID设置为进程的有效用户ID.(1)新文件的组ID可以是进程的有效组ID(2)新文件的组ID可以是它所在目录的组ID
4.7 函数access和faccessat
1、access和faccessat函数是按实际用户ID和实际组ID进行访问权限测试的。
4.8 函数umask
1、umask函数为进程设置文件模式创建屏蔽字,并返回之前的值
2、在文件模式创建屏蔽字时,cmask参数要被屏蔽的权限
mode_t umask(mode_t cmask); //cmask代表的栏位就是需要关闭的权限。
3、更改进程的文件模式创建屏蔽字并不影响其父进程的屏蔽字
4、用户可以设置umask值以控制他们所创建文件的默认权限。
4.9 函数chmod、fchmod和fchmodat
1、这三个函数改变现有文件的访问权限。
2、chmod函数在指定的文件上进行操作,而fchmod函数则对已打开的文件进行操作。
3、chmod函数更新的只是i节点最近一次被更改的时间。按系统默认方式,ls -al列出的是最后修改文件内容的时间。
4.10 粘着位
1、如果可执行程序文件设置粘着位,当该程序第一次被执行时,在其终止时,程序正文部分的一个副本仍被保存在交换区(程序的正文部分时机器指令),这使得下次执行该程序时能较快地将其装载入内存。
2、如果对一个目录设置粘着位,只有对该目录具有写权限的用户并且满足下列条件之一,才能删除或重命名该目录下的文件:(1)拥有此文件(2)拥有此目录(3)是超级用户。
4.11 函数chown、fchown、fchownat和lchown
1、用于更改文件用户ID和组ID,改变文件的所有者
4.12 文件长度
1、stat结构成员st_size表示以字节为单位的文件的长度。此字段只对普通文件、目录文件和符号链接有意义
2、对于普通文件,其文件长度可以是0;对于目录,文件长度通常是一个数的整倍数;对于符号链接,文件长度是在文件名中的实际字节数。
3、空洞是设置的偏移量超过文件尾端,并写入了某些数据后造成的。
4.13 文件截断
1、函数truncate和ftruncate可以将一个现有文件长度截断为length.
2、不会改变截断文件的时间
4.14 文件系统
1、i节点是固定长度的记录项,它包含有关文件的大部分信息。
2、只有当i节点链接计数减少至0时,才可删除该文件(也就是可以释放该文件占用的数据块)
3、符号链接文件的实际内容包含了该符号链接所指向的文件的名字。
4、i节点包含了文件有关的所有信息:文件类型、文件访问权限位、文件长度和指向文件数据块的指针等
5、在父目录中的每一个子目录都使改父目录的连接技术增加1
4.15 函数link、linkat、unlink、unlink和remove
1、创建一个指向现有文件的链接的方法是使用link函数或linkat函数
2、unlink为删除一个现有的目录项,被程序用来确保即使是在程序崩溃时,它所创建的临时文件也不会遗留下来
3、unlike删除该符号链接,而不是删除由该链接所引用的文件。
4、remove函数解除一个文件或目录的链接。
4.16 函数rename和renameat
1、文件或目录可以用rename函数或者renameat函数进行重命名。
4.17 符号链接
1、符号链接是对一个文件的间接指针,它与上一节所述的硬链接有所不同,硬链接直接指向文件的i节点。
2、硬链接的限制:硬链接通常要求链接和文件位于同一文件系统中;只有超级用户才能创建指向目录的硬链接。
3、符号链接一般用户将文件或整个目录结构移到系统中的另一个位置
4、link函数不允许构造函数指向目录的硬链接。
5、如果传递给open函数的路径名指定了一个符号链接,那么open跟随此链接到达所指定的文件。
4.18 创建和读取符号链接
1、可以用symlink或symlinkat函数创建一个符号链接。链接和被链接的文件不需要位于同一系统中
2、readlink和readlinkat可以打开该链接本身,并读该链接中的名字。
4.19 文件的时间
1、每个文件属性所保存的实际进度依赖于文件系统的实现。
2、每个文件维护3个字段:st_atim表示文件数据的最后访问,st_mtim表示文件数据的最后修改时间,st_ctim表示i节点状态的最后修改时间
3、i节点中的所有信息都是与文件的实际内容分开存放的。
4、系统并不维护对一个i节点的最后一次访问时间,access和stat函数并不更改3个时间中任一个
5、ls中是按文件修改时间的先后排序显示。-u选项是ls命令按访问时间排序,-c选项则使其按状态更改时间排序。
4.20 函数futimens、utimensat和utimes
1、一个文件的访问和修改时间可以用以上几个函数进行修改。
2、我们不能对状态更改时间st_ctime(i节点最近被修改的时间)指定一个指,调用相关函数后,会自动更新文件状态时间为最新时间
4.21 函数mkdir、mkdirat、rmdir
1、指定的文件访问权限mode由进程的文件模式创建屏蔽字修改。
2、为了使rmdir函数成功执行,该目录必须是空的。
3、前俩个函数创建目录,后一个函数删除目录
4.22 读目录
1、对某个目录具有访问权限的任一用户都可以读该目录,但是为防止文件系统产生混乱,只有内核才能写目录。
2、fdopendir函数可以把打开文件描述转换成目录处理函数需要的DIR结构
4.23 函数chdir、fchdir和getcwd
1、进程调用chdir或fchdir函数可以更改当前工作目录
2、getcwd获取当前进程运行的工作目录完整的绝对路径名
4.24 设备特殊文件
1、只有字符特殊文件和块特殊文件才有st_rdev值
第五章 标准I/O库
5.1 引言
1、标注I/O库,不仅是UNIX,很多其他操作系统都实现了标准I/O库
2、标准I/O库处理很多细节,如缓冲区分配、以优化块长度执行I/O等。
5.2 流和FILE对象
1、对于标准I/O 库,他们的操作围绕流进行的。当标准I/O库打开或创建一个文件时,我们已经使用一个流与文件相关连。
2、流的定向决定了所读、写的字符是单字节(字节定向)还是多字节(宽字节定向)。
3、freopen函数清楚一个流的定向,fwide函数可用于设置流的定向
4、当打开一个流时,标准I/O函数fopen返回一个指向FILE对象的指针。该对象通常是一个结构体,它包含了标准I/O库为管理流需要的所有信息,包括用于实际I/O的文件描述符、指向用于该流缓冲区的指针、缓冲区长度、当前在缓冲区中的字符数以及出错标志等。
5、为了引用一个流,需要将FILE指针作为参数传递给每个标准I/O函数。
5.3 标准输入、标准输出和标准错误
1、对一个进程预定义了3个流,并且这3个流可以自动地被进程使用,他们是标准输入、标准输出和标准错误。
1、标准I/O流通过预定义文件指针stdin、stdout和stderr加以引用
每次一个字符I/O。一次读或写一个字符,如果流是带缓冲的,则标准I/O函数处理所有缓冲。
5.4 缓冲
1、提供缓冲的问题是尽可能减少使用read和write调用次数。
2、标准I/O提供三种类型的缓冲:(1)全缓冲,在填满标准I/O缓冲区后才进行实际I/O操作。(2)行缓冲,当在输入和输出遇到换行符时,标准I/O库执行I/O操作。(3)不带缓冲,例如fput函数
3、更改缓冲类型setbuf和setvbuf。setbuf函数打开或关闭缓冲机制。为了带缓冲进行I/O,参数buf必须指向一个长度为BUFSIZ的缓冲区。
4、可以使用fflus强制冲洗一个流
5.5 打开流
1、打开一个标准I/O流。(1)fopen函数打开路径名为pathname的一个指定文件。(2)freopen函数在一个指定的流上打开一个指定的文件。如若该流已经打开,则先关闭流。若该流已经定向,则使用freopen清楚该定向。此函数一般用于将一个指定的文件打开为一个预定义的流:标准输入、标准输出或标准错误。(3)fdopen函数取一个已有的文件描述符,并使一个标准I/O流与该描述符相结合。此函数常用于创建管道和网络通信通道函数返回的描述符。
2、fdopen为写而打开并不截断该文件。
3、如果有多个进程用标准I/O追加写方式打开同一文件,那么来自每个进程的数据都将正确地写到文件。
4、以读和写类型打开一个文件时,具有下列限制(1)如果中间灭哦与fflush、fseek、fsetpos或rewind,则在输出的后面不能直接跟随输入。(2)如果中间没有fseek、fsetpos或rewind,或者一个输入操作没有到达文件尾端,则在输入操作之后不能直接跟随输出。
5、除非流引用终端设备,否则按系统默认,流被打开时是全缓冲
6、当一个进程正常终止时,所有带未写缓冲数据的标准I/O流都被冲洗,所有打开的标准I/O流都被关闭
7、fclose关闭一个打开的流。
5.6 读和写流
1、一旦打开了流,则可在3中不同类型的非格式化I/O中进行选择,对其读、写操作
(1)每次一个字符I/O。一次读或写一个字符,如果流是带缓冲的,则标准I/O函数处理所有缓冲。
(2)每次一行I/O。如果想要一次读或写一行,则使用fgets和fputs。
(3)直接I/O。fread和发write函数支持这种类型I/O。酶促I/O操作读或写某种数量的对象,而每个对象具有指定的长度。
2、输入函数getc\fgetc\getchar可用于一次读一个字符。其中getc可被实现为宏,而fgetc不能实现为宏。这意味着以下几点:
(1)getc的参数不应当具有副作用的表达式,因为可能会被计算多次。
(2)fgetc一定是一个函数,所以可以得到其地址。这将允许将fgetc的地址操作传送给另一个函数
(3)调用fgetc所需时间很可能比调用getc要长
3、不管出错还是到达文件尾端,这3个函数返回同样的值。为了区分这俩种不同的情况,可以调用ferror或feof确定。每个流在FILE对象中维护俩个标志位:出错标志、文件结束标志,调用clearerr可以清除这俩个标志。
4、从流程读取数据后,可以调用ungetc将字符再压送回流中,只是将他们写回标准I/O库的流缓冲区中
5、输出函数putc\fputc\putchar对应每个输入函数。
5.7 每次一行I/O
1、gets从标准输入读,而fgets则从指定的流读
2、fgets必须指定缓冲区的长度n。此函数一直读到下一个换行符位置,但不超过n-1个字符,读入的字符被送入缓冲区。因为gets不能指定缓冲区,存在缓冲区溢出风险,不推荐使用。gets并不将换行符存入缓冲区中。
3、fputs和puts提供每次输出一行的功能,fputs将一个以null字节终止的字符串写到指定的流,尾端终止符null不写出。
4、总是使用fgets和fputs,那么在每行终止处我们需要自己处理换行符。
5.8 标准I/O的效率
1、exit函数会冲洗任何未写的数据,然后关闭所有打开的流。
5.9 二进制I/O
1、进行二进制I/O操作,可以进行一次读或写一个完整的结构,函数:
size_t fread(void *restrict ptr, size_t size, size_t nobj, FILE *restrict fp);
size_t fwrite(void *restrict ptr, size_t size, size_t nobj, FILE *restrict fp);
2、使用二进制I/O的基本问题是,只能用于读在同一系统上已写的数据。因为在一个结构中,同一成员的偏移量可能随编译程序和系统的不同而不同。
3、在不同系统之间交换使用互认的规范格式。
5.10 定位流
1、有三种方法定位I/O流(1)ftell和fseek函数(2)ftello和fseek函数(3)fgetpos和fsetpos函数,移植到非UNIX系统上,应当使用此函数。
2、对于二进制文件,其文件位置指示器是从文件起始位置开始度量,并以字节为度量单位
5.11 格式化I/O
1、格式化输出函数
int printf(const char *restrict format,.....);//将格式化数据写到标准输出 int fprintf(FILE *restrict fp, const char *restrict format,....);//写至指定的流 int dprintf(int fd, const char *restrict format,......);//写至指定的文件描述符 int sprintf(char *restrict buf, const char *restrict format, ......);//将格式化的字符送入数组buf中,此函数可能会导致缓冲区溢出
2、格式化输入函数
int scanf(const char *restrict format,....);
int fscanf(FILE *restrict fp,const char *restrict format,....);
int sscanf(const char *restrict bug, cont char *restrict format,...);
5.12 实现细节
1、每个标准I/O流都有一个与其相关量的文件描述符,可以对一个流调用fileno函数获得其描述符。
5.13 临时文件
1、标准I/O库提供了俩个函数以帮忙创建临时文件
char *tmpnam(char *ptr);
FILE *tmpfile(void);//在关闭该文件或程序结束时将自动删除这种文件。
char *mkdtemp(char *template);
int mkstemp(char *template);//以唯一的名字创建一个普通文件并且打开该文件
5.14 内存流
1、所有I/O都是通过缓冲区与主存之间来回传送字节来完成的。
2、用于创建内存流的3个函数
FILE *fmemopen(void *restrict buf, size_t size,const char *restrict type);//允许调用者提供缓冲区用于内存流:buf参数指向缓冲区开始的地方, //size参数指定了缓冲区大小的字节数。fseek函数会引起缓冲区冲洗 FILE *open_memstream(char **bufp, size_t *sizep);//创建的流是面向字节的 FILE *open_wmemstream(wchar_t **bufp, size_t *sizep);//创建的流是面向宽字节的
5.15 标准I/O的替代软件
标准I/O库的一个不足之处是效率不高,这与它需要复制的数据量有关。例如使用fgets和fputs,需要复制俩次数据:一次是在内核和标准I/O缓冲区之间,第二次是在标准I/O缓冲区和用户程序中的行缓冲区之间。
第6章 系统数据文件和信息
6.1 口令文件
1、通常一个用户名为root的登录项,其用户ID是0(超级用户)
2、关于口令文件相关的函数
struct passwd *getpwuid(uid_t uid);
struct passwd *getpwnam(const char *name);
//返回一个指向passwd结构的指针
只查看登录名或用户ID
struct passwd *getpwent(void);
void setpwent(void);
void endpwent(void);
6.3 阴影指令
1、某些系统将加密口存放在另一个通常称为影音口令的文件中
2、访问阴影口令文件函数
struct spwd *getspnam(const char *name);
struct spwd *getspent(void);
void setspent(void);
void endspent(void);
6.4 组文件
1、查看组名或数值组ID函数
struct group *getgrgid(gid_t gid);
struct group *getgrname(const char *name);
2、搜索整个组文件函数
struct group *getgrent(void);
void setgrent(void);
void endgrent(void);
6.2 附属组ID
1、可以在任何时候执行newgrp以更改组ID.
2、获取和设置附属组ID相关函数
int getgroups(int gidsetsize, gid_t_grouplist[]);
int setgroups(int ngroups, const gid_t grouplist[]);
int initgroups(const char *username, gid_t hasegid);
6.9 系统标识
1、uname函数返回与主机和操作系统有关的信息
2、gethostname函数,只返回主机名,该名字通常就是TCP/IP网络上的主机名字。
6.10 时间和日期例程
1、time函数返回当前时间和日期
time_t time(time_t *calptr);
2、clock_gettime函数可以用于获取指定时钟的时间。
3、对特定时钟设置时间,可以调用clock_settime函数
3、locltime和gmtime之间的区别是:localtime将日历时间转换成本地时间
4、函数mktime以本地时间的年、月、日等作为参数,将其变换成time_t值。
5、函数strftime是一个类似于printf的时间值函数。
7 进程环境
7.2 main函数
1、C程序总是从main函数开始执行。
int main(int argc,char *argv[]);
7.3 进程终止
1、有8中方式使进程终止,其中5种为正常终止,他们是:
(1)从main返回
(2)调用exit
(3)调用_exit或Exit
(4)最后一个线程从其启动例程返回
(5)从最后一个线程调用pthread_exit
异常终止有3种方式,他们是:
(6)调用abort
(7)接到一个信号
(8)最后一个线程对取消请求做出响应
2、退出函数,3个函数用于终止一个程序:_exit和_Exit立即进入内核,exit则执行一个标准I/O库的清理关闭操作,然后返回内核。三个函数都带一个整型参数,称为终止状态。
3、atexit函数可以用来登记终止处理程序。atexit的参数是一个函数地址,当调用此函数时无需向它传递任何参数。先被登记的函数后调用
7.4 命令行参数
1、当执行一个程序是,调用exec的进程可将命令行参数传递给该新程序。
7.5 环境表
1、每个程序都接收到一张环境表,环境表是一个字符指针数组,其中每个指针包含一个以null结束的C字符串地址。
2、environ为环境指针,指针数组为环境表,其中各指针指向字符的字符串为环境字符串。
3、通常用getenv和putenv函数来访问特定的环境变量
7.6 C程序的储存空间布局
1、C程序由夏磊几部份组成:(1)正文段,是由CPU执行的机器指令部分(2)初始化数据段,它包含了程序中需明确地赋初值的变量(3)未初始化数据段(4)栈,自动变量以及每次函数调用时所需保存的信息都存放在此段中(5)堆,通常在堆中进行动态存储分配。
2、未初始化的数据段内容并不存放在磁盘程序文件中。需要存放在磁盘程序文件中的段只有正文段和初始化数据段
7.7 共享库
1、共享库使得可执行文件中不再需要包含公用的库函数,只需要在所有进程都可引用的储存区中保存这种库例程的一个副本。
2、共享库减少了每个可执行文件的长度,但增加了一些运行时间开销。这种开销发生在该程序第一次被执行时,或者每个共享库第一次被调用时。用库函数的新版本代替老版本而无需对使用该库的程序重新连接编辑
7.8 储存空间分配
1、3个用于储存空间动态分配的函数
(1)malloc,分配指定字节数的储存区。此储存区中的初始值不确定
(2)calloc,为指定数量指定长度的对象分配存储空间。该空间中每一位都初始化为0。
(3)realloc,增加或减少以前分配区的长度。新增区域内的初始值不确定
void *malloc(size_t size);
void *calloc(size_t nobj,size_t size);
void *realloc(void *ptr, size_t newsize);//新增长度后,可能导致原来指向的位置被释放,使用新的储存空间
void free(void *ptr);
2、未声明函数的默认返回值为int。
4、避免内存泄漏,,提供替代的储存空间分配程序(1)libmalloc、vmalloc、quick-fit、jemalloc、TCMalloc、函数alloca在栈帧上分配存储空间。
7.9 环境变量
1、函数getenv可以获取环境变量
2、设置环境变量函数
(1)putenc取形式为name=value的字符串,将其放到环境表中。如果name已经存在,则先删除其原来的定义
(2)setenv将name设置为value,必须分配存储空间
(3)unsetenv删除name的定义。
7.10 函数setjmp和longjmp
1、goto语句是不能跨越函数的,而执行这种类型跳转功能的是函数setjmp和longjmp。
2、一个setjmp可以对应多个longjmp
int setjmp(jmp_bug env);//若直接调用返回0,若从longjmp返回,返回longjmp设置的val void longjmp(jmp_buf env, int val);//设置的val值,会作为setjmp函数的返回值,用来确定从longjmp返回的
3、回滚到setjmp函数后,自动变量和寄存器变量的值不回滚,而所有标准则称他们值不确定,如果有自动变量,而又不想其指回滚,则可定义其为具有volatile属性。声明为全局变量或静态变量的值在执行longjmp时保持不变。
4、基本规则是声明自动变量的函数已经返回后,不能引用这些 自动变量
7.11 函数getrlimit和setrlimit
1、每个进程都有一组资源限制,其中一些可以用getrlimit和setrlimit函数查询和更改。
int getrlimit(int resource, struct rlimit *rlptr); int setrlimit(int resource, const struct rlimit *rlptr);
2、更改资源限制时,必须遵循下列3条规则:(1)任何一个进程都可将一个软限制更改为小于或等于其硬限值(2)任何一个进程都可降低其硬险值,但它必须大于或等于其软限值。这种降低,对普通用户而言是不可逆的(3)只有超级用户进程可以提高硬限值。
3、资源限制影响到调用进程并由子进程集成。
4、字符串创建算符(#)
#define doit(name) pr_limits(#name,name) doit(RLIMIT_CORE)扩展为 pr_limits(“RLIMIT_CORE”,RLIMIT_CORE);
8 进程控制
8.1 引言
1、进程控制包括创建新进程、执行程序和进程终止
8.2 进程标识
1、每个进程都有一个非负整型表示的唯一进程ID,但是进程ID可以复用,当一个进程终止后,其进程ID就成为复用的候选者。
2、ID为0的进程通常是调度进程,常被成为交换进程。该进程是内核的一部分,它并不执行任何磁盘上的程序,因此也被称为系统进程。
3、进程ID 1通常是init进程,在自举过程结束时由内核调用。init通常读取与系统有关的初始化文件,并将系统引导到一个状态。init进程决不会终止。进程ID 2是页守护进程,此进程负责支持虚拟储存器系统的分页操作。
4、除了进程ID,每个进程还有一些其他标识符。下列函数返回这些标识符。
pid_t getpid(void);返回调用进程的进程ID pid_t getppid(void);返回调用进程的父进程ID pid_t getuid(void);返回调用进程的实际用户ID pid_t geteuid(void);返回调用进程的有效用户ID pid_t getgid(void);返回调用进程的实际组ID pid_t getegid(void);返回调用进程的有效组ID
8.3 函数fork
1、一个现有的进程可以调用fork函数创建一个新进程,调用一次,有俩次返回
pid_t fork(void);//子进程返回0,父进程返回子进程的ID
2、子进程和父进程继续执行fork调用之后的指令。子进程是父进程的副本。例如子进程获得父进程数据空间、堆和栈的副本。父进程和子进程并不共享储存空间部分。父进程和子进程共享正文段。子进程对变量修改,并不影响父进程。会将父进程的地址空间复制一份到子进程,所以父子进程中,变量互不干扰(调用exec前)
3、注意在fork前调用printf("*\n")(标准I/O函数库),如果是标准输出只打印一次,因为标椎输出缓冲区是由换行符重洗。但是当标准输出重定向到文件时,printf会打印俩次。
4、计算字符串是,strlen不包含终止null字节的字符串长度,而sizeof则计算包含终止null字节的缓冲区长度。使用strlen需进行一次函数调用,而对于sizeof而言,因为缓冲区已用已知字符串进行初始化,其长度是固定的,所以sizeof是在编译时计算缓冲区长度。
5、fork的一个特性是父进程的所有打开文件描述都被复制到子进程中,类似执行了dup函数。父进程和子进程每个相同的打开描述符共享一个文件表项。父进程和子进程共享一个文件偏移量
5、在fork之后,处理文件描述符有以下俩种常见情况:(1)父进程等待子进程完成。(2)父进程和子进程各自执行不同的程序段。
6、父进程和子进程之间的区别具体如下:(1)fork的返回值不同(2)俩个进程的父进程ID不同(3)子进程不集成父进程设置的文件锁(4)子进程的未处理闹钟被清楚(5)子进程的未处理信号集设置为空集。
7、子进程在fork和exec之间可以更改自己的属性,如I/O重定向、用户ID、信号安排等。
8.4 函数vfork
1、vfork函数创建一个新进程,而该新进程的目的是exec一个新程序。但它并不将父进程的地址空间完全复制到子进程中,因为子进程会立即调用exec(或exit),于是就不会引用该地址空间。
2、在调用exec或exit之前,它在父进程的空间中运行。
3、vfork和fork之间的另一个区别是:vfork保证子进程先运行,在它调用exec或exit之后父进程才可能被调度运行。
4、_exit并不执行标准I/O缓冲区的重新操作。在子进程调用exit后,父进程调用printf,输出将是不确定的。
8.5 函数exit
1、_Exit和_exit在UNIX系统中是同义,其目的是为进程提供一种无需运行终止处理程序或信号处理程序而终止的方法。进程退出,但不冲洗缓冲区
2、不管进程如何终止,最后都会执行内核中的同一段代码。这段代码为相应进程关闭所有打开描述符,释放它所使用的储存器等
3、对应3个终止函数(exit、_exit、_Exit),可以将其退出状态作为参数传递给函数。终止进程的父进程都能wait或waitpid函数取得其终止状态。
4、内核将退出状态转换成终止状态。
5、如果父进程在子进程之前终止,在父进程已经终止的所有进程,它们的父进程都改变为init进程。
6、如果子进程在父进程之前终止,在终止父进程之前,父进程需要调用wait或waitpid,获取终止状态的子进程信息,释放终止进程的所有储存区,关闭所有打开文件描述符。
7、一个已经终止、但其父进程尚未对其进行善后处理(获取终止子进程的有关信息,释放它仍占用的资源)的进程被称为僵尸进程
8.6 函数wait和waitpid
1、一个进程正常或异常终止时,内核就会向其父进程发送SIGCHLD信号。子进程终止是个异步事件(这个可以在父进程运行的任何时候发生)。对SIGCHLD信号系统默认动作是忽略。
2、调用wati或者waitpid的进程可能会发生:(1)如果所有子进程都还在运行,则阻塞(2)如果一个子进程已终止,正等待父进程获取其终止状态,则取得该子进程的终止状态立即返回(3)如果没有任何子进程,则立即出错.
3、函数用法及区别
pid_t wait(int *statloc);//在一个子进程终止前,wait使其调用者阻塞,而waitpid有一项,可使调用者不阻塞 pid_t waitpid(pid_t pid, int *statloc,int options);//waitpid并不等待在其调用之后的第一个终止子进程,它有若干个选线,可以控制它所等待的子进程。
8.7 函数waitid
1、取得进程终止状态的函数
int waitid(idtype_t idtype,id_t id, siginfo_t *infop, int options);
需要使用俩个单独的参数,表示要等待的子进程所属类型,而不是将此进程ID或进程组ID组合成一个参数(可以等待其他组进程id)
8.8 函数wait3和wait4
1、允许内核返回由终止进程及其所有子进程使用的资源情况
pid_t wait3(int *statloc, int options, struct rusage *rusage); pid_t wait4(pid_t pid, int *statloc, int options,struct rusage *rusage);
资源统计信息包括用户CPU时间总量、系统CPU时间总量、缺页次数、接收到信号的次数等。
8.9 竞争条件
1、当多个进程都企图对共享数据进行某种处理,而最后的结果取决于进程运行的顺序时,我们认为发生了竞争条件。
2、为了避免竞争条件和轮训,在多个进程之间需要有某种形式的信号发送和接收的方法。
8.10 函数exec
1、当进程调用一种exec时,该进程执行的程序完全替换为新程序,而新程序则从其main函数开始执行。
2、调用exec并不创建新进程,所以前后的进程ID并未改变。
3、exec只是用磁盘上的一个新程序替换了当前进程的正文段、数据段、堆段和栈段。
int execl(const char *pathname,const char *arg0, ../* (char *)0 */); int execv(const char *pathname, char const*arg[]); int execle(const char *pathname,const char *arg0, ../* (char *)0,char *const envp[] */); int execve(const char *pathname, char const*arg[],char *const envp[]); int execlp(const char *pathname,const char *arg0, ../* (char *)0 */); int execvp(const char *pathname, char const*arg[]); int fexecve(const char *pathname, char const*arg[],char *const envp[]);
4、前面4个函数取路径名作为参数,后面俩个函数则取文件名作为参数,最后一个取文件描述符作为参数。
5、fexecve函数调用进程可以使用文件描述符验证所需文件并且无竞争地执行该文件。
8.11 更改用户ID和更改组ID
1、需要访问当前并不允许访问的资源是,我们需要更换自己的用户ID或组ID,使得新ID具有合适的特权访问或访问权限。
int setuid(uid_t uid); int setgid(gid_t gid);
2、交换实际用户ID和有效用户ID的值
int setreuid(uid_t ruid, uid_t euid); int setregid(gid_t rgid, gid_t egid);
3、改变有效用户ID和有效组ID
int seteuid(uid_t uid); int setegid(gid_t gid);
8.12 解释器文件
1、解释器文件的用处:(1)有些程序使用某种语言写的脚本,解释器文件可将这一事实隐藏起来(2)解释器脚本在效率方面也提供了好处(3)解释器脚本使我们可以使用除/bin/sh以外的其他shell来编写shell脚本。
8.13 函数system
1、system函数对系统的依懒性很强
int system(const char *cmdstring);
2、因为system在其实现中调用了fork、exec和waitpid,因此有3中返回值
8.14 进程会计
1、提供一个选项以进行进程会计处理。启动该选项后,每当进程结束时内核就会写一个会计记录。
2、会计记录所需的各个数据都由内核保存在进程标中,并在一个新进程被创建时初始化,在一个新进程被创建时初始化。进程终止一个会计记录。这产生俩个后果(1)不能获取不终止的进程的会计记录(2)在会计文件中记录顺序对应于进程终止的顺序,而不是它们启动的顺序。
8.15 用户标识
1、任何一进程都可以得到其实际用户ID和有效用户ID及组ID,获取登录名
char *getlogin(void)
8.16 进程调度
1、UNIX系统历史上对进程提供的只是基于调度优先级的粗粒度的控制。调度策略和调度优先级是由内核确定的。进程可以通过调整nice值选择以更低优先级运行。nice值越小,优先级越高,使用CPU的机会越多。
2、nice值越小,优先级越高。niceh函数获取或更改它的nice值
int nice(int incr);
3、getpriority函数可以像nice函数那样用于获取进程nice值,当getpriority还可以获取一组相关进程的nice值
int getpriority(int which, id_t who,int value);
4、setpriority函数可用于为进程、进程组合属于特定用户ID的所有进程设置优先级。
int setpriority(int which,id_t who,int value);
8.17 进程时间
1、任意进程都可以调用times函数获得它自己以及终止子进程的上述值。
clock_t times(struct tms *buf)
9 进程间的关系
9.2 终端登录
1、所有使用的终端可以基于字符的终端、仿真基于字符终端的图形终端,或者运行窗口系统的图形终端。
2、BSD终端登录是由习题供管理者创建通常名为、etc/ttys的文件,其中每个终端设备都有一行,每一行说明设备名和传到getty程序的参数。
3、终端登录方式包括:BSD终端登录、Mac OS X终端登录、Linux终端登录、Solaris终端登录
9.3 网络登录
1、通过串行终端登录至系统和经由网络登录至系统俩者之间的主要区别是:网络登录时,在终端和计算之间的连接不再是点到点的。
2、为使同一个软件即能处理终端登录,又能处理网络登录,系统使用了一种称为伪终端的软件驱动程序。
3、网络登录方式:BSD网络登录、Mac OS X网络登录、Linux网路登录、Solaris网络登录
9.4 进程组
1、进程组是一个或多个进程的集合。通常,它们是在同一作业中结合起来的,同一进程组中的各进程接收来自同一终端的各种信号。函数getpgrp可以获取调用进程的进程组ID。另外函数getpgid函数返回参数为0,也返回调用进程的进程组ID
2、每个进程组有一个组长进程。组长进程的进程组ID等于其进程ID。
3、进程调用setpgid可以加入一个现有进程组或者创建一个新进程组
int setpgid(pid_t pid,pid_t pgid);
4、一个进车只能为它自己或它的自进程设置进程组ID。在它的子进调用了exec后,它就不在更改子进程的进程组ID
9.5 会话
1、会话是一个或多个进程组的集合。
2、调用setsid函数的进程不是一个进程组的组长进程,则建立一个新会话
(1)该进程变成新会话的会话首进程
(2)该进程成为一个新进程组的组长进程
(3)该进程没有控制终端
3、fork一个进程后,可以确保子进程不会是一个进程组的组长进程。
4、getsid返回会话首进程的进程组ID
9.6 终端控制
1、会话和进程组还有有一些其他特性:(1)一个会话可以有一个控制终端(2)建立与控制终端连接的会话首进程被称为控制进程(3)一个会话中的几个进程组可以被分成一个前台进程组以及一个或多个后台进程组(4)如果一个会话有一个控制终端,则它有一个前台进程组,其他进程组为后台进程组(5)无论何时键入终端的中断键,都会将中断信号发送至前台进程组的所有进程
9.7 函数tcgetpgrp、tcsetpgrp和tcgetsid
1、函数tcgetpgrp返回青苔进程ID,它与在fd上打开的终端相关联。如果进程有一个控制终端,则进程可以调用tcsetpgrp将前台进程组ID设置为pgrpid。pgrpid值应当是在同一会话中的一个进程组的ID。fd必须引用该会话的控制终端。
pid_t tcgetpgrp(int fd); int tcsetpgrp(int fd, pid_t pgrpid);
2、给出控制TTY的文件描述符,通过tcgetsid函数,应用程序就能获得会话首进程的进程组ID(等价于会话首进程的会话ID)
pid_t tcgetsid(int fd);
9.8 作业控制
1、它允许在一个终端上启动多个作业(进程组),它控制哪一个作业可以访问该终端以及哪些作业在后台运行。作业控制要求以下3种形式的支持(1)支持作业控制的shell(2)内核中的终端驱动车需必须支持作业控制(3)内核必须提供对某些作业控制信号的支持
2、键入一个影响前台做的特殊字符--挂起键(通常采用Ctrl+Z),与终端驱动程序进行交互作用。键入此字符使终端驱动程序将信号SIGTSTP发送至前台进程组中的所有进程,后台进程组作业则不受影响。实际上有3个特殊字符可使终端驱动程序产生信号,并将它们发送至前台进程组,它们是(1)中断字符(一般采用Delete或Ctrl+C)SIGINT(2)退出字符(一般采用Ctrl+\)产生SIGQUIT(3)挂起字符(一般采用Ctrl+Z)产生SIGTSTP
3、对应于SIFTTOU信号的虚线表明后台进程组进程输出是否出现在终端是可选择的。
9.9 shell执行程序
1、ps的父进程是shell。
2、因为shell不知道作业控制,所以没有将后台作业放入自己的进程组,也没有从后台作业处取走控制终端。
9.10 孤儿进程组
1、一个其父进程已终止的进程称为孤儿进程,这种进程由init进程收养。
2、孤儿进程组则定义为:该组中每个成员之间的父进程要么是该组的一个成员,要么不是该组所属会话的成员
9.11 FreeBSD实现
1、每个会话都会分配一个session结构
(1)s_count是该会话中的进程组数
(2)s_leader是指向会话进程proc结构的指针
(3)s_ttyvp是指向控制终端vnode结构的指针
(4)s_ttyp是指向控制终端tty结构的指针
(5)s_sid是会话ID
(6)t_session指向将此终端作为控制终端的session结构
(7)t_pgrp指向前台进程组的pgrp结构
(8)t_termios是包含终端窗口当前大小的winsize型结构
2、prgrp结构体包含一个特定进程组的信息
(1)pg_id是进程组ID
(2)pg_session指向此进程组所属会话的session结构
(3)pg_members是指向此进程组proc结构表的指针,该proc结构代表进程组的成员
3、proc结构包含一个进程的所有信息
(1)p_pid包含进程ID
(2)p_pptr指向父进程proc结构的指针
(3)p_pgrp指向本进程所属的进程组的pgrp结构的指针
(4)pg_members是指向此进程组proc结构表的指针,该proc结构代表进程组的成员
10 信号
10.1 引言
1、信号提供了一种处理异步事件的方法。
10.2 信号概念
1、每个信号都有一个名字。这些名字都以3个字符SIG开头。
2、在头文件<signal.h>中,信号名都被定义为正整数常量(信号编号)
3、接收信号进程和发送信号进程的所有者必须相同,或者发送信号进车的所有者必须是超级用户
4、产生信号的事件对进程而言是随机出现的,在某个信号出现时,可以告诉内核按下列三种方式之一进行处理:(1)忽略此信号,但是SIGKILL和SIGDTOP不能被忽略(2)捕捉信号,通知内核在某种信号发生是,调用一个用户函数(3)执行系统默认动作
5、此节有各信号的说明
10.3 函数Signal
1、UNIX系统信号机制最简单的接口就是signal函数
int signal(int signo,void *func);
(1)signo参数是信号名
(2)func的值可以是常量SIG_IGN(忽略signo)、常量SIG_DEL(系统默认行为)或街道此信号号后要调用的函数地址。
2、当指定函数地址是,则在信号发生时,调用该函数,我们称这种处理为不中该信号,称此函数为信号处理程序或信号捕捉函数。--》待实验捕捉函数传参问题
3、当一个进程调用fork时,其子进程继承父进程的信号处理方式。
10.4 不可靠信号
1、不可靠在这里指的是,信号可能会丢失。一个信号发生了,但进程却可能一直不知道这一点。
2、pause函数使自己休眠,直到捕捉到一个信号。
10.5 中断的系统调用
1、系统调用和函数。当捕捉到某个信号时,被中断的是内核中执行的系统调用。
10.6 可重入函数
1、在信号处理程序中保证调用安全的函数,这些函数是可重入的并称为是异步信号安全的。
2、信号处理程序中调用一个非可重入函数,则其结果是不可预知的。
10.7 SIGCLD语义
1、子进程状态改变后会产生SIGCHLD信号,父进程需要调用一个wait函数以检测发生了什么。
2、如果进程明确地将该信号的配置设置为SIG_IGN,则调用进程的子进程将不会产生僵死进程。
3、如果将SIGCLD的配置设置为捕捉,则内核立即检查是否有子进程准备好被等待,如果是这样,则调用SIGCLD处理程序。
示例10-6待分析
10.8 可靠信号术语与语义
1、当一个信号产生时,内核通常在进程表中以某种形式设置一个标志。在向进程递送一个信号。在信号产生和递送之间的时间间隔内,称信号是未决的
2、如果一个进程产生了一个阻塞的信号,而且对该信号的动作是系统默认动作或捕捉该信号,则该进程将此信号保持为未决状态。
3、大多数UNIX并不对信号排队,而是只传递这种信号一次
4、每个进程由一个信号屏蔽字,它规定了当前要阻塞递送到该进程的信号集。对于每种的信号,该信号屏蔽字中都有一位与之对应。对于某种信号,若其对应已设置,则它当前是被阻塞的。进程可以调用sigprocmask来检测和更改其当前型号屏蔽字。
10.9 函数kill和raise
1、kill函数将信号发送给进程或进程组。raise函数则允许进程向自身发送信号
int kill(pid_t pid, int signo);//pid>0 将该信号发送给进程ID 为pid的进程;pid==0 将该信号发送给与发送进程属于同一进组的所有进程; //pid<0 将该信号发送给其进程组ID等于pid绝对值。pid==-1将该信号发送给发送进程有权限向他们发送信号的所有进程。 int raise(int signo);
10.10 函数alarm和pause
1、使用alarm函数可以设置一个定时器(闹钟时间),在将来的某个时刻该定时器会超时。当定时器超时时,产生SIGALRM信号。如果忽略或不捕捉此信号,则其默认动作是终止调研该alarm函数的进程
unsigned int alarm(unsigned int seconds);
2、seconds的值是产生信号SIGALRM需要经过的始终描述。当着一时刻到达时,信号由内核产生,由于进程调度的延迟,所以进程得到控制从而能够处理该信号还需要一个时间间隔。
3、每个进程只能有一个闹钟时间。如果调用alarm时,之前已为该进程注册的闹钟时间还没有超时,则该闹钟的时间的预留值作为本次alarm函数的调用的返回值。以前注册闹钟时间则被新值代替。
4、如果想捕捉SIGALRM信号,则必须在调用alarm之前安装该信号的处理程序
5、pause函数使调用进程挂起直至捕捉到一个信号
int pause(void);
6、alarm还常用于对可能阻塞的操作设置时间上限。
10.11 信号集
1、我们需要一个能表示多个信号--信号集的数据类型,在sigprocmask类函数中使用这种数据类型,以便告诉内核不允许发生该信号集中的信号
2、5个处理信号集的函数
int sigemptyset(sigset_t *set);//初始化有set指向的信号集,清除其中所有信号
int sigfillset(sigset_t *set);//初始化由set指向的信号集,使其包括所有信号。
int sigaddset(sigset_t *set, int signo);//将一个信号添加到已有的信号集中
int sigdelset(sigset_t *set,int signo); //从信号集中删除一个信号
int sigismember(const sigset_t *set, int signo);
10.12 函数sigprocmask
1、调用sigpromask可以检测或更改,或同时进行检测和更改进程的信号屏蔽字。
int sigprocmask(int how, const sigset_t *restrict set, sigset *restrict oset);//若oset是非空指针,那么进程的当前信号屏蔽字通过oset返回。
//若set是一个非空指针,则参数how只是如何修改当前信号屏蔽字,如果set空指针,则how的值也无意义
2、在调用sigprocmask后,如果有任何未决的、不再阻塞的信号,则在sigprocmask返回前,至少将其中之一递送给该进程。
10.13 函数sigpending
1、sigpending函数返回一信号集,对于调用进程而言,其中的各信号是阻塞不能递送的,因而也一定是当前未决的
int sigpending(sigset *set);
10.14 函数sigaction
1、sigaction函数的功能是检查或修改与指定信号相关联的处理动作。
int sigaction(int signo,const struct sigaction *restrict act,struct sigaction *restrict oact); //signo是要检查或修改其具体动作的信号 //若act指针非空,则要修改其动作 //若oact指针非空,则系统经由oact指针返回该信号的上一个动作
2、siginfo结构包含了信号产生原因的有关信息。
10.15 函数sigsetjmp和siglongjmp
1、可以在信号处理程序中进行分局部转移。
int sigset_jmp(sigjmp_buf env, int savemask); void siglongjmp(sigjmp_buf env,int val);
10.16、函数sigsuspend
1、在一个原子操作中先恢复信号屏蔽字,然后使进程休眠
int sigsuspend(const sigset_t *sigmask);
2、等待一个信号处理程序设置一个全局变量
10.17 函数abort
1、abort函数的功能是使程序异常终止,将SIGABRT信号发送给调用进程
2、abort不理会进程对此信号的阻塞和忽略
3、让进程捕捉SIGABRT的意图是:在进程终止之前由其执行所需清理的操作。
4、让进程捕捉SIGABRT的意图是:在进程终止之前有其执行所需清理操作。
10.18 函数system
1、system的返回值,他是shell的终止状态,但shell的终止状态并不总是执行命令字符串进程的终止状态。
10.19 函数sleep、nanosleep和clock_nanosleep
1、函数sleep使调用进程被挂起直到满足下面俩个条件之一:(1)已经过了seconds所指的墙上时钟时间(2)调用进程捕捉到一个信号并从信号处理程序返回
2、nanosleep函数提供纳秒级精度
int nanosleep(const struct timespec *reqtp,struct timespec *remtp);
reqtp参数用秒和纳秒指定了需要休眠的时间长度。如果某个信号中断了休眠间隔,进程并没有终止,remtp参数指向的timespec结构体会被设置为未休眠完的时间长度。
3、clock_nanosleep函数对特定时钟的延迟时间来挂起调用线程
int clock_nanosleep(clockid_t clock_id, int flags, const struct timespec *reqtp,struct timespec *remtp);
10.20 函数sigqueue
1、一个信号带有一个位信息
2、使用排队信号必须做以下几个操作:(1)使用sigcation函数安装信号处理程序时指定SA_SIGINFO标志(2)在sigaction结构的sa_sigaction成员中提供信号处理程序(3)使用siggqueue函数发送信号
int sigqueue(pid_t pid, int signo, const union sigval value);
10.21 作业控制信号
1、与作业控制相关的信号:SIGCHLD 子进程已停止或终止;SIIGCONT 如果进程已停止,则使其继续运行;SIGSTOP 停止信号(不能被捕捉或忽略);SIGTSTP 交互停止信号;SIGTTIN 后台进程组成员读控制终端;SIGTTOU 后台进程组成员写控制终端。
10.22 信号名和编号
1、使用psignal函数可移植地打印与信号编号对应的字符串
void psignal(int signo, const char *msg);
2、如果在sigaction信号处理程序中有siginfo结构,可以使用psiginfo函数打印信号信息
void psiginfo(const siginfo_t *info, const char *msg);
3、只需要信号的字符描述部分,也不需要把它写到标准错误文件中,可以使用strsignal函数
char *strsignal(int signo);
4、函数将信号映射为信号名,另一个则反之
int sig2str(int signo, char *str); int str2sig(const char *str, int *signop);
第11章 线程
11.1 引言
1、一个进程中的所有线程都可以访问该进程的组成部件,如文件描述符和内存
11.2 线程概念
1、有了多个线程控制以后,在程序设计时可以把进程设计成在某一时刻能够做不止一件,每个线程处理各自独立的任务。这种方法有多种好处(1)通过为每种事件类型分配单独的处理线程,可以简化处理异步事件的代码(2)多个进程必须使用操作系统提供的复杂机制才能实现内存和文件描述符的共享(2)有些问题可以分解从而提供整个程序的吞吐量(3)交互的程序同样可以通过使用多线程来改善响应时间。
2、每个线程都包含有表示执行环境所需的信息,其中进程中标识线程ID、一组寄存器值、栈、调度优先级和策略、信号屏蔽字、errno变量以及线程私有数据。
3、一个进程的所有信息对该进程的所有线程都是共享的,包括可执行程序的代码、程序的全局内存和堆内存、栈以及文件描述符
11.3 线程标识
1、每个线程也有一个ID,但线程ID只有在它所属的进程上下文中才有意义
2、线程ID用pthread_t数据类型表示的,实现时可以用一个结构来代表pthread_t数据类型,所以可移植的操作系统实现不能把它作为整数处理。对线程ID进行比较函数
int pthread_equal(pthread_t tid1, pthread_t tid2);
3、调用pthread_self函数获取得自身的线程ID
pthread_t pthread_self(void);
11.4 线程创建
1、新增的线程可以通过pthread_create函数创建
int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void *), void *restrict arg);
新创建的线程的线程ID会被设置为tidp指向的内存单元。attrc参数用于定制各种不同线程的属性。新创建的线程从start_rtn函数的地址开始运行,该函数只有一个无类型指针参数arg。如果需要向start_rnt函数传递的参数以上,哪么需要把这些参数放到一个结构中。
2、线程创建时并不能保证哪个线程会先运行:是新创建的线程,还是调用线程。新创建的线程可以访问进程的地址空间,并且继承调用线程的浮点环境和信号屏蔽字,但是该线程的挂起信号集会被清除。
11.5 线程终止
1、进程可以通过3中方式退出,在不终止整个进程的情况下,停止它的控制流
(1)线程可以简单地从启动例程中返回,返回值是线程的退出码
(2)线程可以被同进程中的其他线程取消
(3)线程调用pthread_exit,通过rval_ptr设置线程返回码
void pthread_exit(void *rval_ptr);
2、pthread_join自动把线程至于分离状态,这样资源可以恢复,并且可以获取退出线程的退出状态
int pthread_join(pthread_t thread, void **rval_ptr);
3、线程可以通过调用pthread_cancel函数来请求取消同一进程中的其他线程。并不等待线程终止,它仅仅提出请求
int pthread_cancel(pthread_t tid);
4、线程可以安排它退出时需要调用的函数,这样的函数称为线程处理函数,一个线程可以建立多个清理处理程序。它们执行顺序与它们注册时相反
void pthread_cleanup_push(void (*rtn)(void *),void *arg);
void pthread_cleanup_pop(int execute);
线程通过从它的启动例程中返回而终止的话,它的清理处理程序就不会被调用
5、线程被分离后,不能调用pthread_join函数等待它的终止状态。调用pthread_detach函数可以分离线程
int pthread_detach(pthread_t tid);
11.6 线程同步
1、当一个线程可以修改的变量,其他线程也可以读取或者修改的时候,我们就需要对这些线程进行同步,确保他们在访问变量的储存内容时不会访问到无效值。
2、俩个或多个线程试图在同一时间修改同一变量时,也需要进行同步。对变量操作通常分解为以下3步:(1)从内存单元读入寄存器(2)在寄存器中对变量做操作(3)把新的值写回内存单元
11.6.1 互斥量
1、使用pthread的互斥接口来保护数据,确保同一时间只有一个线程访问数据。互斥量从本质上说是一把锁。
2、互斥变量是用pthread_mutex_t数据类型表示。在使用互斥变量以前,必须对它进行初始化,可以把它设置为常量PTHREAD_MUTEX_INITIALIZER(只适用于静态分配的互斥量),也可以通过调用pthread_mutex_init函数进行初始化。如果动态分配互斥量(例如,通过调用malloc函数),在释放内存前需要调用pthread_mutex_destory
int pthread_mutex_int(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
int pthread_mutex_dextory(pthread_mutex_t *mutex);
3、对互斥量进行加锁,需要调用pthread_mutex_lock.如果互斥量已经上锁,调用线程将阻塞直到互斥量被解锁。
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);//互斥量被锁住,返回EBUSY
int pthread_mutex_unlock((pthread_mutex_t *mutex);
11.6.2 避免死锁
1、如果一线程内试图对同一个互斥量加锁俩次,那么它自生就会陷入死锁
2、程序中使用一个以上的互斥量,而且俩个线程都在互相请求另一个线程所拥有的资源,就会产生死锁。多个互斥量时,总是让他们以相同顺序加锁,可以避免死锁
3、需要在满足锁需求的情况下,在代码复杂性和性能之间找到正确的平衡。
11.6.3 函数pthread_mutex_timedlock
1、pthread_mutex_timedlock互斥量原语允许绑定线程阻塞时间。pthread_mutex_timedlock函数与pthread_mutex_lock是基本等价的,但是在达到超时时间值时,pthread_mutex_timedlock不会对互斥量进行加锁,而是返回错误吗ETIMEDOUT。
int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex, const struct timespec *restrict tsptr);
11.6.4 读写锁
1、读写锁可以有3种状态:读模式下加锁状态,写模式下加锁状态,不加锁状态。一次只有一个线程可以占有写模式的读写锁,但是多个线程可以同时占有读模式的读写锁。
2、当读写锁处于读模式锁住的状态,而这时有个线程试图以写模式获取锁时,读写锁通常会阻塞随后的读模式锁请求。这样可以避免读模式锁长期占用,而等待的写模式锁请求一直得不到满足。
3、读写锁非常适合对于数据结构读的次数远大于写的情况。读写锁也叫做共享互斥锁。
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr); int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
4、读写锁的使用函数
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);//在读模式下锁定读写锁 int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);//在写模式下锁定读写锁 int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
5、获取锁时,这个俩个函数返回0。否则返回错误EBUSY
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);//在读模式下锁定读写锁 int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);//在写模式下锁定读写锁
11.6.5 带有超时的读写锁
1、避免引用程序在获取读写锁时避免先入永久阻塞状态,指定的超时时间是绝对时间,而不是相对时间
int pthread_rwlock_timedrdlock(pthread_rwlock_t *rwlock,const struct timespec *restrict tsptr);//在读模式下锁定读写锁 int pthread_tryrwlock_timedwrlock(pthread_rwlock_t *rwlock,const struct timespec *restrict tsptr);//在写模式下锁定读写锁
11.6.6 条件变量
1、条件变量与互斥量一起使用时,允许线程以无竞争的方式等待特定的条件发生。
2、条件本身是由互斥量保护的。线程在改变条件状态之前必须首先锁住互斥量
3、条件变量使用的函数
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict atr); int pthread_cond_destroy(pthread_cond_t *cond); int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex); //等待条件变量变为真 int pthread_cond_timedwait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex,const struct timespec *restrict tsptr); int pthread_cond_signal(pthread_cond_t *cond); //唤醒一个等待该条件的线程 int pthread_cond_broadcast(pthread_cond_t *cond);//唤醒所有等待该条件的所有线程
11.6.7 自旋锁
1、自旋锁不是通过休眠使进程阻塞,而是在获取锁之前一直处于忙等阻塞状态。
2、自旋锁可用于以下情况:被持有的时间短,而且线程并不希望在重新调度上花费太多的成本。
3、自旋锁常用的函数
int pthread_spin_int(phtread_spinlock_t *lock,int pshared);//pshard参数标书进程共享属性
int pthread_spin_destory(pthread_spinlock_t *lock);
int pthread_spin_lock(pthread_spinlock_t *lock);
int pthread_spin_trylock(pthread_spinlock_t *lock);
int pthread_spin_unlock(pthread_spinlock_t *lock);
11.6.8 屏障
1、屏障是用户协调多个线程并行工作时的同步机制。屏障允许每个线程等待,直到所有的合作线程都到达某一点,然后从该点继续执行。
2、对屏障进行处理的相关函数
int pthread_barrier_init(pthread_barrier_t *restrict barrier, const pthread_barrierattr_t *restrict attr,unsigned int count);//count表示必须到达屏障的线程数目
int phread_barrier_destroy(pthread_barrier_t *barrier);
int pthread_barrier_wait(pthread_barrier_t *barrier);//满足了屏障计数,所有线程都被唤醒
12 线程控制
12.2 线程限制
1、线程的限制可以通过sysconf函数进行查询
12.3 线程属性
1、pthread接口允许我们通过设置每个对象关联的不同属性来细调线程和同步对象的行为。
2、pthread_attr_t结构修改线程默认属性,并把这些属性与创建的线程联系起来。
int pthread_attr_init(pthread_attr_t *attr);//初始化attr结构包含操作系统实现支持的所有线程属性的默认值。 int pthread_attr_destroy(pthread_attr_t *attr);
3、使用pthread_detach函数让操作系统在线程退出时收回它所占用的资源
4、使用函数修改线程的detachstate线程属性,让线程一开始就处于分离状态
int pthread_attr_getdetachstate(const pthread_attr_t *restrict attr, int *detachstate);
int pthread_attr_setdetachstate(pthread_attr_t *attr,int *detachstate);
5、对线程栈属性进行管理,改变新建线程的栈位置。进程中只有一个栈,所以它的大小通常不是问题。单对于线程来说,同样大小的虚地址空间必须被所有线程栈共享。可能导致线程栈的累计大小超过了可用的虚拟地址空间。
int pthread_attr_getstack(const pthread_attr_t *restrict attr, void **restrict stackaddr, size_t *restrict stacksize);
int pthread_attr_setstack(pthread_attr_t *attr, void *stackaddr, size_t stacksize);
6、读取或设置线程属性stacksize.,改变线程栈的默认大小
int pthread_attr_getstacksize(const pthread_attr_t *restrict attr, size_t *restrict stacksize);
int pthread_attr_setstacksize(pthread_attr_t *attr,size_t stacksize);
7、线程属性guardsize控制着线程栈末尾之后用以避免栈溢出的扩张内存的大小
int pthread_attr_getguardsize(const pthread_attr_t *restrict attr, size_t *restrict guardsize);
int pthread_attr_setguardsize(pthread_attr_t *attr,size_t guardsize);
12.4 同步属性
1、线程的同步对象也有属性。
12.4.1 互斥量属性
1、互斥量属性使用pthread_mutexattr_t结构表示。互斥量属性处理函数。函数将默认的互斥量属性初始化pthread_mutexattr_t结构。包括3个属性:进程共享属性、健壮属性以及类型属性
int pthread_mutexattr_init(pthread_mutexattr_t *attr);//
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);
2、进程共享属性的查询和修改函数。设置进程共享属性,使多个进程彼此之间共享的内存数据块中分配的互斥量就可以用于这些进程的同步
int pthread_mutexattr_getpshared(const pthread_mutexattr_t *restrict attr, int *restrict pshared);
int pthread_mutexattr_getpshared(pthread_mutexattr_t *attr, int pshared);
3、互斥量健壮属性与在多个进程间共享的互斥量有关,使用健壮的互斥量改变了我们使用pthread_mutex_lock的方式
int pthread_mutexattr_getrobust(const ptread_mutexattr_t *restrict attr, int *restrict attr, int *restrict robust);
int pthread_mutexattr_setrobust(pthread_mutexattr_t *attr, int robust);
4、避免互斥量处于永久不可用状态,调用函数pthread_mutex_consistent函数,指明与该互斥量相关的状态在互斥量解锁之前是一致的
int pthread_mutex_consistent(pthread_mutex_t *mutex);
5、设置互斥量类型属性函数
int pthread_mutexattr_gettype(const pthread_mutexattr_t *restrict attr, int *restrict type);
int pthread_mutexattr_settpye(pthread_mutexattr_t *attr,int type);
12-8函数待测试
12.4.2 读写锁属性
1、读写锁支持的唯一属性是进程共享属性 ,相关处理函数
int ptrhead_rwlockattr_init(pthread_rwlockattr_t *attr);
int pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr);
int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t *restrict attr, int *restrict pshared);
int pthread_rwlockattr_setpshread(pthread_rwlockattr_t *attr, int pshared);
12.4.3 条件变量属性
1、条件变量有俩个属性:进程共享属性和时钟属。条件变量相关属性处理函数
int pthread_condattr_init(pthrad_condattr_t *attr);
int pthread_condattr_destroy(pthread_condattr_t *attr);
//设置进程共享属性,控制着条件变量是可以被单进程的多个线程使用,还是被多进程的线程使用
int pthread_condattr_getpshared(const pthread_condattr_t *restrict attr, int *restrict pshared);
int pthread_condattr_setpshared(pthread_condattr_t *attr, int pshared);
//获取、设置被用于线程的时钟ID
int pthread_condattr_getclock(const pthread_condattr_t * restrict attr, clockid_t *restrict clock_id);
int pthread_condattr_getclock(pthread_condattr_t *attr, clockid_t clock_id);
12.4.4 屏障属性
1、屏障属性只有进程共享属性,它控制着屏障是可以被多进程的线程使用,还是只能被初始化屏障的进程内的多线程使用。
int pthread_barrierattr_init(pthread_barrierattr_t *attr);
int pthread_barrierattr_destroy(pthread_barrierattr_t *attr);
int pthread_barrierattr_getpshared(const pthread_barrierattr_t *restrict attr, int *restrict pshared);
int pthread_barrierattr_setpshared(pthread_barrierattr_t *attr,int pshared);
12.5 重入
1、如果一个函数在相同的时间点可以被多个线程安全地调用,就称该函数是线程安全。
2、很多函数并不是线程安全的,因为他们返回的数据存放在静态的内存缓冲区中。通过修改接口,要求调用者自己提供缓冲区可以使函数变为线程安全。
3、一个函数对多个线程来说是可重入的,就说这个函数就是线程安全的。但并不能说明对信号处理程序来说该函数也是可重入的。
4、如果函数对异步信号处理程序的重入是安全,那么就可以说函数是异步信号安全的。
5、以线程安全的方式管理FILE对象的方法
int ftrylockfile(FILE *fp);
void flockfile(FILE *fp);
void funlockfile(FILE *fp);
6、不加锁版本的基于字符的标准I/O例程
int getchar_unlocked(void);
int getc_unlocked(FILE *fp);
int putchar_unlocked(int c);
int putc_unlocked(int c,FILE *fp);
int putc_unlocked(int c,FILE *fp);
12.6 线程特定数据
1、线程特定数据,也称为线程私有数据,是储存和查询某个特定线程相关数据的一种机制。
2、线程模型促进了进程中数据和属性的共享
3、线程私有数据的作用:(1)防止某个线程的数据与其他线程的数据相混淆(2)提供了基于进程的接口适应多线程环境的机制
4、管理线程特定数据的函数可以提高线程间的数据独立性,使得线程不太容易访问到其他线程的线程特定数据。
5、分配线程特定数据处理函数
//分配线程特定数据之前,需要创建与该数据关联的键。这个键将用于获取对线程特定数据的访问
//每个线程把这个键与不同的线程特定数据地址进行关联。
int pthread_key_create(pthread_key_t *keyp, void (*destructor) (void *));
//取消键与线程特定数据值之间的关联关系
int pthread_key_delete(pthread_key_t key);
//每个线程代用pthread_once,系统就能保证初始化例程initfn只被调用一次
pthread_once_t initflag initflag =PTHREAD_ONCE_INIT
int pthread_once(pthread_once_t *initflag, void (*initfn) (void))
//把键和线程特定数据关联函数
int *pthread_setspecific(pthread_key_t key ,const void *value)
//通过键获得线程特定数据的地址
void *pthread_getspecific(pthread_key_t key);
6、malloc函数本身并不是异步信号安全的
12.7 取消选项
1、可取消状态和可取消类型没有包含在pthread_attr_t结构中。
2、可取消状态属性函数
int pthread_setcancelstate(int state,int *oldstate);
//在程序中添加自己的取消点
void pthread_testcancel(void);
//修改取消类型
int pthread_setcancletype(int type, int *oldtype);
12.8 线程和信号
1、当某个线程修改了某个给定信号相关的处理行为后,所有线程必须共享这个处理行为的改变。
2、一个线程选择忽略摸个给定信号,另一个线程可以通过俩种方式撤销上述线程的信号选择:回复信号的默认处理行为,或者为信号设置一个新的信号处理程序
3、线程中阻止信号发送函数
int pthread_sigmask(int how, const sigset_t *restrict set, sigset_t *resrcit oset)
4、线程等待一个或多个信号出现函数,sigwait函数会原子地取消信号集的阻塞状态,知道有新的信号被递交。它可以简化信号处理,允许把异步产生的信号用同步的方式处理。
int sigwait(const sigset_t *restrict set, int *resrcit signop)
5、把信号发送给线程的处理函数
int pthread_kill(pthread_t thread, int signo
12.9 线程和fork
1、子进程通过集成整个地址空间的副本,还从父进程哪儿集成了每个互斥量、读写锁和条件变量的状态。
int pthread_atfork(void (*prepare)(void), void (*parent) void,void (*child)(void));
12.10 线程和I/O
1、进程中的所有线程共享相同的文件描述符
2、pread、pwrite使偏移量的设定和数据的读取成为一个原子操作
第 13章
13.1 引言
1、守护进程是生存期长的一种进程。在系统引导装入时启动,仅在系统关闭时才终止。
13.2 守护进程的特征
1、父进程ID为0的各进程通常是内核进程,它们作为系统引导装入过程的一部分而启动。存在于系统的整个生命期中。
2、在linux中的内核守护进程:
(1)kswapd守护进程也称为 内存幻夜守护进程
(2)flush守护进程在可用内存达到设置的最小阈值时将脏页面冲洗至磁盘
(3)sync_supers守护进程定期将文件系统元数据冲洗至磁盘
(4)jbd守护进程帮助实现了ext4文件系统中的日志功能
3、rpcbind守护进程提供将远程调用(Remote Procedure Call,RPC)程序号映射为网络端口号的服务。
13.3 编写规则
1、编写守护进程需遵循一些基本规则:
- 调用umask将文件模式创建屏蔽字设置为一个已知值(通常是0)
- 调用fork,然后使父进程exit
- 调用setsid创建一个新会话,使调用进程:(a)成为新会话的首进程(b)成为一个新进程组的组长进程(c)没有控制终端
- 将当前工作目录更改为根目录
- 关闭不需要的文件描述符
- 某些守护进程打开/dev/null使其具有文件描述符0、1和2
13.4 出错记录
1、syslog设施额的详细组织结构
2、3种产生日志消息的方法:(1)内核例程可以调用log函数(2)大多数用户进程(守护进程)调用syslog(3)函数产生日志消息(3)无论一个用户进程是在此主机上,还是通过TCP/IP网络连接到此主机的其他主机上,都可将日志消息发向UDP端口514
3、syslog函数
void openlog(const char *ident, int option, int facility);//可选选择调用,如果不调用,则在第一次调用syslog时,自动调用openlog。ident参数一遍是程序的名称。
void syslog(int priority, const char *format,....);
void closelog(void); //可选择,因为它只是关闭层被用于与syslogd守护进程通信的描述符。
int setlogmask(int maskpri);
13.5 单实力守护进程
1、文件和记录锁机制为一种方法提供了基层,该方法保证一个守护进程只有一个副本在运行。
13.6 守护进程的惯例
1、在UNIX系统中,守护进程遵循下列通用惯例:(1)若守护进程使用锁文件,那么该文件通常储存在/var/run目录中(2)若守护进程支持配置选项,那么配置文件通常存放在/etc目录中(3)守护进程可用命令启动,但通常他们是由系统初始化脚本之一(etc/rc*或/etc/init.d/*)启动的(4)若一个守护进程有一个配置文件,那么当该守护进程启动时会读该文件,但在此之后一般不会在查看它
13.7 客户进程-服务器进程模型
1、守护进程通常用作服务器进程
2、对所有被执行程序不需要的文件描述符设置执行时关闭标志。
14 高级I/O
14.2 非阻塞I/O
1、低速系统调用是可能会使进程永远阻塞的一类系统调用:(1)如果某些文件类型(如读管道、终端设备和网络设备)的数据并不存在,读操作可能会使调用者永远阻塞(2)如果数据不能被相同的文件类型立即接受(如管道中无空间、网络流控制),写操作可能会使调用者永远阻塞(3)在某种条件发生之前打开某些文件类型可能会发生阻塞(4)对已经加上强制性记录锁的文件进行读写(5)某些ioctl操作(6)某些进程间通行函数
2、非阻塞I/O使我们可以发出open、read和write这样的I/O操作,并使这些操作不会永远阻塞。
3、对于一个给定的描述符,有俩种为其指定非阻塞I/O的方法(1)如果调用open获得描述符,则可指定O_NONBLOCK标志(2)对于已经打开的一个描述符,则可调用fctnl,由该函数打开O_NONBLOCK文件状态标志
14.3 记录锁
1、记录锁的功能是:当第一进程正在读或修改文件的某个部分时,使用记录锁可以阻止其他进程修改同一文件区。
2、fcntl记录锁
int fcntl(int fd, int cmd,.../* struct flock *flockptr *f);
cmd是F_GETLK(判断有flockptr所描述的锁是否会被另外一把锁排斥)、F_SETLK(设置由flockptr所描述的锁)或F_SETLKW(是F_SETLK的阻塞版本),第三个参数是一个指向flock结构的指针。
3、加读锁时,该描述符必须是读打开。加写锁时,该描述符必须是写打开
4、关于记录锁的自动继承和释放的规则(1)锁与进程和文件俩者相关联(2)由fork产生的子进程不继承父进程所设置的锁(3)在执行exec后,新程序可以继承原执行程序的锁
5、在fctnl使用当前偏移量(SEEK_CUR)或文件尾端(SEEK_END)。当前偏移量和文件尾端可能会不断变化
6、所有函数都以一致的方法处理记录锁,则称使用这些函数访问数据库的进程集合为合作进程。如果这些函数是唯一地用来访问数据库的函数,他们使用建议性锁是可行的。
7、强制性锁会让内核检查每一个open、read和write,验证调用进程是否违背了正在访问的文件上的某一把锁。强制性锁有时也称为强迫方式锁
8、对一个特定文件打开其设置组ID位、关闭其组执行位便开启了对该文件的强制性锁机制。
14.4 I/O多路转接
1、使用I/O多路转接技术,需要先构造一张我们感兴趣的描述符(通常不止一个)的列表,然后调用一个函数,直到这些描述符中中的一个已准备好I/O时,该函数才返回。
2、poll、pselect和select这3个函数使我们能够执行I/O多路转接。
14.4.1 函数select和pselect
1、select函数使用
int select(int maxfdp1, fd_set *restrict readfds, fd_set *restrict writefds, fd_set *restrict exceptfds, struct timeval *restrict tvptr);
tvptr指定愿意等待的时间长度,单位为秒和我微秒。有如下3中情况:(1)tvptr==NULL,永远等待。当所指定的描述符中的一个已经准备好或捕捉到一个信号则返回。如果捕抓到一个信号,则select返回-1,errno设置EINTR(2)tvptr->tv_sec==0&&tvptr->tv_usec==0 根本不等待。测试所有指定的描述符并立即返回(3)tvptr->tv_sec!=0||tv[tr->tv_usec!=0 等待指定的描述和微秒数。
中间3个参数readfds、writefds和exceptfds是指向描述符集的指针。3个描述符集说明了我们关心的可读、可写或处于异常条件的描述符集合。
2、fd_set数据类型处理函数
int FD_ISSET(int fd, fd_set *fdset);//测试描述符集中的一个指定位是否已打开 void FD_CLR(int fd, fd_set *fdset);//可以清除一位 void FD_SET(int fd, fd_set *fdset);//开启描述符集中的一位 void FD_ZERO(fd_set *fdset);//将变量所有位设置为0
3、select有3个可能的返回值(1)返回值-1表示出错(2)返回值0表示没有描述符准备好(3)一个正返回值说明了已经准备好的描述符数。
4、select的变体,称为pselect
int pselect(int maxfdp1, fd_set *restrict readfds, fd_set *restrict writefds, fd_sett *restrict exceptfds, const struct timespec *restrict tsptr, cosnt sigset_t *restrict sigmask);
14.4.2 函数poll
1、poll函数可用于任何类型的文件描述符
int poll(struct pollfd fdarray[], nfds_t nfds, int timeout);
构造一个pollfd结构的数组,每个数组元素指定一个描述符编号以及我们对该描述符感兴趣的条件。
14.5 异步I/O
1、select和poll可以实现异步形式的通知。关于描述符的状态,系统并不主动告诉我们任何信息,需哦啊进行查询。
2、每个异步操作有3处可能产生错误的地方:在操作提交部分;在操作本生结果;在用于决定异步操作状态的函数中
14.5.3 POSIX异步I/O
1、POSXI异步I/O接口为不同类型的文件进行异步I/O提供了一套一致的方法。
2、这些异步I/O接口使用AIO控制块描述I/O操作。aiocb结构定义了AIO控制块。
3、在I/O事件完成后,如何通知应用程序。这个字段通过sigevent结构来描述
14.6 函数readv和writev
1、用于在一次函数调用中读、写多个非连续缓冲区。
sisize_t readv(int fd, const struct iovec *iov, int iovcnt); ssize_t writev(int fd, const struct iovec *iov, int iovcnt);
14.7 函数readn和writen
1、分别读、写指定的N字节数据,并处理返回值可能小于要求值的情况。
ssize_t readn(int fd, void *bug, size_t nbytes);
ssize_t writen(int fd, void *buf, size_t nbytes);
14.8 储存映射I/O
1、储存映射I/O能将一个磁盘文件映射到存储空间中的一个缓冲区上,于是,当从缓冲区取数据时,就相当于读文件中的相应字节。
2、mmap函数告诉内核将一个给定的文件映射到一个储存区域中
void *mmap(void *addr, size_t len, int prot, int flag, int fd, off_t off);
addr参数用于指定映射存储区的起始地址。
fd参数是指定要被映射文件的描述符。
prot参数指定了映射存储区的保护要求
3、对指定映射储存区的保护要求不能超过文件open模式访问权限
4、更改现有映射的权限函数
int mprotect(void *addr, size_t len, int prot);
5、将页冲洗到被映射的文件中
int msync(void *addr, size_t len, int flags);
6、当进程终止时,会自动解除存储映射区的映射,或者直接调用munmap函数也可以解除映射区。
int munmap(void *addr, size_t len);
第15章 进程间通信
15.1 引言
1、目前支持的IPC方法:半双工管道、FIFO、全双工管道、命名全双工管道、XSI消息队列、XSI信号量、XSI共享存储、消息队列、信号量、共享存储、套接子、STREAMS.
15.2 管道
1、所有UNIX系统都提供此种通信机制。管道有以下俩种局限性(1)他们是半双工的(即数据只能在一个方向上流动)(2)管道只能在具有公共祖先的俩个进程之间使用。
2、通过调用pipe函数创建管道
int fd[2];
int pipe(fd);
//fd[0]为读打开,fd[1]为写打开
3、通常进程会调用pipe,接着调用fork,从而创建父进程到子进程的IPC通道,反之亦然
15.3 函数popen和pclose
1、popen和pclose函数实现的操作是:创建一个管道,fork一个子进程,关闭未使用的管道段,执行一个shell运行命令,然后等待命令终止
FILE *popen(const char *cmdstring, const char *type);//函数先执行fork,然后调用exec执行cmdstring,并且返回一个标准I/O文件指针。 int pclose(FILE *fp);
15.4 协同进程
1、当一个过滤程序既产生某个过滤程序的输入,又读取该过滤程序的输入时,它就变成了协同进程
15.5 FIFO
1、FIFO有时被称为命名管道,它是一种文件类型。不相关的进程也能交换数据。
int mkfifo(const char *path, mode_t mode);
int mkfifoat(int fd, const char *path, mode_t mode);
2、FIFO的俩种用途:(1)shell命令使用FIFO将数据从一条管道传送到另一条时,无需创建中间临时文件(2)客户进程-服务器进程应用程序中,FIFO用作汇聚点,在客户进程和服务进程二者之间传递数据。
3、FIFO是有名字的,因此它可用于非线性连接
15.6 XSI IPC
有3种称为XSI IPC的IPC:消息队列、信号量以及共享储存器。
15.6.1 标识符和键
1、每个内核中的IPC结构(消息队列、信号量或共享存储段)都用一个非负整数的标识加以引用。
2、使客户进程和服务进程在同一IPC结构上汇聚方法:(1)服务器进程可以指定键IPC_PRIVATE创建一个新IPC结构(2)可以在一个公用头文件中定义一个客户进程和服务进程都认可的键(3)客户进程和服务器进程认同一个路径名和项目ID,接着调用函数ftok将这俩个值变换为一个键。
key_t ftok(const char *path, int id);
15.6.2 权限结构
1、每个IPC结构关联一个ipc_perm结构。在创建IPC结构时,对所有字段都赋初值,修改这些值,调用进程必须是IPC结构的创建者或超级用户。
15.6.3 结构限制
1、所有3种形式的XSI IPC都有内置限制。大多数限制可以通过重新配置内核来改变
15.6.4 优点和缺点
1、IPC结构是在系统范围内起作用的,没有引用计数。
15.7 消息队列
1、消息队列是消息的链接表,存储在内核中,由消息队列表示符标识。
2、msgget用于创建一个新队列或打开一个现有队列。msgsnd将新消息添加到队列尾端。每个消息包含一个正的长整型类型的字段、一个非负的长度以及实际数据字节数(对应于长度),所有这些都将在消息添加到队列时,传送给msgsnd。msgrcv用于从队列中取消息。
3、我们并不一定要以先进先出次序取消息,也可以按消息类型字段取消息。
4、队列处理常用函数
int msgget(key_t key, int flag); //创建一个新队列或引用一个现有队列 int msgctl(int msqid, int cmd, struct msqid_ds *buf); //对队列执行多种操作,依据cmd参数指定对msqid指定的队列要执行的命令。 int msgsnd(int msqid, const void *ptr, size_t nbytes, int flag);//将数据放到消息队列中。 ssize_t msgrcv(int msqid, void *ptr, size_t nbytes, long type, int flag);
15.8 信号量
1、它是一个计数器,用于为多个进程提供对共享数据对象的访问。
2、为了获得共享资源,进程需要执行下列操作:(1)测试控制该资源的信号量(2)若此信号量的值为正,则进程可以使用该资源,在这种情况下,进程则会将信号量值减1,表示它使用了一个资源单位(3)否则,若此信号量的值为0,则进程进入休眠状态,直至信号量大于0。
3、为了正确地实现信号量,信号量值的测试及减1操作应当是原子操作。
4、常用的信号量形式被称为二元信号量。
5、内核为每个信号量集合维护着一个semid_ds结构。
6、信号量ID处理常用函数
int semget(key_t key, int nsems, int flag);//获取一个信号量ID int semctl(int semid, int semnum, int cmd, .../* union semun arg */);//多种信号量操作 int semop(int semid, struct sembug semoparray[], size_t nops);//自动执行信号量集合上的操作数组,具有原子性,它或者执行数组中的所有操作,或者一个也不做。
7、使用记录锁比使用互斥量更多的原因:(1)在多个进程间共享的内存中使用互斥量来恢复一个终止的进程更难(2)进程共享的互斥量属性还没有得到普遍支持
15.9 共享存储
1、共享存储允许两个或多个进程共享一个给定的存储区域。因为数据不需要在客户进程和服务进程之间复制,所以这是最快的一种IPC。
2、若服务进程正在将数据放入共享储存区,则在它做完这一种操作之前,客户进程不应当去取这些数据。通常信号量用于同步共享储存访问。
3、内核为每一个共享存储段维护着一个结构。
4、共享存储常用处理函数
int shmget(key_t key, size_t size, int flag);//它获得一个共享存储标识符。 int shmctl(int shmid, int cmd, struct shmid_ds *buf); //对共享存储段执行多种操作。 void *shmat(int shmid, const void *addr, int flag);//将其连接到它的地址空间中。 int shmdt(const void *addr);//对共享存储段的操作已经结束时,则调用shmdt与该段分离。
5、共享存储段紧靠在栈之下。
15.10 POSIX信号量
1、POSIX信号量接口意在解决XSI信号量接口的缺陷:(1)相比于XSI接口,POSIX信号量接口考虑到了更高性能的实现(2)POSIX信号量接口使用更简单:没有信号量集,在熟悉的文件系统操作后一些接口被模式化了(3)POSIX信号量在删除时表现更完美
2、POSIX信号量有俩种形式:命名的和未命名的。差异在于创建和销毁的形式上,但其他工作一样。
3、POSIX信号量常用处理函数
sem_t *sem_open(const char *name, int oflag, .../*mode_t mode, unsigned int value*/);//创建一个新的命名信号量或者使用一个现有信号量。
int sem_close(sem_t *sem);//释放任何信号量相关的资源
int sem_unlink(const char *name);//销毁一个命名信号量。
//实现信号量减1操作
int sem_trywait(sem_t *sem);
int sem_wait(sem_t *sem);
//阻塞一段确定的时间
int sem_timedwait(sem_t *restrict sem, const struct timespec *restrict tsptr);
int sem_post(sem_t *sem);//是信号量值增1
int sem_init(sem_t *sem, int pshared, usigned int value); //创建一个未命名的信号量
int sem_destroy(sem_t *sem); //丢弃已经使用完成的未命名信号量
int sem_getvalue(sem_t *restrict sem, int *restrict valp); //检索信号量值
15.11 客户进程-服务器进程属性
1、使用FIFO、消息队列、信号量以及共享存储,使服务器进程可以标识客户进程。
第16章 网络IPC:套接字
16.1 引言
1、套接字接口可以采用许多不同的网络协议进行通信。
16.2 套接字描述符
1、调节字是通信端点的抽象。
2、创建一个套接字函数
int socket(int domain, int type, int protocol);
2.1 参数domain确定通信的特性,包括地址格式。意指地址族。
2.2 参数type确定套接字的类型,进一步确定通信特征。在实现中可以自由增加其他类型的支持。
2.3 参数protocol通常是0,表示为给特定的域和套接字类型选择默认协议。在AF_INET通信域中,SOCK_STREAM的默认协议是传输控制协议(TCP),套接字类型SOCK_DGRAM的默认协议是UDP.
3、对于数据报(SOCK_DGRAM)接口,俩个对等进程之间的通信时不需要逻辑连接。只需要向对等进程所使用的套接字送出一个报文。
4、字节流(SOCK_STREAM)要求在交换数据之前,在本地套接字和通信的对等进程的套接字之间建立一个逻辑连接。
5、SOCK_SEQPACKET套接字得到的是基于报文的服务而不是字节流服务。
6、SOCK_RAW套接字提供一个数据报接口,用于直接访问下面的网络层。
7、套接字描述符本质上是一个文件描述符,但不是所有参数为文件描述符的函数都可以接受套接字描述符。
8、套接字通信是双向的。可以采用shutdown函数禁止一个套接字的I/O。
int shutdown(int sockfd, int how);
16.3 寻址
1、进程标识由俩部分组成:(1)计算机的网络地址,它可以帮助标识网络上我们想与之通信的计算机(2)计算机上用端口表示的服务,它可以帮助识别特定的进程。
16.3.1 字节序
1、字节序是一个处理器架构特性,用于指示像整数这样的大数据类型内部的字节如何排序。
2、大端字节序:最大字节地址出现最低有效字节(地址从左往右增大,有效字节从左往右排列);小端字节则相反。例如:地址为0001、0002、0003、0004,有效字节为0x4321,最大字节地址为0004,最低有效字节为1,0004中存放1,表示大端字节序。
3、网络协议指定了字节序,因此异构计算机系统能够交换协议信息而不被字节序混淆。
4、对于TCP/IP应用程序,有4个用来在处理器字节序和网络字节序之间转换的函数
uint32_t htonl(uint32_t hostint32);
uint16_t htons(uint16_t hostint16);
uint32_t ntohl(uint32_t netint32);
uint16_t ntohs(uint16_t netint16);
h:主机字节序;n:网络字节序;l:长整数;s:短整数
16.3.2 地址格式
1、为使不同格式地址能够传入到套解字函数,地址会被强制转换成一个通用的地址结构sockaddr;
2、网络字节序和文本字符串互相转换格式函数
const char *inet_ntop(int domain, const void *restrict addr, char *restrict str, socklen_t size);//将网络字节序的二进制转换成文本字符串格式。 int inet_pton(int domain, const char *restrict str, void *restrict addr);//将文本字符串格式转换成网络字节序的二进制地址。
16.6 套接字选项
1、套接字机制可以获取或设置一下3种选项:(1)通用选项,工作在所有套接字类型上(2)在套接字层次管理的选项(3)特定于某协议的选项,每个协议独有的
2、设置套接字选项函数
int setsockopt(int sockfd, int level, int option, const void *val, socklen_t len);
3、查看套接字选项的当前值
int getsockopt(int sockfd, int level, int option, void *restrict val, socklen_t *restrict lenp);
16.7 带外数据
1、带外数据是一些通信协议所支持的可选功能,与普通数据相比,它允许更高优先级的数据传输。
2、判断是否已经到达紧急标记函数
int sockatmark(int sockfd);
16.8 非阻塞和异步I/O
1、启用异步I/O是一个两步骤的过程:(1)建立套接字所有权,这样信号可以被传送到合适的进程(2)通知套接字当I/O操作不会阻塞是发信号
第17章 高级进程间通信
17.2 UNIX域套接字
1、UNIX域套接字用于在同一台计算机上运行的进程之间的通信,它仅仅复制数据,并不执行协议处理,不需要添加或删除网络报头,无需计算校验和,不要产生顺序号,无需发送确认报文。
2、UNIX域套接字提供流和数据报的俩种接口。UNIX域数据报服务是可靠的,既不会丢失报文也不会传递出错。它就像是套接字和管道的混合。
3、创建一对无命名的、相互连接的UNIX域套接字函数
int socketpair(int demain,int type, int protocol, int sockfd[2]);
虽然接口足够通用,允许socketpair用于其他域,但一般来说操作系统仅对UNIX域提供支持。
17-4中的strtol待确认
4、运用套接字函数,可以命名UNIX域套接字。
17.3 唯一连接
1、服务器进程可以使用标准bind、listen和accept函数,为客户进程安排一个唯一UNIX域连接。客户进程使用connect与服务器进程联系。
2、在运行于同一台计算机上的俩个无关进程之间创建唯一连接的函数
int serv_listen(const char *name);//声明它要在一个众所周知的名字(文件系统中的某个路径名)上监听客户进程的连接请求。函数返回值用于接收客户进程连接请求的服务器UNIX域套接字。 int serv_accept(int listenfd, uid_t *uidptr);//等待客户进程连接请求的到达。 int cli_conn(const char *name);//连接至服务器进程。
17.4 传送文件描述符
1、传送文件描述符是指一个进程向另一个进程传送一个打开文件描述符时,可以让发送进程和接收进程共享同一文件表项。
2、用以发送和接收文件描述符的3个函数
int send_fd(int fd, int fd_to_send);
int send_err(int fd, int status, const char *errmsg);
//以上俩个函数是将一个描述符传送给另一个进程
int recv_fd(int fd, ssize_t (*userfunc)(int, const void *, size_t));//客户进程接收描述符函数
3、用于访问控制数据的宏
unsigned char *CMSG_DATA(struct cmsghdr *cp);
struct cmsghdr *CMSG_FIRSTHDR(struct msghdr *mp);
struct cmsghdr *CMSG_NXTHDR(struct msghdr *mp, struct cmsghdr *cp);
unsigned int CMSG_LEN(unsigned int nbytes);
17.5 open服务器进程第1版
end
待梳理
1、在P86中(statbuf.st_mode & ~S_IXGRP) | S_ISGID)<0逻辑待数理
1、execlp函数执行逻辑,为什么不能执行 ls -al的逻辑
待进一步梳理知识点:
1、select和poll函数使用实例
2、popen和pclose函数的具体使用场景
3、过滤程序概念
4、关于getaddrinfo函数使用待确定
5、set_cloexec函数的作用
6、daemonzie函数用法待确认
7、16-6和16-17无法运行起来
8、16-9无法运行起来
9、出现getaddrinfo error: Servname not supported for ai_socktype
end