第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