1.管道的特点
1.一个管道是一个字节流
当讲到管道是一个字节流时,意味着在使用管道时是不存在消息或者消息边界的概念的。从管道中可以读取任意大小的数据块,
通过管道传递的数据是顺序的,在管道中无法使用 lseek() 来随机访问数据.
2.从管道中读取数据
当试图从一个当前为空的管道中读取数据将会被阻塞直到至少有一个字节被写入到管道中为止。
3.管道是单向的
在管道中的数据传递方向是单向的。
4.可以确保写入不超过 PIPE_BUF 字节的操作是原子的
如果多个进程写入同一个管道,那么如果它们在一个时刻写入的数据量不超过 PIPE_BUF 字节,那么就可以确保写入的数据不会发生互相混合的情况。
当写入管道的数据块大小超过了 PIPE_BUF 字节,那么内核可能会将数据分割成几个较小的数据块来传输,在读者从管道中消耗数据时再附加上后续的
数据。当只有一个进程向管道写入数据时,PIPE_BUF 的取值就没有关系了。但如果有多个进程写入,那么大数据块的写入可能被分割成任意大小的段,并
可能出现于其他进程的写入的数据混合的情况。
5.管道的容量是有限的
其实管道是一个在内核维护的缓冲器。这个缓冲器的能力是有限的。一旦管道被填满之后,后续向该管道的写入操作就会被阻塞直到读者从管道中移除一些数据
为止。
使用较大的缓冲器的原因是效率:每当写着充满管道时,内核必须执行一个上下文切换以允许读者被调度来消耗管道中的一些数据。使用较大的缓冲器意味着
需要执行的上下文切换次数更少。
2.创建和使用管道
1.管道允许相关进程间的通信
管道允许任意2个或者多个相关进程之间的通信,只要在创建子进程的系列 fork() 调用之前通过一个共同的祖先进程创建管道即可。
2.关闭未使用的管道描述符
1.关闭未使用管道文件描述符不仅仅是为了确保进程不会消耗其文件描述符的限制。
2.从管道中读取数据的进程会关闭其持有的管道的写入描述符,这样当其他进程完成输出并关闭其写入描述符之后,读者就能够看到文件结束。
如果读取进程没有关闭管道的写入端,那么在其他进程关闭了写描述符之后,读者也不会看到文件结束,即使它读完了管道中的所有数据。相反,
read()将会阻塞以等待数据,这是因为内核知道至少还有一个管道的写入描述符打开着,即读取进程自己打开的这个描述符。
3.写入进程关闭其持有的管道的读描述符是出于不同的原因。当一个进程试图向一个管道中写入数据但没有任何进程拥有该管道的打开着的读取描述符时,
内核会向该写入进程发送一个 SIGPIPE 信号。在默认情况下,这个信号会杀死一个进程。但进程可以捕获或者忽略这个信号,这样就会导致一个 write()
操作因为 EPIPE 错误而失败。收到 SIGPIPE 信号或者得到 EPIPE 错误对于提示管道的状态是有用的,这就是为何需要关闭管道的未使用读取描述符的原因。
如果写入进程没有关闭管道的读取端,那么即使在其他进程已经关闭了管道的读取端之后写入进程仍然能够向管道写入数据,最后写入进程会将数据充满整个管道,
后续的写入请求会被永远阻塞。
4.关闭未使用描述符的最后一个原因是只有当所有进程中所引用一个管道的文件描述符被关闭之后才会销毁该管道以及释放该管道占用的资源以供其他进程复用。
此时,管道中所有未读取的数据都会丢失。
3.将管道作为进程同步的一个方法
4.使用管道作为连接过滤器
5.通过管道与 shell 命令进行通信: popen()
popen() 创建了一个管道,然后创建了一个子进程来执行 shell,而 shell 又创建了一个子进程来执行 command 字符串。
pclose() IO 结束之后,可以调用 pclose()函数关闭管道并等待子进程中的 shell 终止(不应该使用 fclose() 函数,因为它不会等待子进程)。pclose()在成功时
会返回子进程中的 shell 的终止状态(即 shell 所执行的最后一条命令的终止状态,除非 shell 是被信号所杀)。与 system() 一样,如果无法执行 shell, 那么pclose()
会返回一个值就像是子进程中的 shell 通过 _exit(127)来终止一样。如果发生了其他错误,那么返回 -1.
popen()更加方便一些,它会构建管道,执行描述符复制,关闭未使用的描述符并帮助开发人员处理 fork()和 exec() 的所有细节。此外,shell 处理针对的是命令。这种便捷
牺牲的是效率。因为至少需要创建2个额外的进程:一种用于 shell,一个或多个用于 shell 执行的命令。与 system() 一样,在特权进程中永远都不应该使用 popen().
6.管道和 stdio 缓冲
由于 popen()调用返回的文件流指针没有引用一个终端,因此 stdio 库会对这种文件流应用块缓冲。
可以使用一个伪终端来替换管道。一个伪终端是一个 IPC 通道,对进程来说它就像一个终端。其结果是 stdio 库会逐行输出缓冲器中的数据。
7.FIFO
从语义上来说,FIFO与管道类似,它们两之间最大的差别在于 FIFO 在文件系统中拥有一个名称,并且其打开方式与打开一个普通文件是一样的。这样就能够将 FIFO 用于非相关
进程之间的通信。FIFO 有时也被称为 命名管道。
mkfifo
使用 FIFO 和 tee(1) 创建双重管道线:
shell 管道线的其中一个特征是它们是线性的,管道线中的每个进程都读取前一个进程产生的数据并将数据发送到其后的一个进程中。使用 FIFO 就能够在管道线中创建子进程,这样
除了将一个进程的输出发送给管道线中的后面一个进程之外,还可以复制进程的输出并将数据发送到另外一个进程。要完成这个任务需要使用 tee 命令,它将其从标准输入中读取到的数据
复制2份并输出:一份写到标准输出,另外一份通过命令参数指定的文件中。
pipe();
pipe2();
popen();
pclose();
mkfifo();
mknod();