# 1.3: Pipes ## 简要介绍 管道是一种用于进程间通信的小型内核缓冲区。它由一对文件描述符表示——一个用于读取,一个用于写入。向管道的一端写入数据后,这些数据会从管道的另一端供读取使用。 ## 主要特性 - 管道为进程之间提供了一种简单的通信机制。 - 如果没有数据可读,管道的读取操作会阻塞,直到发生以下情况之一: 1. 管道中有数据被写入。 2. 管道写端的所有文件描述符都已关闭,此时读取操作会返回 0,就像到达文件末尾一样 通过管道连接子进程和 wc 程序 以下代码示例展示了如何创建一个管道,并运行wc程序,其中标准输入连接到了管道的读取端。 ```c int p[2]; char *argv[2]; argv[0] = "wc"; argv[1] = 0; pipe(p); if (fork() == 0) { close(0); // 关闭标准输入 dup(p[0]); // 将管道的读取端复制为标准输入 close(p[0]); // 关闭管道读取端(原描述符) close(p[1]); // 关闭管道写入端 exec("/bin/wc", argv); // 执行 wc 程序 } else { close(p[0]); // 父进程关闭管道读取端 write(p[1], "hello world\n", 12); // 向管道写入数据 close(p[1]); // 关闭管道写入端 } ``` 解析 pipe(p):创建一个管道,返回的文件描述符存储在数组 p 中。 - p[0]:管道的读取端。 - p[1]:管道的写入端。 子进程: - 通过 dup(p[0]) 将标准输入重定向到管道的读取端。 - 关闭管道的原始文件描述符,避免影响后续的 read 和 write。 - 使用 exec 执行 wc 程序。 父进程: - 关闭管道的读取端,避免干扰。 - 向管道写入数据,供子进程读取。 - 写入完成后,关闭管道的写入端 ## 阻塞行为与结束标志 - 当管道的写端关闭后,read 操作会返回 0,表示数据已读完。 - 子进程在执行 wc 前必须关闭管道的写入端,以确保 wc 能正确检测到文件结束符 (EOF)。 ## 管道与 xv6 Shell 的实现 xv6 shell 以类似的方式实现了管道,比如 grep fork sh.c | wc -l(见 user/sh.c:101)。子进程创建一个管道,将管道左端和右端连接起来。然后调用 fork 和 runcmd 分别运行管道左端和右端,最后等待两者完成。管道右端可能是一个包含管道的命令(例如 a | b | c),此时 shell 将再创建两个新子进程(一个用于 b,一个用于 c)。因此,shell 可能会创建一个进程树。该树的叶节点是命令,内部节点则是等待左右子进程完成的进程。 管道看起来并不比临时文件更强大:例如,管道操作 ```bash echo hello world | wc ``` 可以通过临时文件实现为: ```bash echo hello world >/tmp/xyz; wc