Unix环境高级编程复习总结

Unix环境高级编程

第一章 Unix基础知识

1.操作系统定义
  • 严格意义,OS是一种方便用户管理和控制计算机硬件资源的软件(内核)

  • 广义,OS包括内核和其他软件(shell,库函数)

2.Unix体系结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Y0bmfykY-1678841859519)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230220105103433.png)]

  • 内核 是UNIX操作系统的核⼼,它实现存储管理、⽂件管理、设备管理、进程管理等功能并为外壳层提供系统调⽤。
  • 系统调用 内核的接口称为系统调用,是操作系统的一部分,系统调用的处理运行在内核空间中
  • shell 命令解释器,读取用户输入(来自终端或shell脚本文件)然后执行命令,一个特殊的应用程序,为其他应用程序提供接口
  • 库函数:应用程序的一部分,可以运行在用户空间中。可能调用系统调用也可能不调用,但系统调用不能被替换,库函数可以,库函数功能更复杂
  • 应用程序
3.登录
  • 登录名 用户键入登录名,再键入口令,系统在其口令文件(/etc/passwd文件中查看登录名)
4.文件和目录
  • 文件系统:目录和文件的一种层次结构,根目录是起点(/)
  • 目录:包含目录项的文件,每个目录项包含一个文件名及说明该文件属性(文件大小、所有者、文件权限、最后修改时间等)的信息,stat和fstat函数返回包含所有文件属性的信息结构
  • 文件名:目录中个各个名字,不能有斜线(/)和空字符。斜线分隔构成路径的文件名,空字符种植路径名
  • 创建新目录时自动创建两个文件名:. ,点指向当前目录,点点指向父目录,在最高层次根目录中,点点=点
  • 路径名:由斜线分隔得一个或多个文件名组成的序列,以斜线开后称为绝对路径名,否则相对路径名(指向相对于当前目录的文件),文件系统根(/)是特殊的绝对路径名,不包含文件名
  • 工作目录:每个进程有一个工作目录(也称当前工作目录),所有相对路径名都从工作目录开始解释,可用chdir更改
  • 起始目录:用户登录时,工作目录设置为起始目录(home directory:/home,),该目录从口令文件的相应用户的登录项获得
5.输入和输出
  • 文件描述符:内核打开一个文件或创建一个新文件时返回一个fd,用以标识一个特定进程正在访问的文件。非负整数
  • 标准输出,标准输入,标准错误输出,每运行一个程序,系统为其维护这三个fd。不重定向就默认链接终端,也可重定向到某个文件
    • command < file 将标准输入重定向到file文件
    • command > file 将标准输出重定向到file文件
    • command 2 >file 将标准错误输出重定向到file文件(口诀尖尖对谁数据流向谁)
    • eg:ls> file.list 执行ls命令,将stdout重定向到该文件;./a.out < file1 >file2 将标准输入定向到file,将标准输出重定向到file2
  • 不带缓冲的IO:open,read,write,lseek,close,使用fd
  • 标准IO:为不带缓冲的IO函数提供带缓冲的接口,使用标准IO函数无需担心选取最佳缓冲区的大小,简化了对输入行的处理。例如fgets读取一个完整行,read读取指定字节数。标准IO eg:printf
6.程序和进程
  • 程序:可执行文件
  • 进程:程序的执行实例,进程ID每个进程唯一数字标识符
  • 进程控制:fork,exec,waitpid
  • 线程:一个进程的所有线程共享进程资源和空间,fd,栈等,由线程ID识别
7.出错处理

通常函数出错会返回负值,整型变量errno被设置为具有特定信息的值。errno=EACCES表示产生了权限问题,每个线程有局部errno以免干扰其他进程

errno规则:没有出错,起值不会被例程清楚,仅当函数出错才检验其值;任何函数不会将errno设为0.

strerror将errno映射为出错消息字符串,perror输出包含出错消息的字符串

8.用户标识
  • 用户ID:在口令文件的登录项中,每个用户唯一,用户不能更改;为0是超级用户
  • 组ID;也在口令文件登录项中,允许同组共享资源
  • 附属组ID
9.信号

用于通知进程发生事件,三种处理方式:忽略,系统默认方式处理(终止进程),提供一个函数

终端键盘产生信号的两种方式:

  • 中断键(ctrl+C或delete)和退出键(Ctrl+\)
  • kill函数,调用此函数向另一个进程发送信号,但我们必须是那个进程的所有者或者root
10.时间值
  • 日历时间:自1970年1.1日零点开始的秒数,time_t数据结构保存
  • 进程时间:cpu时间
    • 时钟时间:进程运行的总时间
    • 用户CPU时间:执行内核程序
    • 系统CPU时间:执行用户指令

第三章 文件IO

1、文件描述符 0~openMax-1

代表一个文件,内核打开(open)一个文件或创建(creat )一个新文件时返回一个fd,用以标识一个特定进程正在访问的文件,可作为参数传递给read()和write。非负整数,

​ fd=0与进程的标准输入关联,fd=1 关联进程的标准输出 ,fd=2关联进程的标准错误输出,即

stdin=0,stdout=1,stderr=2;

2、open和opennat
#include <fcntl.h>
int open(const char *pathname, int oflag,/* , mode_t mode */ );
//oflag:O_RDONLY(只读打开)O_WRONLY(只写)O_RDWR(读写打开)
//Returns: file descriptor if OK, -1 on error
  • open作用 以oflag的方式打开路径为pathname的文件
  • opneat作用:若path是绝对路径则两函数同;若path是相对路径,则fd指出相对路径名在文件系统的开始地址;path相对路径,fd=AT_FDCWD,路径名在当前工作目录中获取
  • 两个函数返回的是最小未用描述符fd
oflag作用
O_APPEND每次写追加到文件尾端
O_CREAT若文件不存在则创建,open需说明mode参数(指定多个文件权限位)
O_EXCL若同时指定了O_CREAT而文件存在,则出错,以此检验文件是否存在,如果不存在则创建文件,使测试和创建原子操作
O_TRUNC如果文件存在,且只写或读写成功打开,则将长度截断为0
3. creat创建新文件
#include <fcntl.h>
int creat(const char *pathname, mode_t mode);
// Returns: file descriptor opened for writeonly if OK, -1 on erro
  • creat=open(pathname, O_WRONLY| O_CREAT | O_TRUNC, mode)
  • creat只能以只写方式打开文件。如果要创建一个临时文件,并且先写然后又读,则必须creat,close,再open,而open可读写打开,open(pathname, O_RDWD| O_CREAT | O_TRUNC, mode)
4. close 关闭文件
◼ #include <unistd.h>
 int close(int filedes);// Returns: 0 if OK, -1 on error
  • 当进程终止时,内核自动关闭进程的所有打开文件,可以不显示调用close关闭
5. lseek 为打开文件设置偏移量
  • 文件偏移量:每个打开文件都有一个current file offset,从文件开始的字节数,read和write都从当前文件偏移量开始,并使其增加所读写的字节数
  • open一个文件时除非指定O_APPEND,否则偏移量为0
  • lseek只修改文件文件表项中的当前文件偏移量,不进行IO操作
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
// Returns: new file offset if OK, -1 on error

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-67gdvtqh-1678841859521)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230220161637354.png)]

注意:

  • 管道、FIFO、网络套接字无法设置偏移量,lseek返回-1且errno=ESPIPE
  • 文件偏移量可能为负数,所以测试lseek返回值是否等于-1已检测是否成功
  • 文件偏移量可以大于文件长度,下次写会加长文件并形成空洞,空洞不占磁盘
6. read(): 从fd读nbytes到缓冲区
#include <unistd.h>
ssize_t read( int filedes, void *buff, size_t nbytes );
//Returns: #bytes read, 0 if EOF, -1 on error ,bytes read may be < nbyte
  • 实际读<要求读的几种情况
    • 普通文件:30B,要求读100B,第一次读read返回30,再read返回0(文件尾端)、
    • 终端:一次最多一行
    • 网络:buffer机制
    • 管道或FIFO,若管道包含的字节数少于所需数量,则read只返回实际读的字节数
    • 磁带L自闭最多一个记录
7. write() 将缓冲区内容写入fd指向的文件
#include <unistd.h>
ssize_t write(int filedes, const void *buff, size_t nbytes);
//Returns: #bytes written if OK, -1 on error
//File offset incremented by #bytes written
  • write出错要么磁盘写满,或者超过给定进程的文件长度限制
8.IO效率
9.文件共享
  • 每个进程在进程表中有一个记录项,该项包含一个打开文件描述符表(包含文件描述符标志如图,和指向文件表项的指针)
  • 内核为所有文件维持一个文件表(文件状态,当前文件偏移量,指向v节点的指针)
  • 每个打开文件有一个v-node结构(文件类型,文件操作的函数指针,i节点)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1aRpC7bF-1678841859522)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230220163825887.png)]

一个从fd=0即stdin打开fd=1stdout打开

以下是两个独立进程各自打开同一文件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KZyuheYN-1678841859522)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230220164003379.png)]

  • 若open时设置O_APPEND,会被设置到文件表项(file table entry)的文件状态标志中,对打开文件的所有进程的所有fd都有效。每次追加写,文件表项的当前文件偏移量会设置为i节点表项中的文件长度
  • lseek定位到文件当前尾端,则文件表项中的当前文件偏移量会设置为i节点的当前文件长度(与O_append不同,append设置后每次都是从尾端,但是lseek每次写都需要设置偏移量才能写到尾端)
  • lseek只修改第二列表(文件表项)中的文件偏移量
  • 文件描述符标志(fd flags)只作用于一个进程的一个描述符,文件状态标志作用于指向该文件表项的任何进程的所有描述符(所以设置O_APPEND文件状态标志后所有写都是从尾端),而lseek只用fd flags
10.原子操作
  • 定义:是指由多步组成的操作,要么执行完所有操作,要么不执行,不能只执行所有步骤的自己,不可中断。

  • 没有O_APPEND时就会产生数据覆盖的问题,两个进程对一个文件用lseek追加写会产生覆盖的问题p62因此用文件状态标志追加写,原子操作

  • 函数pread和pwrite

    • pread=lseek+read,从指定位置读,但是无法中断
    • pwrite=lseek+write 从指定的位置写
11.函数dup和dup2 复制现有fd
#include <unistd.h>
 int dup ( int filedes );
 int dup2 ( int filedes, int filedes2 );
//Return: new file descriptor if OK, -1 on error
//Duplicate filedes and return as new file descriptor, 
//dup: 当前可用最小fd dup2: 可以指定新描述符的值,若已打开则先关闭,若相等则返回不关闭

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sKWaSWkj-1678841859523)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230220171044528.png)]

返回的新文件描述符与fd共享一个文件表项(文件状态标志读写追加等)

12.sync,fsync,fdatasync来保证磁盘上实际文件与缓冲区内容一致性
#include <unistd.h>
 int fsync(int fd);
 int fdatasync(int fd);
 void sync(void);//Returns: 0 if OK, −1 on erro
函数
sync只将所有修改过的块缓冲区排入写队列,不等待实际写磁盘操作结束
fsync只对fd文件起作用,等待磁盘操作结束才返回
fdatasync不仅影响数据部分,还会同步更新文件的属性
13.函数fcntl——可以改变已打开文件的属性
#include <fcntl.h>
int fcntl ( int filedes, int cmd,/* int arg */ );
//Returns: depends on cmd if OK, -1 on error

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M6seuxfV-1678841859523)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230220172939372.png)]

shell命令 ./a.out 5 5<>temp.foo 在文件描述符5上打开文件temp.foo以供读写

./a.out 2 2>>temp.foo 在文件描述符2上以追加模式打开文件temp.foo,就是向标准错误输出追加到temp.foo

  • 修改文件描述符或者文件状态标志时必须先获得(cmd=F_GETFL)现在的标志值,然后按照期望修改,最后设置(F_SETFL)新标志值,不能直接设置会关闭以前设置的标志位。
  • val &= ~flags;关闭标志位val |= flags; /* turn on flags *打开标志位
14.函数ioctl
#include <sys/ioctl.h> /* 4.3+ BSD */
int ioctl(int filedes, int request,);
// Returns: -1 on error, something else if OK
//not POSIX.1, but SVR4 & 4.3+BSD use it for many device operations

以上IO函数不能标识的IO操作都用ioctl表示,系统为不同种类的设备提供通用的ioctl命令

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7ItUW9bF-1678841859524)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230220174647230.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4nsSPwEm-1678841859524)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230220174734787.png)]

第四章 文件和目录

1.函数stat,fstat,lstat——buf返回文件有关的信息结构
#include <sys/stat.h>
 int stat(const char *pathname, struct stat *buf);//命名文件有关的信息结构
 int fstat(int filedes, struct stat *buf);//在fd上打开的文件信息结构
 int lstat(const char *pathname, struct stat *buf);//若文件是符号链接,则返回符号链接的相关信息,而不是符号链接所引用文件的信息
//Return: 0 if OK, -1 on error,buf指向我们必须提供的结构stat结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lGpzpkEB-1678841859524)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230220175536058.png)]

2.文件类型
  • 普通文件:包含某种行驶的数据(Unix不区分二进制还是文本数据)
  • 目录文件:包含了其他文件的名字以及指向与这些文件有关信息的指针
  • 块特殊文件:提供对设备带缓冲的访问,每次以固定长度为单位进行(磁盘)
  • 字符特殊文件:不带缓冲的IO,每次长度可变,系统中的设备要么是字符特殊要么是块特殊
  • FIFO:命名管道,用于进程间通信
  • 套接字:进程间通信
  • 符号链接:指向另一个文件

posix允许将IPC对象(消息队列、信号量)说明为文件

用宏确定文件类型:(这些宏都是stat结构中st_mode的成员)p77

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7McfG1Bs-1678841859525)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230220180858467.png)]

3.设置用户ID和设置组ID
  • 进程关联的ID有more than 6个,常用的是有效用户id
实际用户ID 实际组ID我们实际是谁:取自口令文件的登录项,通常不变,但root可变
有效用户ID 有效组ID 附属ID决定了进程的文件访问权限
保存的设置用户ID和保存的设置组ID由exec函数保存,在执行一个程序时包含了有效用户id和有效组id的副本
  • 文件关联的id;每个文件有一个所有者组所有者,所有者由stat结构中的st_uid指定,组所有者st_gid
  • 当执行一个程序文件时,
    • 通常,进程的有效用户id等于实际
    • 若在程序文件的st_mode中设置设置用户id,当执行文件时,进程有效用户id设置为文件所有者的用户id,st_uid。可用于增加进程权限
    • eg:若程序文件所有者是root,进程执行此文件时若打开设置用户id位,则进程的有效用户id=root
4.文件访问权限

文件信息结构中的st_mode指明了文件类型及文件权限,所有类型的文件都有访问权限

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RaxJqCka-1678841859525)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230220182916522.png)]

用户读(user-read)是中的用户是指文件所有者,

chmod(1)命令用于修改这9个权限位-u改用户(所有者),g组,o其他

  • 三类访问权限以各种方式由不同的函数引用,使用方式:

    1. 用名字pathname打开任意类型的文件时,对该名字包含的每一个目录(包括可能隐含的当前目录)都应具有执行权限,因此目录的执行权限位常被称为搜索位

      eg:为了打开/usr/dict/words, need “x” for: / ; /usr ; /usr/dic;还需要以何种方式打开文件本身就需要哪种权限

    2. 为了在open中指定O_TRUNC,需要对文件具有写权限

    3. 创建文件,需要对该目录具有写和执行权限

    4. 删除文件,需要对目录具有写,执行权限,但是不需要对文件本身读写

  • 进程每次打开、删除、创建文件,内核会进程文件访问权限测试,按序测试

    1. 有效uid=0(超级用户),则允许访问
    2. 有效uid=文件所有者(进程拥有此文件),则若文件的访问权限位被设置,则允许访问(设置用户读位为1,则进程可读该文件)
    3. 有效gid或进程的附属组id之一等于文件的组id,那么适当访问权限位被设置就允许访问
    4. 若其他用户适当的访问权限位被设置,则允许访问,否则拒绝访问
5.新文件和目录的所有权
  • 新文件的用户id=进程的有效用户id

  • 新文件的组id=进程的有效组id文件所在目录的组id

6.函数access和faccessat——测试进程实际用户id和实际组id的访问权限

一般是测试进程有效用户id对文件的访问权限,但是及时一个进程已经通过设置用户id获得root,也想测试一下实际用户id对文件的访问权限

#include <unistd.h>
int access(const char* pathnname,int mode);
int faccessat(int fd,const char*pathname,int mode,int flag);//若flag设置为AT_EACCESS则检查的是有效id的访问权限
//Returns:0 if OK,-1 on error

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JSTDaRv4-1678841859526)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230220185955968.png)]

chown设置文件用户id;chmod修改文件权限

7.函数umask——设置进程umask值来控制进程所创建文件的默认权限

与每个文件相关联的访问权限位

与每个进程相关联的文件模式创建屏蔽字(文件相关联的权限位=去掉创建文件的进程的文件模式创建屏蔽字位剩下的权限位),

eg:当创建新文件时,确保任何用户都能读文件,则设置umask=0

#include <sys/stat.h>
mode_t umask(mode_t cmask);
//Returns: previous file mode creation mask
//cmask = bitOR { S_IRUSR, S_IWUSR, S_IXUSR, S_IRGRP, S_IROTH, …}
8.函数chmod(), fchmod() ——更改文件访问权限,
#include <sys/stat.h>
 int chmod(const char *pathname, mode_t mode);//在指定文件上操作
 int fchmod(int filedes, mode_t mode);//对已打开的文件操作
//Return: 0 if OK, -1 on error,获取当前权限并修改
//Effective UID of process = file owner or root进程必须拥有文件才能改文件权限

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mnDpeQVH-1678841859526)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230220191606685.png)]

9.函数chown(), fchown(), lchown() 更改文件的用户id和组id
 #include <sys/types.h>
 #include <unistd.h>
//若参数为-1,则对应的id不变
 int chown(const char *pathname, uid_t owner, gid_t group);
 int fchown(int filedes, uid_t owner, gid_t group);
 int lchown(const char *pathname, uid_t owner, gid_t group);
//Return: 0 if OK, -1 on errot
  • lchown修改的是符号链接文件本身的id,前两个修改的是符号链接指向的文件的id
10.文件长度

st_size文件长度(单位:B),只对普通文件,目录文件和符号链接有意义

普通空文件,长度0,read返回EOF(end-of-file)

符号链接,文件长度是该符号链接所指向的文件名的实际字节数(不包含null字节)eg:符号链接文件的实际内容为usr/lib 则文件长度7

文件空洞:read读到的字节是0

11.文件截断,截为length

open中O_TRUNC截为0

#include <sys/types.h>
#include <unistd.h>
int truncate( const char *pathname, off_t length);
int ftruncate(int filedes, off_t length);
//Return: 0 if OK, -1 on erro,将现有文件长度截为length
  • 如果文件长度>length,则length后数据不能访问
  • 如果文件长度<length,文件长度增加 中间形成空洞
12.文件系统
  • 硬链接指向inode(备份,本分区link),符号链接指向path(跨分区link)
  • ls列出最后修改文件内容(不在inode里)的时间,chmod更新i节点更新的最后时间

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2KZyNUgT-1678841859526)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230220193404747.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gXLJcJGh-1678841859527)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230220193425179.png)]

  • i-node:包含文件类型,文件访问权限位,文件大小,指向文件数据段的指针
  • 重命名文件只需要构造指向i-node的新目录项,删除老目录项,链接计数不变,
  • 普通文件:硬链接+源文件的和
  • 目录文件inode引用计数:硬链接
    • dir目录下mkdir subdir,则对subdir其链接计数为2,subdir中的.和dir目录中的subdir都指向subdir
    • dir目录的链接计数为3,dir中的.,subdir中的…,dir的父目录中的dir
13.link,unlink,remove 创建和解除硬链接
  • link 为现有文件创建硬链接(指向inode节点的是硬链接)
#include <unistd.h>
int link(const char *existingpath, const char *newpath);
//Return: 0 if OK, -1 on erro

linkat可以对符号链接文件创建硬链接,设置flag可以选择是创建符号链接文件的硬链接,或者符号链接所指向文件的硬链接

  • unlink解除硬链接,只有当链接计数=0且打开文件的进程数=0才删除文件
#include <unistd.h>
 int unlink(const char *pathname);
//Returns: 0 if OK, -1 on error
//Removes directory entry
//Decrements link count
//Link count = 0 & open count = 0 then delete file

如果pathname是符号链接,则删除该顾浩链接,而不是删除该链接引用的文件。

  • remove
#include <stdio.h>
int remove(const char *pathname);
// Returns: 0 if OK, -1 on error
//For files, call unlink()
//For directories, call rmdir()
14.rename重命名文件或目录
◼ #include <stdio.h>
 int rename( const char *oldname, 
const char *newname);
// Returns: 0 if OK, -1 on error
// If newname exists:
				//If both are files, oldname newname
				//If both are dirs, oldname newname
//(newname must be empty)
//previous newname is first deleted

15.符号链接
符号链接硬链接
跨文件系统同一文件系统
任何用户都可以创建指向目录的链接只有超级用户才可以
  • 符号链接用于将一个文件或目录结构移到系统另一位位置(快捷键?)
  • 如果函数跟随符号链接,则路径名参数引用符号链接指向的文件
  • 如果不跟随,则函数的pathname指向符号链接本身

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cYv4vT4i-1678841859527)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230220201717908.png)]

16.创建和读取符号链接
  • symlink创建符号链接
#include <unistd.h>
int symlink(const char *actualpath, const char *sympath);
//Returns: 0 if OK, -1 on error
//Creates a symbolic link: sympath -> actualpath
// open() cannot open a link, so what if we want to read a link?
  • **readlink()**打开链接本身,并读链接中的名字(实际文件名)
 #include <unistd.h>
 int readlink( const char *pathname, char *buf, int bufsize);
// Returns: #bytes read if OK, -1 on error
//Combines open, read, & close
//buf: name of link, not null terminated
//open() function follows a symbolic link

17.文件时间

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-li2wd66z-1678841859527)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230220202227243.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W7Rpc3mQ-1678841859528)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230220202305235.png)]

18.修改 文件的访问时间 和修改时间
  • funtimes,utimensat——纳秒级
#include <sys/stat.h>
int futimens(int fd, const struct timespec times[2]);//打开文件才能更改
int utimensat(int fd, const char *path, const struct timespec times[2],int
flag);//使用文件名更改,不用打开
//Both return: 0 if OK, −1 on erro
//若time=null,访问时间=修改时间=当前时间
//若time指向两个timespec结构的数组,若任一数组元素tv_nsec为UTIME_NOW,相应时间设为当前时间,忽略tv_nsec
//若time指向两个timespec结构的数组,若任一数组元素tv_nsec为UTIME_OMIT,相应时间保持不变
//若time指向两个timespec结构的数组,若任一数组元素tv_nsec为上面两者都不是,则设为tv_sec和tv_nsec

时间结构体

struct timespec {
time_t tv_sec; /* seconds */
long tv_nsec; /* nanoseconds */
};
  • utimes——微秒级
#include <sys/time.h>
int utimes(const char *pathname, const struct timeval times[2]);
//Returns: 0 if OK, −1 on error

struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
19.目录的创建和删除
  • 新建目录mkdir
#include <sys/types.h>
#include <sys/stat.h>
int mkdir(const char *pathname, mode_t mode);
// Returns: 0 if OK, -1 on error
//Creates a new, empty directory
// . and .. 目录项自动创建,所制定的文件访问权限mode由进程的文件创建模式屏蔽字修改

常见错误是制定与文件相同的mode(读写),但是目录至少需要设置一个执行权限位,以允许访问该目录中的文件名。

  • 删除目录
#include <unistd.h>
int rmdir(const char *pathname);
//Returns: 0 if OK, -1 on error
//Link# = 0 & Open# = 0 space of dir freed

空目录只包含.he…

20.读目录
  • 任何人都可以读目录,但只有内核才可以写目录

    目录结构体:

struct dirent { /* defined in <dirent.h> */
ino_t d_ino; /* i-node # */
char d_name[NAME_MAX + 1]; 
/* NULL-terminated filename */
...
}
  • opendir(), rewinddir(), closedir()
#include <sys/types.h>
 #include <dirent.h>
 DIR *opendir(const char *pathname);
 struct dirent *readdir(DIR *dp);
void rewinddir(DIR *dp);
int closedir(DIR *dp)
21.更改进程的当前工作目录(搜索相对路径名的起点)

相对路径名,不以/开头的文件名

  • chdir()
#include <unistd.h>
 int chdir(const char *pathname);//新的当前工作目录
 int fchdir(int filedes);
// Return: 0 if OK, -1 on erro

第五章 标准IO库

1.标准IO库
  • 标准IO库处理缓冲区分配,以优化的块长度执行IO
  • IO缓冲总结

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7itlu3qJ-1678841859528)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230221083412919.png)]

2.流和FILE对象
  • 当我们用标准IO库打开或创建文件时,已使一个流与一个文件关联

  • 流的定向:流的定向决定了所读写的字符是单字节还是多字节。一个流最初被创建是没有定向的,若在未定向的流使用多字节IO函数,则将流的定向设置为多字节IO,反之设为单字节IO函数。

    • fwide(FILE *fp,int mode)函数可以改变流的定向
  • FILE对象:当打开一个流时,返回一个指向FILE对象的指针(文件指针),FILE对象是一个结构,包含了标准IO库为管理该流所需要的所有信息,包括实际IO文件描述符、指向该缓冲区的指针、缓冲区长度等

3.标准输入、标准输出和标准错误

一个进程被创建时,进程的三个流自动被创建

4.缓冲
  1. 全缓冲:填满缓冲区才可以进程实际操作(磁盘文件一般是全缓冲)
    • 冲洗:一个缓冲区可被冲洗
      • 在标准IO库,flush意味着将缓冲区的内容写到磁盘上:fflush()
      • 在终端驱动程序,flush丢弃缓冲区中的数据:tcflush()
  2. 行缓冲:当在输入和输出中遇到换行符时,biaozhunIO执行IO操作,允许我们一次输出一个字符,但只有写了一行才进行IO。当流涉及终端时,通常使用行缓冲,行缓冲有限制:
    • 每一行缓冲区长度固定,只要填满了计时没有换行符,也IO
    • 通过IO标准库要求从一个不带缓冲的流或行缓冲的流得到输入数据,那么就会冲洗所有行缓冲的流,理由是数据可能已在缓冲区中,并不一定要去从内核获得数据
  3. 不带缓冲:立即输出,如标准错误流
  • 缓冲特征:
    • 当且仅当stdin和stdout不指向交互设备,才是全缓冲
    • 标准错误绝不会是全缓冲,通常无缓冲
    • 指向终端设备的流是行缓冲,否则全
  • 更改缓冲类型:
#include <stdio.h>
void setbuf( FILE *fp, char *buf );//打开或关闭流,buf终端设备行缓冲,其他全缓冲
int setvbuf( FILE *fp, char *buf, int mode, size_t size );//若不带缓冲,则忽略buf和size,否则设置缓冲区及其长度,若buf=null。则系统自动分配长度BUFSIZE
//Returns 0 if OK, nonzero on error

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GdsjV0UE-1678841859528)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230221090814648.png)]

  • 流冲洗:函数内分配了自动变量的缓冲区,则返回前必须关闭流,缓冲区一部分存放管理操作信息,实际字节数少于size。一般由系统分配缓冲区长度,关闭流时,会自动释放缓冲区

    #include <stdio.h>
    int fflush(FILE *fp);
    //Returns: 0 if OK, EOF on error
    //Unwritten data passed to kernel所有未写数据传送到内核
    //fp = NULL ➔ ALL output streams flushed!
    
5.打开流
#include <stdio.h>
FILE *fopen( const char *pathname, const char *type );//打开指定文件,管道、套接字不可以通过fopen打开
FILE *freopen(const char *pathname, const char *type, FILE *fp);//在指定流打开文件
FILE *fdopen( int fd, const char *type );//通过fd打开,
//Return: file pointer if OK, NULL on error
  • type参数:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5YQv4aiL-1678841859529)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230221091725873.png)]

  • 六种打开流的方式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mBLTi4fM-1678841859529)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230221091824974.png)]

  • 关闭流
#include <stdio.h>
int fclose(FILE *fp);
// Returns: 0 if OK, EOF one error
//Before closing:输出流会被冲洗,输入流被丢弃
// 进程关闭时,所有未写缓冲数据的流都被冲洗,所有打开的流都被关闭
6.读和写流
  • 一旦打开流,可选三种非格式化IO对其操作
    • Character-at-a-time I/O: getc(), fgetc(), getchar()
    • Line-at-a-time I/O: fgets(), fputs()
    • Direct I/O: fread(), fwrite() (read/write some number of objects)
7.每次一个字符IO
  • 输入函数,用于一次读一个字符
#include <stdio.h>
int getc(FILE *fp);
int fgetc(FILE *fp);
int getchar(void); (== getc(stdin))
//Return: next char if OK, EOF on EOF or error

以上三个在文件出错或到达尾端返回值相同,可用ferror或feof区分两种情况

#include <stdio.h>
int ferror(FILE *fp);
int feof(FILE *fp);
// Return: nonzero if true, 0 otherwise
void clearerr(FILE *fp);//Clears 2 flags in FILE object:an error flag;an EOF flag
  • 将读出的数据压回流
#include <stdio.h>
int ungetc(int c, FILE *fp);
//Returns: c if OK, EOF on error
// Chars are pushed back into stream
//回送的字符不一定是上次读到的
// Cannot push EOF
  • 输出函数
#include <stdio.h>
int putc(int c, FILE *fp);
int fputc(int c, FILE *fp);
int putchar(int c); (== putc(c, stdout)
//Return: c if OK, EOF on error
8.每次一行IO
  • 输入函数
#include <stdio.h>
char *fgets(char *buf, int n, FILE *fp);//(stores: line + \n + NULL)
//只读入n-1个字符,还有一个位置用来放0,若超过n-1可能返回不完整的行,下次会继续读该行
char *gets(char *buf);//(stores line only, without \n and NULL)不推荐,可能造成缓冲区溢出
// Return: buf if OK, NULL on EOF or error

缓冲区总是以null结尾,但是字符串是以0结尾

  • 输出函数
#include <stdio.h>
int fputs(const char *str, FILE *fp);
int puts(const char *str);//不推荐,需要自己添加换行符
//Return: nonnegative value if OK, EOF on error

推荐使用fgets和fputs

9.标准IO效率

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WBqKH1k4-1678841859530)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230221094634026.png)]

注意:exit函数会冲洗所有未写的数据,然后关闭所有打开的流

10.二进制IO——一次读写一个完整的结构

fputs遇到null(缓冲区结束符)就会停止,因此需要二进制IO

#include <stdio.h>
size_t fread( void *ptr, size_t size, 
size_t nobj, FILE *fp);
size_t fwrite(const void *ptr, size_t size, 
size_t nobj, FILE *fp);
//Return: #objects read or written
  • 两种常见用法

    • 读或写二进制数组

      float data[10]
      if (fwrite(&data[2], sizeof(float), 4, fp) != 4) 
          err_sys(“fwrite error”);
      
    • 读或写一个结构

      struct {
      short count;
      long total;
      char name[NAMESIZE];
      } item;
      if (fwrite(&item, sizeof(item), 1, fp) != 1) 
      	err_sys(“fwrite error”);
      
  • 二进制IO不能跨系统:偏移量不同,二进制结构不同

11.定位流
  • 三种方式定位流

    1. ftell and fseek (get then set)

      #include <stdio.h>
      long ftell (FILE *fp);
      // Returns: curr filepos indicator if OK,-1L on error
      int fseek (FILE *fp, long offset, int whence);
      // Returns: 0 if OK, nonzero on error
      void rewind (FILE *fp);
      
    2. ftello and fseeko

    3. fgetpos and fsetpos

      #include <stdio.h>
      int fgetpos(FILE *fp, fpos_t *pos);
      int fsetpos(FILE *fp, const fpos_t *pos);
      //Return: 0 if OK, nonzero on erro
      
12.格式化IO
  1. 格式化输出

    #include <stdio.h>
    int printf(const char *format,);
    int fprintf(FILE *fp, const char *format,);
    // Return: #char output if OK, <0 on error
    int sprintf(char *buf, const char *format,);
    //Returns: #char stored in array (without NULL)
    //NULL appended to buf
    
    • 变种
    #include <stdarg.h>
    #include <stdio.h>
    int vprintf(const char *format, va_list arg);
    int vfprintf(FILE *fp, const char *format, va_list arg);
    // Return: #char output if OK, <0 on error
    int vsprintf(char *buf, const char *format, va_list arg);
    //Return: #char stored in arra
    
  2. 格式化输入

    #include <stdio.h>
    int scanf(const char *format,);
    int fscanf(FILE *fp, const char *format,);
    int sscanf(const char *buf, const char *format,);
    //Return: #items assigned, EOF on error or on EOF
    
13.实现细节
#include <stdio.h>
int fileno(FILE *fp);//获得标准IO流想关联的文件描述符fd
//Returns: file descriptor from stream
//Needed by dup() and fcntl() function
14.临时文件
  • 创建临时文件

    #include <stdio.h>
    char *tmpnam(char *ptr);//创建与现有文件名不同的有效路径名字符串
    // Returns: pointer to unique pathname
    FILE *tmpfile(void);//先调用tmpane,然后用该路径名创建一个临时二进制文件并立即unlink
    // Returns: file pointer if OK, NULL on error
    

第六章 系统数据文件和信息

1.口令文件
  • 口令文件:/etc/passwd,口令文件包含了passwd结构:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XsT4OU8D-1678841859530)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230221101838191.png)]

  • 获取口令文件项(passwd结构)

    #include <sys/types.h>
    #include <pwd.h>
    struct passwd *getpwuid(uid_t uid);
    struct passwd *getpwnam(const char *name);
    //Return: pointer if OK, NULL on error,返回指向passwd结构的指针
    
  • 查看整个口令文件(包含passwd结构的文件/etc/passwd)

     #include <sys/types.h>
     #include <pwd.h>
    struct passwd *getpwent(void);
    //Returns: pointer if OK, NULL on error/EOF
     void setpwent(void);//打开
     void endpwent(void);//关闭,两个必须搭配一起使用
    
2.阴影口令
  • 加密口令放在/etc/shadow或/etc/master.passwd中
  • 阴影口令文件只有root可读,普通口令文件可由各用户自由读取
3.组文件
  • 二进制文本:/etc/group,组结构在<grp.h>

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k3Cvhsli-1678841859530)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230221102841913.png)]

  • 查看组结构

    #include <sys/types.h>
    #include <grp.h>
    struct group *getgrgid(gid_t gid);
    struct group *getgrnam(const char *name);
    //Return: pointer if OK, NULL on error
    
  • 搜索整个组文件

    #include <sys/types.h>
    #include <grp.h>
    struct group *getgrent(void);
    //Returns: pointer if OK, NULL on error/EOF
    void setgrent(void);
    void endgrent(void);
    
4.实现区别

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dEhsdLas-1678841859531)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230221103121305.png)]

5.其他数据文件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Qn8BFuZx-1678841859531)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230221103152689.png)]

6.登录账户记录
  • 两个文件:

    • utmp :当前登录到系统的各个用户
    • wtmp: 跟踪各个登录和注销事件、
  • /var/run/utmp ---- who

  • /var/log/wtmp ---- last

  • 结构体

    struct utmp {
    char ut_line[8]; /* tty line */
    char ut_name[8]; /* login name */
    long ut_time; /* secs since Epoch */
    };
    
7.系统标识
#include <sys/utsname.h>
 int uname(struct utsname *name);//返回与主机和操作系统有关的信息
 //Returns: non-negative value if OK, −1 on error
struct utsname {
char sysname[];/* name of the operating system */
char nodename[];/* name of this node */
char release[];/* current release of operating system */
char version[];/* current version of this release */
char machine[];/* name of hardware type */
};
8.时间和日期例程
  • time返回当前时间和日期

    #seconds since Epoch: 00:00:00 1970/1/1, UTC
    #include <time.h>
    time_t time(time_t *calptr);
    //Returns: value of time if OK, -1 on error
    //time is stored in calptr if not NULL
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J5L6zVuh-1678841859531)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230221103802694.png)]

  • 分解时间(Broken down time)

    struct tm {
     int tm_sec; /* secs after the minute: [0, 60] */
     int tm_min; /* minutes after the hour: [0, 59] */
     int tm_hour; /* hours after midnight: [0, 23] */
     int tm_mday; /* day of month: [1, 31] */
     int tm_mon; /* month of year: [0, 11] */
     int tm_year; /* years since 1900 */
     int tm_wday; /* days since Sunday: [0, 6] */
     int tm_yday; /* days since Jan 1: [0, 365] */
     int tm_isdst; /* daylight saving time flag: <0, 0, >0 */
    };
    
  • 时间函数

    #include <time.h>
    struct tm *gmtime(const time_t *calptr);
    struct tm *localtime(const time_t *calptr);
    // Return: pointer to broken-down time
    time_t mktime(struct tm *tmptr);// Returns: calendar time if OK, -1 on error
    size_t strftime(char *buf, size_t maxsize, const char 
    *format, const struct tm *tmptr);
    //Returns: #char stored in buf if room, else 0
    //Time value from tmptr is formatted according to formatand stored in buf of size maxsize, if there is enough room, otherwise 0 is returned.
    

第七章 进程环境

1.main函数
int main(int argc, char *argv[]);
// arc = #arguments命令行参数数目  //argv[] = arguments指向参数的各个指针构成的数组
  • Kernel executes a special START-UP routine before main()内核在main前执行启动历程
  • Start-up routine sets things up before main() is called: stack, heap, etc
2.进程终止
  • 正常终止

    • return from main()
    • calling exit()
    • calling _exit()
    • 最后一个线程从其启动例程返回 (Section 11.5)
    • 从最后一个线程调用pthread_exit (Section 11.5)
  • 异常终止

    • calling abort() (Section 11.7)
    • 接到信号 (Section 10.2)
    • 最后一个线程对取消请求作出响应 (Sections 11.5 and 12.7)
  • 退出函数

    #include <stdlib.h>
     void exit(int status);//关闭所有打开流,会使所有输出缓冲的数据被冲洗(写到文件上),再返回内核
     #include <unistd.h>
    void _exit(int status);//立即返回内核
    
  • 函数atexit,用于等级终止处理程序,调用与登记顺序相反

    #include <stdlib.h>
     int atexit(void (*func) (void));
    // Returns: 0 if OK, nonzero on error
    // func is an exit handler
    // exit() calls these exit handler functions in the reverse order of registration
     #times called = #times registered
    

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q67dcjjb-1678841859532)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230221110130205.png)]

3.命令行参数
  • 执行一个程序时,调用exec()的进程可将命令行参数传递给新程序

  • getopt可以分析命令行参数和选项

    #include <unistd.h>
     int getopt(int argc, char * const argv[], const char *options);
     //extern int optind, opterr, optopt;
     //extern char *optarg;
    // Returns: the next option character, or−1 when all options have been processed
    

    Example:

    ​ command [-i] [-u username] [-z] filename

    ​ pass “iu:z” as the options

4.环境表
  • 每个程序一张环境表,该表是一个字符指针数组,
  • 全局变量extern char environ包含该指针数组的地址
  • getenv(): get an environment string
  • putenv(): set an environment string

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DpeQjNXi-1678841859532)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230221110759598.png)]

5.C程序的存储空间布局
  • 正文段:机器指令,通常该段是可共享的只读的
  • 初始化数据段:已经赋初值的变量
  • 未初始化数据段(bss段):未赋初值的变量,程序执行前内核将其自动置为0,如long sum[1000];
  • 栈:自动变量以及函数调用所需保存的信息
  • 堆:动态存储分配

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tZXUYuF8-1678841859533)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230221111745981.png)]

6.共享库
  • 共享库使得可执行文件中不需要包含公用库函数,只需要再所有进程都可引用的存储区中保存这种库例程的副本,程序第一次运行或第一次调用某个库函数是,用动态链接方法将陈鼓型与共享库函数相连接
  • 这减少了可执行文件的长度,但增加了运行时间开销
7.存储空间分配
  • 三个动态分配存储空间函数(从堆)

    #include <stdlib.h>
    void *malloc(size_t size);//分配指定字节,存储区初始值不确定
    void *calloc(size_t nobj, size_t size);//为指定数量指定长度的对象分配存储空间,初始化为0
    void *realloc(void *ptr, size_t newsize);//增加或减少以前分配区长度,可能需要移动位置,且新增区域内初始值不确定
    //Return: nonnull pointer if OK, NULL on error
    void free(void *ptr);
    
  • alloa

    • 从栈分配空间,而不是堆
    • 不需要free,函数返回时自动释放
    • 有些系统不支持
8.环境变量
  • 获取环境变量值

    #include <stdlib.h>
    char *getenv(const char *name);
    // Returns: pointer to value associated with name, NULL if not found
    // Some environment variables are set automatically by shell upon login
    // E.g.: HOME, USER, etc.
    

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j8zmjBoN-1678841859533)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230221112706623.png)]

  • 设置环境变量:改变现有变量值或者增加新的环境变量

    #include <stdlib.h>
    int putenv(const char *str);
    int setenv(const char *name, const char *value, int rewrite);
    //Return: 0 if OK, nonzero on error
    void unsetenv(const char *name);
    

    修改环境表的注意事项:

    • 删除字符串简单,再环境表中找到指针,所有后续执行很向环境表首部移动即可
    • 增加字符串或修改就比较困难,因为环境表和环境字符串通常占用进程地址空间的顶部,所以不不能向高地址扩展;同时也不能移动它之下的栈帧,所以也不能向低地址扩展。两者组合是的该控件的长度不能增加,只能全部移动到另一个空间(堆)
9.函数setjmp和longjmp实现函数之间的跳转
  • goto语句不能跨越函数,这两个函数可以处理发生在深层嵌套函数调用中的出错情况

  • setjmp和longjmp搭配使用可以实现当函数出现致命错误时,在栈上跳过若干栈帧,返回到当前函数调用路径上的某个函数

  • 栈帧:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3C29XXyv-1678841859533)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230221130410367.png)]

  • setjmp和longjmp函数

    #include <setjmp.h>
    int setjmp(jmp_buf env);
    //Returns: 0 if called directly, nonzero if returning from a call to longjmp
    void longjmp(jmp_buf env, int val)
    
  • 自动变量(automatic)、寄存器(register)变量和易失(volatile)变量

    • longjmp后这些变量值时自动变量寄存器变量的值是不确定的
    • 有一个自动变量,又不想其值回滚,可以将变量定义为具有易失属性(volatile)
    • 声明为全局变量和或静态变量的值在执行longjmp时不变
    • 自动变量的存储空间分配在函数的栈帧上,函数返回时,下一个函数创建的栈帧可能覆盖自动变量,因此生命自动变量的函数返回后,不能再引用这些变量。
10.函数 getrlimit(), setrlimit()
  • 每个进程都有一组资源限制,可用以下函数查询和更改

    #include <sys/time.h>
    #include <sys/resource.h>
    int getrlimit(int resource, struct rlimit *rlptr );
    int setrlimit( int resource, const struct rlimit *rlptr );
    //Return: 0 if OK, nonzero on error
    
  • 上两个函数每次调用都指定一个资源以及一个指向下列结构的指针

    struct rlimit {
     rlim_t rlim_cur; /* soft limit: curr limit */
     rlim_t rlim_max; /* hard limit: max */
    };
    
    • 软限制:可以被任何进程更改,但需要≤硬限制值
    • 硬限制:可以被任何进程降低,必须≥软限制值
    • 只有root可以提高硬限制值

第八章 进程控制

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YxLMepXQ-1678841859534)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230221132438388.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-839euErX-1678841859534)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230221132936523.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zoI0XNXh-1678841859535)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230221132548881.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TdmAT66p-1678841859537)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230221132750091.png)]

1.进程标识
  • 是一个非负整数

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jmIn3ZNW-1678841859537)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230221133033389.png)]

  • 获取进程id的函数

    #include <sys/types.h>
    #include <unistd.h>
    pid_t getpid(void); return PID
    pid_t getppid(void); return parent PID
    uid_t getuid(void); return real UID
    uid_t geteuid(void); return effective UID
    gid_t getgid(void); return real GID
    gid_t getegid(void); return effective GID
    
2.fork函数
  • fork()时由用户在Unix内核中创建进程的唯一方式,可用于父子进程执行不同的程序或者不同代码段

    #include <sys/types.h>
    #include <unistd.h>
    pid_t fork(void);
    //Returns: 0 in child, child PID in parent, -1 on error
    
  • 父进程/子进程

    • 父子进程继续执行fork()调用之后的指令
    • 子进程获得父进程数据空间、堆和栈的副本(浪费时间空间)
    • 通常共享正文段时只读的,通常fork后跟随着exec
  • 写时复制Copy-On-Write (COW)

    • 存储区对父进程和子进程只读共享
    • 如果由进程想写,则内核只为修改区域的呢快内存制作一个副本(省时空)

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TSgueE1c-1678841859538)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230221134610136.png)]

  • 实例(Program 8.1: fork())

    #include <sys/types.h>
    #include "apue.h"
    int glob = 6; /* external variable in initialized data */
    char buf[] = "a write to stdout\n";
    int main(void) {
        int var; /* automatic variable on the stack */
        pid_t pid;
        var = 88;
        if (write(STDOUT_FILENO, buf, sizeof(buf)-1) != sizeof(buf)-1)
            err_sys("write error");
        printf("before fork\n"); /* we don't flush stdout */
        if ( (pid = fork()) < 0)
            err_sys("fork error");
        else if (pid == 0) { 
            /* child */
        	glob++; /* modify variables */
        	var++;
        } else
        sleep(2); /* parent */
        printf("pid = %d, glob = %d, var = %d\n", getpid(), glob, var);
        exit(0);
    }
    
    

    结果

    $ a.out
    a write to stdout
    before fork
    pid = 430, glob = 7, var = 89
    pid = 429, glob = 6, var = 88
    #输出到终端,行缓冲由换行符冲洗,不再输出
    $ a.out > temp.out
    $ cat temp.out
    a write to stdout
    before fork
    pid = 432, glob = 7, var = 89
    before fork
    #输出到文件全缓冲,子进程复制了父进程的缓冲区,数据还在缓冲区中,第二个printf将其追加到文件中
    pid = 431, glob = 6, var = 88
    
3.文件共享
  • Parent and child share the same file descriptors

  • 父子进程共享文件偏移量, otherwise overwrite

  • 若父子写同一fd指向的文件,但又没有同步(wait)父子进程会混合输出,因此一般fork后

    • 父等子
    • 父子执行不同的程序段,且各自关闭他们不再使用的fd

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pmVQccTK-1678841859538)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230221135717433.png)]

子进程集成父进程的:实际用户id,有效用户id,等等

4.vfork 函数创建一个新进程去exec一个新程序
  • 不为子进程创建地址空间副本
  • exec前,子进程运行在父进程的地址空间
  • vfork保证子进程先执行(而fork不行),再他调用exec或exit后父进程才运行
5.子进程终止
  • 终止状态:
    • 正常终止:exit status
    • 异常终止:kernel indicates reason
  • 如果子进程在父进程之前终止,返回终止状态给父进程
  • 如果子进程在父进程之后终止,子进程的父进程变为init(pid=1)被init进程收养
  • SIGCHILD
    • 子进程终止后,内核会发送一个信号SIGCHILD给父进程
    • 信号默认处理方式:忽略
    • 信号处理方式可由用户定义
6.wait(), waitpid()获取子进程的终止状态
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *statloc);//在一个子终止前,一直阻塞
pid_t waitpid( pid_t pid, int *statloc, int options);//可以等待特定的进程,可以不阻塞
//Return: PID if OK, 0, -1 on error
  • 调用wait(), waitpid()的进程(父进程)会发生什么:

    • 若所有子进程都在运行,则父阻塞

    • 若有一个终止,正等待父获取其状态,则立即返回

    • 若没有任何子进程,则出错返回

    • 以下为option可选值,可进一步控制waitid的操作

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4rLKCCzq-1678841859538)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230221141552309.png)]

7.wait3 and wait4——在wait基础上还获得资源概况
  • 允许内核返回由终止进程机器所有子进程使用的资源概况

    #include <sys/types.h>
    #include <sys/wait.h>
    #include <sys/time.h>
    #include <sys/resource.h>
    pid_t wait3(int *statloc, int options, struct rusage *rusage);
    pid_t wait4(pid_t pid, int *statloc, int options, struct rusage *rusage);
    // Return: PID if OK, 0 or -1 on error
    
8.僵死进程(zombie)
  • 假设子进程先终止,且父进程不wait子进程(父没有回收子进程,释放子进程资源),子进程成为zombie
  • 僵死:minimal info of dead child process (pid, termination status, CPU time)
  • 一个进程不想等待子进程完成,如何避免僵死进程
    • fork两次
9.竞争条件
  • 当多个进程企图处理共享数据,最后结果由取决于进程运行顺序时,认为发生了竞争条件
  • fork后并不知道哪个进程先运行
  • 竞争条件问题大多数时间难以探测到
    • 父进程可以等待子进程call wait, waitpid, wait3, wait4
    • 子进程可以等待父进程while (getppid() != 1) sleep(1);
  • fork后,父子进程都需要做些事情时,父子进程需要告知对方各自状态以避免产生竞争条件
    • TELL each other when its initial set of operations are done, and
    • WAIT for each other to complete
10.函数exec
  • 子进程调用exec执行另一个程序,用磁盘上的一个新程序替换当前子进程的正文段、数据段、堆段和栈段

  • 7中exec

    #include <unistd.h>
    int execl(const char *pathname, const char *arg0,/* (char *)0 */);
    int execv(const char *pathname, char *const argv[]);
    int execle(const char *pathname, const char *arg0,/* (char *)0, char *const envp[] */);
    int execve(const char *pathname, char *const argv[], char *const envp[]);
    int execlp(const char *filename, const char *arg0,/* (char *)0 */);
    int execvp(const char *filename, char *const argv[]);
    //Return -1 on error, no return on success.
    
  • 不同之处:

    • filename (execlp, execvp: uses PATH) v/s pathname (others: does not use PATH)
    • list (l) v/s vector (v)
      • list of arguments (execl, execle, execlp)
      • array of pointers to arguments (execv, execve, execvp)
    • pointer to an array of pointers to environment strings (execle, execve) v/s environ (others)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZwzvLfsM-1678841859539)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230221145224161.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lFub7gA5-1678841859539)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230221145246743.png)]

  • 在执行exec后,进程id没变,新程序从调用进程继承了下列属性,进程执行exec后实际用户id和实际组id不变,而有效用户id可能变,如果设置用户id位打开,则有效用户id=程序文件的所有者id

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BAzI3YPV-1678841859539)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230221145350701.png)]

11.更改用户ID和组ID
  • 更改实际用户ID和有效用户ID

    #include <sys/types.h>
    #include <unistd.h>
    int seteuid(uid_t uid);
    int setegid(gid_t gid);
    ◼ Return: 0 if OK, -1 on error
    ◼ Only effective UID or GID is changed
    
  • 更改用户ID的规则

    • 若进程有root特权,则setuid将实际用户id、有效用户id和保存的设置用户ID设置为uid
    • 若进程没有有root特权,但uid=实际用户ID或保存的设置用户id,则sutuid只将有效用户id设置为uid,不更改实际用户id和保存的设置用户id
    • 若都不满足,则errno
  • 三个id

    • 只有root才能更改实际用户ID
    • 仅当对文件设置了设置用户id位,exec函数才设置有效用户id为文件的所有者id
    • 保存的设置用户id时由exec服饰有效用户id,如果哦设置了设置用户id位,会保存有效用户id的副本到保存的设置用户id,然后将有效用户id设置为文件所有者id
  • 函数setreuid 交换实际用户id和有效用户id值

    #include <sys/types.h>
    #include <unistd.h>
    int setreuid(uid_t ruid, uid_t euid);
    int setregid(gid_t rgid, gid_t egid);
    // Sets the real user ID of the process to ruid and the effective user ID to euid. 
    // If argument is -1, leave that ID unchanged
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DxYQjUcw-1678841859540)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230221150727541.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ELYjQJPD-1678841859540)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230221151316073.png)]

12.at程序
  1. assuming owned by daemon, set-UID bit is SET, after exec:
    • Real UID = our own UID
    • Effective UID = daemon
    • Saved set-UID = daemon
  2. reduce privileges. it calls seteuid(), only e-UID is changed
    • Real UID = our own UID (unchanged!)
    • Effective UID = our own UID
    • Saved set-UID = daemon (unchanged!)
  3. Increase privileges to to access the configuration files that control which commands are to be run and the time at which they need to run. These files are owned by the daemon that will run the. The at command calls seteuid to set the effective user ID to daemon. This call is allowed because the argument to seteuid equals the saved set-user-ID. (This is why we need the saved setuser-ID.) After this, we have:
    • Real UID = our own UID (unchanged!)
    • Effective UID = daemon
    • Saved set-UID = daemon (unchanged!)
  4. After the files are modified to record the commands to be run and the time at which they are to be run, the at command lowers its privileges by calling seteuid to set its effective user ID to our user ID. This prevents any accidental misuse of privilege. At this point, we have
    • Real UID = our own UID (unchanged!)
    • Effective UID = our own UID
    • Saved set-UID = daemon (unchanged!
  5. The daemon atd starts out running with root privileges. To run commands on our behalf, the daemon calls fork and the child calls setuid to change its user ID to our user ID. Because the child is running with root privileges, this changes all of the IDs. We have
    • Real UID = our own UID
    • Effective UID = our own UID
    • Saved set-UID = our own UID
13.system函数
#include <stdlib.h>
int system ( const char *cmdstring );
//uses fork to create a child process that executes the shell command using execl :
//execl("/bin/sh", "sh", "-c", command, (char *) 0); 
//system() returns after the command has been completed.

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m8eosaJ3-1678841859540)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230221153510376.png)]

  • 不直接使用fork和exec的邮电是:system进行了所需的何种出错处理以及各种信号处理
  • 但是如果在一个设置用户ID程序中调用system,会发生安全性漏洞:使程序的超级用户权限在system后会保留,因此设置用户id程序应直接调用fork,exec
14.进程时间
  • 墙上时钟时间、用户cpu时间、系统cpu时间

    clock_t times(struct tms *buf);
    ◼ Returns: elapsed wall clock time in clock ticks if OK, -1 on error
    struct tms {
    clock_t tms_utime; /* user CPU time */
    clock_t tms_stime; /* system CPU time */
    clock_t tms_cutime; /* sum of user time for terminated children */
    clock_t tms_cstime; /* sum of system time for terminated children*/
    };
    

第九章 进程关系

1.终端登录

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-d3cr8m7Z-1678841859540)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230221154636715.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YbjLR8lw-1678841859541)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230221154645907.png)]

因为最初的init进程具有root特权,所有fork后的所有进程都root权限(父子各种有效用户id等相同)

2.网络登录

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Y3ssbRoh-1678841859541)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230221155718214.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P4EFOTmB-1678841859541)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230221155730990.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9HbmTUEt-1678841859542)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230221155744181.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YJZMZUzt-1678841859542)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230221155828050.png)]

第十章 信号

  • 信号是软件中断
  • 信号提供了一种处理异步事件(不知道先后)的方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xLuFcjgv-1678841859542)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230221160709001.png)]

1.信号概念
  • 每个信号都有一个名字,以SIG开头,eg:
    • SIGABRT: abort signal from abort()
    • SIGALRM: alarm signal from alarm()
  • 在 <signal.h>中信号名被定义为一个正整数常量(信号编号)
2.信号产生
  1. 终端产生信号:(CTRL+/)产生中断信号SIGINT
  2. 硬件异常:
    • 除数为0:SIGFPE
    • 无效的内存引用:SIGSEGV
  3. 进程调用kill命令,将信号发送给另一个进程
  4. 检测到某种软件条件发生
    • SIGURG: out-of-band network data
    • SIGPIPE: pipe-write after pipe reader is terminated
    • SIGALRM: alarm clock expires
3.信号的处理
  1. 忽略信号,有两种信号不能忽略SIGKILL and SIGSTOP,因为他们向内核和超级用户提供了使进程终止或停止的可靠方法
  2. 捕捉信号,通知内核在信号发生时调用 一个用户函数
    • Own shell: SIGINT ➔ return to main()
    • Child terminated: SIGCHLD ➔ waitpid()
    • Temporary files: SIGTERM ➔ clean up
  3. 执行默认动作:终止进程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qvibepPH-1678841859543)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230221161738189.png)]

4.signal函数
  • 信号处理函数

    #include <signal.h>
     void ( *signal(int signo, void (*func)(int)) ) (int);
    //Returns: previous disposition of signal if OK, SIG_ERR on error
    // signo: SIGXXXX signal name
    // func: SIG_IGN, or SIG_DFL, or user-defined function
    
    • 该函数的简便形式

      typedef void Sigfunc(int);
      Sigfunc *signal(int, Sigfunc *);
      
  • 程序启动时,通常信号被设置为默认动作(终止进程),除非调用exec忽略改信号。调用exec执行程序时将原先设置为要捕捉的信号都更改为默认动作。一个进程原先要捕捉的信号,在执行一个新程序后就不能再捕捉

5.不可靠的信号
  • 信号可能会丢失:发生了但进程不知道
  • 进程对信号的控制能力差,他能捕捉或忽略,但是有时用户希望内核阻塞信号不要忽略,在其发生时记住它,在进程准备好后再通知它
6.中断系统调用
  • unix特性:进程进程在执行一个低速系统调用而阻塞的时候,捕捉到一个信号,则改系统调用就被终端不在继续执行,改系统调用返回出错,error=EINTR
  • 低速系统调用可能使进程永久阻塞,eg:pause,wait;
  • 可用中断系统调用:一进城启动读终端,但是用户不
  • 为了帮助应用程序不必处理系统调用,引入了某些中断系统调用的自动重启动
    • 自动重启动:ioctl、read、ready、write、wait、waitpid

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1vo5Dplh-1678841859543)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230221162848918.png)]

7.可重入函数
  • 定义:可由两个或多个进程(任务、信号处理程序)调用的函数,可被任意抢占(中断),并且仍然给出相同的可预测输出结果。

  • 不可重入函数:

    • Use static variables,
    • Call malloc or free,
    • belong to standard I/O library(use global data structure)
  • 信号处理程序中必须使用可重入函数(异步信号安全函数),保证在信号处理操作期间,他会阻塞任何引起不一致的信号发送

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qvhK9y0Z-1678841859543)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230221163948038.png)]

8.可靠信号术语和语义
  • 当造成信号的事件发生时,为进程产生一个信号(或向进程发送一个信号),事件可以是硬件异常、软件条件、终端产生的信号或调用kill函数。当一个信号产生时,内核通常在进程表中以某种形式设置一个标志
  • 当对进程采取以上的操作,叫做向进程递送了一个信号,在信号产生和递送之间信号是未决的。若为进程产生一个阻塞的信号,且对此信号的动作是默认或捕捉,则保持此信号为未决状态
  • 每个进程有一个信号屏蔽字,规定了当前要阻塞递送到该进程的信号集。屏蔽位设置则阻塞
  • 可用sigpromask函数检测和更改当前信号屏蔽字
  • 用sigpending判断哪些信号设置为阻塞并术语未决状态的
9.函数kill和raise 发信号
  • kill将信号发送给进程或进程组

  • raise允许向自身发送信号

    #include <sys/types.h>
    #include <signal.h>
    int kill(pid_t pid, int signo);
    int raise(int signo);
    // Both return: 0 if OK, -1 on error
    
  • kill

    • pid > 0: sent to PID==pid
    • pid < 0: sent to PGID==|pid| 绝对值
    • pid == 0: sent to all processes with PGID == PGID of sender (with perm)
    • pid == -1: not defined in POSIX.1 (Broadcast signals in SVR4, BSD)
10.alarm函数 和pause函数
  • alarm函数 设置定时器,在将来某个时刻超时时产生SIFALARM信号,系统默认动作是终止进程

    #include <unistd.h>
    unsigned int alarm(unsigned int seconds);
    // Returns: 0 or #seconds until previously set alarm
    
  • pause函数 使调用进程挂起直至捕捉到信号,只有执行了一个信号处理函数并返回时,pause返回-1

    #include <unistd.h>
    int pause(void);
     Returns: -1 with errno set to EINTR
    // pause returns only if a signal handler is executed and that handler returns!
    
11.信号集
  • 信号集:信号的集合

  • 处理信号集的函数

    sigset_t (data type to represent multiple signals)
    #include <signal.h>
    int sigemptyset (sigset_t *set);//初始化,清除所有信号
    int sigfillset (sigset_t *set);//初始化,包含所有信号
    int sigaddset (sigset_t *set, int signo);//将一个信号添加到已有信号集中
    int sigdelset (sigset_t *set, int signo);//从信号集中删除一个信号
    //Return: 0 if OK, -1 on error
    int sigismember (const sigset_t *set, int signo);
    //Returns: 1 if true, 0 if false
    
12.函数sigprocmask
  • 检测或更改信号屏蔽字,可同时检测并更改信号屏蔽字(当前阻塞而不能递送给进程的信号集)

    #include <signal.h>
    int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
    // Returns: 0 if OK, -1 on error
    //oset != NULL ➔ current mask returned in oset只返回当前屏蔽字oset,不改变
    // set != NULL ➔ current mask modified …修改当前信号屏蔽字
    
  • how指示如何修改

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Rx3F3wVE-1678841859543)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230221170608694.png)]

13.sigpending Function返回正在被阻塞且未决的信号集
  • sigprocmask只是设置进程的信号屏蔽字,可能进程当前并没有信号阻塞。
  • sigpending是返回进程当前的阻塞且未决的信号集
#include <signal.h>
int sigpending(sigset_t *set);
// Returns: 0 if OK, -1 on error
14.sigaction——检查或修改与指定信号相关联的处理动作
#include <signal.h>
 int sigaction(int signo, const struct sigaction *act, struct sigaction *oact);
// Returns: 0 if OK, -1 on error
  • 在调用信号捕捉函数之前应该将添加信号屏蔽字屏蔽该信号,仅当从信号捕捉函数返回再恢复原来的,这样在调用信号处理程序时就能阻塞此信号。例如捕捉alarm信号,处理alarm信号的信号处理程序时应屏蔽alarm信号

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BVaTgR1D-1678841859544)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230221172012628.png)]

15.sigsetjmp, siglongjmp
  • 与setjmp and longjmp类似

  • 调用longjmp,当捕捉到一个信号时,进入信号捕捉函数,此时当前信号被自动加进信号屏蔽字中,这几阻止了后来产生的这种信号中断信号处理程序

  • sigsetjmp, siglongjmp在执行完会恢复原来的信号屏蔽字

    #include <setjmp.h>
    int sigsetjmp(sigjmp_buf env, 
    int savemask);
    //Returns: 0 if called directly, nonzero if returning 
    from siglongjmp
     void siglongjmp(sigjmp_buf env, int val);
    
16.sigsuspend 函数——原子pause+恢复信号屏蔽值
  • 如果在解除信号阻塞和pause之间发生了信号,就会产生问题,可能不会再有这个信号,pause会一直等下去

  • 因此需要一个原子操作:先恢复信号屏蔽字,使进程休眠,这是一个原子操作

    #include <signal.h>
    int sigsuspend(const sigset_t *sigmask);
    //Returns: -1 with errno set to EINTR
    
  • 在捕捉到一个信号或发生了一个会终止该进程的信号 之前,改进程被挂起;如果捕捉到一个信号而且从改信号处理程序返回,则sigsuspend返回,该进程的信号屏蔽字设置为调用sigsuspend之前的值

第十一章 线程

1.线程概念:操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位、
  • 每个线程都包含有表示执行环境所必需的信息,区中包括进程中标识线程的线程ID、一组寄存器、栈、调度优先级和策略、信号屏蔽字、error变量以及线程私有数据。进程的所有线程共享进程的左右信息,包括可执行程序的代码、程序的全局内存、栈以及文件描述符。
2.线程ID:
  • 进程ID在整个系统中是唯一的,但线程ID不同,他只有在他所属的进程上下文中才有意义

    #include <pthread.h>
    pthread_t pthread_self(void); 
    // Returns: the thread ID of the calling thread 
    
  • 线程可以调用pthread_self函数获得自身的线程ID

3.线程创建
  • 创建进程

    #include <pthread.h>
     int pthread_create(
    pthread_t *restrict tidpconst pthread_attr_t *restrict attr, void *(*start_rtn)(void *), void *restrict arg); 
    Returns: 0 if OK, error number on failure
    

    拓展:

    • 传递一个结构作为参数
      • allocate memory to a pointer to the data structure
      • initialize it
      • pass the pointer typecast as **void ***
  • 新创建的线程从start_rnt函数的地址开始运行

  • 线程创建时,并不能保证哪个线程(调用线程、还是新建的线程)会先运行,新线程继承调用线程的浮点环境和信号屏蔽字,但是改线程的挂起信号会清除

  • 每个进程只有一个控制线程

  • 应该创建多少个线程?really depend on the nature of the application

    • A thread that is a 100% CPU-bound will be continually hammering away on the CPU。If the application is determined to be highly CPU-bound, then creating a large number of threads will stress the system
      • max number of threads = number of CPU cores * (1.5 or 2
    • A 100% I/O-bound thread is one that is always in a blocking (or wait) state, never executing on CPU.If the application being designed is I/O-bound in nature, then creating even a large-ish number of threads that just wait for work is all right
4.线程终止
  • 如果进程中的任意线程调用了exit、_Exit或者 _exit整个进程都会终止

  • 三种不终止进程,终止线程的方式

    1. 从启动例程中返回,返回值是线程的退出码

    2. 线程被同一进程中的其他线程取消

    3. 线程调用pthread_exit.

      void pthread_exit(void *rval_ptr); 
      
  • pthread_join函数——等待线程执行结束,获得某个线程执行结束时返回的数据

    #include <pthread.h>
    int pthread_join(pthread_t thread,void **rval_ptr); 
    //Returns: 0 if OK, error number on failure 
    
    • 调用线程将一直阻塞,直到指定的线程调用pthread_exit
    • 调用pthread_join自动把线程设置为分离状态,这样资源可以恢复
    • 如果线程已经是分离态,则pthread_join失败不能再等待它的终止状态
5.线程分离
  • pthread有两种状态,joinable状态和unjoinable状态,如果线程是joinable状态,当线程函数自己返回退出时或pthread_exit时都不会释放线程所占用堆栈和线程描述符(总计8K多)。只有当你调用了pthread_join之后这些资源才会被释放。若是unjoinable状态的线程,这些资源在线程函数退出时或pthread_exit时自动会被释放。

  • unjoinable属性可以在pthread_create时指定,或在线程创建后在线程中pthread_detach自己, 如:pthread_detach(pthread_self()),将状态改为unjoinable状态,确保资源的释放。或者将线程置为 joinable,然后适时调用pthread_join.

  • 线程分离函数

    #include <pthread.h>
    int pthread_detach(pthread_t tid); 
    //Returns: 0 if OK, error number on failure 
    //When it terminates, its resources are automatically released without join.
    
  • 一个线程可以建立多个线程清理程序(与进程的atexit类似),执行顺序与注册顺序相反

    #include <pthread.h>
    void pthread_cleanup_push(void (*rtn)(void *),void *arg); 
    void pthread_cleanup_pop(int execute); 
    
    • 当线程执行以下动作时,清理函数rtn 是由 pthread_cleanup_push调度的,否则不调用清理函数rtn
      • 调用 pthread_exit
      • 响应取消请求时
      • 用非0参数调用pthread_cleanup_pop
6.线程同步
  • 临界区:访问临界资源的代码

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ijBrjRtF-1678841859544)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230221190229210.png)]

  • 线程同步(按序进行)

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LLyc0rkR-1678841859545)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230221190333489.png)]

7.互斥量mutex
  • 本质上是一把锁
#include <pthread.h>
 int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr); 
 int pthread_mutex_destroy(pthread_mutex_t *mutex); 
//Both return: 0 if OK, error number on fa

使得数据总是顺序一致,就不需要额外的同步

8.lock
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex); 
//All return: 0 if OK, error number on failure
9.timelock ——允许绑定线程阻塞时间,避免永久阻塞
#include <pthread.h>
#include <time.h>
int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex,const struct timespec *restrict tsptr); 
//Returns: 0 if OK, error number on failure
10.死锁
  1. Self deadlock (relock): A thread will deadlock itself if it tries to lock the same mutex twice.

    两个线程都在请求另一个线程拥有的资源

  2. ABBA deadlock: When we use more than one mutex in our programs, a deadlock can occur if one thread T1 to hold a mutex A and block while trying to lock a second mutex B at the same time that another thread T2 holding the second mutex B tries to lock the first mutex A.

  • 死锁避免:
    • Deadlocks can be avoided by carefully controlling the order in which mutexes are locked.
    • If all threads always lock mutex A before mutex B, no deadlock can occur from the use of the two mutexes.
11.读写锁
  • 读写锁的三种状态:读模式加锁(其他读可以,写不行);写模式加锁(其他读写都不可以);不加锁
  • 可以多个线程占有读模式的读写锁,但只有一个线程可以占有写模式的读写锁
  • 创建读写锁,与mutex相比读写锁在使用之前必须初始化,释放内存前必须销毁
#include <pthread.h>
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock); 
//Both return: 0 if OK, error number on failure
  • 加锁解锁

    #include <pthread.h>
    int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
    int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
    int pthread_rwlock_unlock(pthread_rwlock_t *rwlock); 
    int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
    int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
    int pthread_rwlock_timedrdlock(pthread_rwlock_t *restrict rwlock, const struct timespec *restrict tsptr); //带有超时的读写加锁函数
    int pthread_rwlock_timedwrlock(pthread_rwlock_t *restrict rwlock, const 
    //All return: 0 if OK, error number on failure
    
12.条件变量
  • 条件变量时线程可用的另一种同步机制,和互斥量一起使用时以无精症的方式登台特定的条件方式。
  • 条件由互斥量保护,线程在改变条件状态之前必须首先锁住互斥量。
  • 条件变量初始化:
#include <pthread.h>
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
int pthread_cond_destroy(pthread_cond_t *cond); 
//Both return: 0 if OK, error number on failure
  • pthread_cond_wait等待条件变量变为真,pthread_cond_timedwait如果在给定时间不能满足则返回错误码(P原语)

    #include <pthread.h>
    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);
    //Both return: 0 if OK, error number on failure
    
    • 传递给函数的互斥量对条件进行保护,调用者把锁住的互斥量传给函数。
  • 通知线程条件已经满足,唤醒等待该条件的线程(V原语)

    #include <pthread.h>
    int pthread_cond_signal(pthread_cond_t *cond);
    int pthread_cond_broadcast(pthread_cond_t *cond); 
    //Both return: 0 if OK, error number on failure
    
  • 如果不使用互斥量对条件变量(同步量)进行加锁就会出现问题

    • Prob1: Thread_B in right side runs first. If Thread_A modifies the condition during condition check in Thread_B, Thread_B could not notice this change.

    • Prob2: pthread_cond_signal in Thread_A can execute before pthread_cond_wait of Thread_B, Thread_B will not wake up forever.

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZwdyahZd-1678841859545)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230221194456217.png)]

  • 使用signal,wait就不会出现这种问题

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4nFkcLiY-1678841859545)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230221194543346.png)]

13.自旋锁
  • 自旋锁在获取锁之前一直忙等阻塞状态,互斥量是通过休眠使进程阻塞
  • 自旋锁可用于:锁被持有的时间段,线程不希望在重新调度上花费太多成本,在非抢占式内核中有用,不适合用户层
#include <pthread.h>
int pthread_spin_init(pthread_spinlock_t *lock, int pshared);//初始化
int pthread_spin_destroy(pthread_spinlock_t *lock); //释放
int pthread_spin_lock(pthread_spinlock_t *lock);//一直等到获取释放
int pthread_spin_lock(pthread_spinlock_t *lock);//一直等到获取锁
int pthread_spin_trylock(pthread_spinlock_t *lock);//马上获取或返回EBUSY
int pthread_spin_unlock(pthread_spinlock_t *lock); //解锁
//All return: 0 if OK, error number on failure 
14.屏障
  • 是用户协调多个线程并行工作的同步机制

  • pthread_join就是一种屏障,允许一个线程等待,直到另一个线程退出(主线程等待子线程执行完再继续执行后面代码)

  • 屏障允许每个线程等待,直到所有的合作线程都到达某一点,然后从该点继续执行

    #include <pthread.h>
     int pthread_barrier_init(pthread_barrier_t *restrict barrier,const pthread_barrierattr_t *restrict attr, unsigned int count);
    int pthread_barrier_destroy(pthread_barrier_t *barrier); 
    // Both return: 0 if OK, error number on failure 
    int pthread_barrier_wait(pthread_barrier_t *barrier); 
    //Returns: 0 or PTHREAD_BARRIER_SERIAL_THREAD if OK, error number on failure
    
15.线程安全
  • 线程安全:函数或API可在所线程下安全的并发执行

  • 五种实现线程安全的方式

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ScDFSJaM-1678841859546)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230221195852671.png)]

  1. Thread safety via mutex locks
  2. Thread safety via function refactoring函数重构:用foo代替foo_r
  3. Thread safety via TLS:只使用本地和TLS变量
  4. Thread safety via TSD:所有数据保持一个单独的副本

第十五章 进程间通信

1.进程间通信IPC
  • IPC需要一些同步机制实现

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-blqnZPZs-1678841859546)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230221200453877.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sxP5NDfN-1678841859546)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230221200308138.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o3xFeAmW-1678841859547)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230221200318864.png)]

2.管道
  • 最古老的形式,不命名管道

  • 半双工(数据只在一个方向流动)

  • 只能在具有公共祖先的进程之间使用

    #include <unistd.h>
    int pipe (int fieldes[2]);
    //Returns: 0 if OK, -1 on erro
    
    • filedes[0]: open for reading
    • filedes[1]: open for writing
    • Output of filedes[1] is input of filedes[0]数据从一到零

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ODyIHQVm-1678841859547)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230221200854689.png)]

单个进程的管道没有任何用户,通常进程会先pipe,再fork,创建父进程到子进程的IPC管道,数据流向取决于fork后的操作

  • 父进程到子进程的管道

    • 父进程f[1]=1,f[0]=0
    • 子进程f[1]=0,f[0]=1
  • 子进程到父进程的管道

    • 父进程f[1]=0,f[0]=1
    • 子进程f[1]=1,f[0]=0

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k6gVMugE-1678841859547)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230221200917142.png)]

  • 当管道的一端被关闭时
    • 读一个关闭写(写入管道)的管道,则读出全部数据,read返回0
    • 写一个关闭读(从管道读出)的管道产生信号SIGPIPE,write返回-1
  • PIPE_BUF: #bytes in kernel’s pipe buffer size,童超男要求写的内容小于它
3.用管道实现进程同步
  • 创建两个管道

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gUFia5Ro-1678841859548)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230221202000002.png)]

4.函数popen & pclose
  • 创建一个管道,fork一个子进程,关闭未使用的管道端,子进程执行shell命令,等待命令终止

    #include <stdio.h>
    FILE *popen(const char *cmdstring, const char *type);
    //Returns: file pointer if OK, NULL on error
    int pclose(FILE *fp);
    //Returns: termination status of cmdstring, or -1 on erro
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gNHGzI28-1678841859549)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230221202354742.png)]

5.FIFO
  • 命名管道,介意在不想管的进程上使用

    #include <sys/types.h>
    #include <sys/stat.h>
    int mkfifo (const char *pathname,mode_t mode);
    //Returns: 0 if OK, -1 on error
    // mode: same as for open function文件权限等信息结构
    
6.用 FIFOs 复制输出流

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jqpTSDk7-1678841859550)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230221203055464.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-paZNnbut-1678841859550)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230221203119306.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s2y3bzPV-1678841859550)(C:\Users\28636\AppData\Roaming\Typora\typora-user-images\image-20230221203134521.png)]

7.XSI IPC
  • 消息队列,信号量、共享内存

  • 当一个IPC结构创建时,一个key必须确定作为外部名,IPC标识符非负整数作为内部名

  • IPC结构

    struct ipc_perm {
    uid_t uid; /* owner’s EUID */
    gid_t gid; /* owner’s EGID */
    uid_t cuid; /* creator’s EUID */
    gid_t cgid; /* creator’s EGID */
    mode_t mode; /* access mode */
    ulong seq; /* slot usage seq number */
    key_t key; /* key */
    };
    

ead safety via TLS:只使用本地和TLS变量
4. Thread safety via TSD:所有数据保持一个单独的副本

第十五章 进程间通信

1.进程间通信IPC
  • IPC需要一些同步机制实现

[外链图片转存中…(img-blqnZPZs-1678841859546)]

[外链图片转存中…(img-sxP5NDfN-1678841859546)]

[外链图片转存中…(img-o3xFeAmW-1678841859547)]

2.管道
  • 最古老的形式,不命名管道

  • 半双工(数据只在一个方向流动)

  • 只能在具有公共祖先的进程之间使用

    #include <unistd.h>
    int pipe (int fieldes[2]);
    //Returns: 0 if OK, -1 on erro
    
    • filedes[0]: open for reading
    • filedes[1]: open for writing
    • Output of filedes[1] is input of filedes[0]数据从一到零

[外链图片转存中…(img-ODyIHQVm-1678841859547)]

单个进程的管道没有任何用户,通常进程会先pipe,再fork,创建父进程到子进程的IPC管道,数据流向取决于fork后的操作

  • 父进程到子进程的管道

    • 父进程f[1]=1,f[0]=0
    • 子进程f[1]=0,f[0]=1
  • 子进程到父进程的管道

    • 父进程f[1]=0,f[0]=1
    • 子进程f[1]=1,f[0]=0

[外链图片转存中…(img-k6gVMugE-1678841859547)]

  • 当管道的一端被关闭时
    • 读一个关闭写(写入管道)的管道,则读出全部数据,read返回0
    • 写一个关闭读(从管道读出)的管道产生信号SIGPIPE,write返回-1
  • PIPE_BUF: #bytes in kernel’s pipe buffer size,童超男要求写的内容小于它
3.用管道实现进程同步
  • 创建两个管道

[外链图片转存中…(img-gUFia5Ro-1678841859548)]

4.函数popen & pclose
  • 创建一个管道,fork一个子进程,关闭未使用的管道端,子进程执行shell命令,等待命令终止

    #include <stdio.h>
    FILE *popen(const char *cmdstring, const char *type);
    //Returns: file pointer if OK, NULL on error
    int pclose(FILE *fp);
    //Returns: termination status of cmdstring, or -1 on erro
    

    [外链图片转存中…(img-gNHGzI28-1678841859549)]

5.FIFO
  • 命名管道,介意在不想管的进程上使用

    #include <sys/types.h>
    #include <sys/stat.h>
    int mkfifo (const char *pathname,mode_t mode);
    //Returns: 0 if OK, -1 on error
    // mode: same as for open function文件权限等信息结构
    
6.用 FIFOs 复制输出流

[外链图片转存中…(img-jqpTSDk7-1678841859550)]

[外链图片转存中…(img-paZNnbut-1678841859550)]

[外链图片转存中…(img-s2y3bzPV-1678841859550)]

7.XSI IPC
  • 消息队列,信号量、共享内存

  • 当一个IPC结构创建时,一个key必须确定作为外部名,IPC标识符非负整数作为内部名

  • IPC结构

    struct ipc_perm {
    uid_t uid; /* owner’s EUID */
    gid_t gid; /* owner’s EGID */
    uid_t cuid; /* creator’s EUID */
    gid_t cgid; /* creator’s EGID */
    mode_t mode; /* access mode */
    ulong seq; /* slot usage seq number */
    key_t key; /* key */
    };
    
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值