第19章 进程通信
1 管道
1.1 特点
- 生产者/消费者模型
- 半双工:双向,每次都能朝一个方向传输数据
- 管道作为一组VFS对象,可以使用VFS通用结构访问(比如读写);管道被组织为pipefs这种特殊文件系统,在系统目录树没有安装点,用户看不到它们
- 管道创建后,返回一对文件描述符,分别用来读和写;然后通过fork()系统调用把这对描述符传给子进程,实现父子进程的管道通信
- 管道创建后,可以被任意进程使用,但是因为其半双工工作模式,如果有多个进程对其访问,必须加锁
- 管道创建的系统调用为pipe(),C函数库中,popen()和pclose()封装了管道相关的系统调用,更加方便
1.2 典型应用
ls | grep xxx
“|”可以创建管道;第一个子进程ls的标准输出被“|”重定向管道中;第二个子进程grep从管道读取数据作为输入,以替代标准输入,完成grep操作;grep的输出默认为标准输出。
1.3 管道数据结构
管道也是文件,可以通过VFS系统调用来访问管道,因此对于每个管道,应该有:一个索引节点对象 + 两个文件对象(读写);
如果一个索引节点表示管道,其i_pipe成员指向struct pipe_inode_info结构:
- struct wait_queue *wait:管道/FIFO 等待队列,里面是等待读写的进程
- struct pipe_buffer[] bufs:管道缓冲区描述符数组,里面包含写入管道待读出的数据
- unsigned int curbuf:待读数据所在的缓冲区索引
- unsigned nrbufs:包含待读数据的缓冲区数量,curbuf + nrbufs得到首个可写缓冲区
1.4 从管道中读数据
从一个管道中读取n个字节(阻塞读)
| 管道大小p | 有写进程 | 无写进程 |
| p = 0 | 等待数据直到拷贝n个字节,返回n | 返回0 |
| 0 < p < n | 等待数据直到拷贝n个字节,返回n | 拷贝p个字节并返回p,管道为空 |
| p >= n | 拷贝n个数据并返回n,管道还剩p-n个字节 | 拷贝n个数据并返回n,管道还剩p-n个字节 |
对于阻塞读,如果管道为空 && 没有写进程,会返回已经拷贝的字节数,不会等待;
对于非阻塞读,立刻拷贝并返回结果(拷贝的字节数),并不会关心有无写进程。
1.5 向管道中写数据
把n个字节写入管道(阻塞写)
| 可用缓冲区u | 有读进程 | 无读进程 |
| u <= n <= 4096 | 等待读,直到有n-u个字节被释放,才写入n个字节,返回n | 发送SIGPIPE并返回-EPIPE |
| u >= n | 写入n个字节,返回n | 写入n个字节,返回n |
