xv6
第1.3节 管道
这一节中涉及的系统调用有:
pipe
fork
close
dup
read
一个管道就是一个小型的内核缓冲区,将一对文件描述符暴露给进程:一个用于读,另一个用于写。向管道一端写入的数据可用于另一端读取。
管道提供了一种进程通信的方式。
wc
程序的标准输入连接到管道的读取端
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);
} else {
close(p[0]);
write(p[1], "hello world\n", 12);
close(p[1]);
}
解释:
- 这个程序调用了系统调用
pipe
,创建了一个新的管道,使用数组p
来记录读文件描述符和写文件描述符。 - 在执行完
fork
后,父进程和子进程都持有了指向管道的文件描述符。 - 子进程调用了
close
和dup
使得文件描述符0指向了管道的读取端,关闭关闭在数组p中的文件描述符,调用exec
来运行程序wc
。 - 当程序
wc
从标准输入读取数据时,实际上是从管道中读取数据。 - 父进程关闭管道的读取端,向管道中写入数据,然后关闭管道的写入端。
如果没有数据可用,则对管道发起的read
调用要么等着写入数据,要么等着所有引用该写入端的文件描述符关闭。
如果是等着所有引用管道写入端的文件描述符关闭,则read
调用就返回0,好像到达文件的末尾一样。
在执行程序wc
之前,子进程必须要关闭管道的写入端,理由是read
会一直阻塞直到不可能有新数据到达为止。
如果程序wc
的文件描述符有一个引用指向管道的写入端,则程序wc
会永远看不到输入。
xv6实现诸如grep fork sh.c | wc -l
这样的流水线的方式跟前面的代码类似,比如:
case PIPE:
pcmd = (struct pipecmd*)cmd;
if(pipe(p) < 0)
panic("pipe");
if(fork1() == 0){
close(1);
dup(p[1]);
close(p[0]);
close(p[1]);
runcmd(pcmd->left);
}
if(fork1() == 0){
close(0);
dup(p[0]);
close(p[0]);
close(p[1]);
runcmd(pcmd->right);
}
close(p[0]);
close(p[1]);
wait(0);
wait(0);
break;
子进程创建了一个管道连接到一个流水线的右端。
然后,子进程为流水线的左端调用了fork
和runcmd
,为流水线的右端调用了fork
和runcmd
,并等待两者的完成。
流水线的右端可能是一个命令,该命令自身就包含了一个管道,比如a|b|c
,该管道自身派生了两个新进程,一个用于b
,另一个用于c
。
因此,shell可能创建一个进程树。树的叶子是命令,内部节点是等待着左右孩子完成的进程。
从理论上讲,可以让内部节点在流水线的左端运行,但是这样会使得流水线的实现变得复杂。
比如,
假设仅做如下修改:修改sh.c
不派生子进程用于p>left
,而是在内部进程中运行runcmd(p->left)
。然后,比如echo hi | wc
将不会产生输出,因为echo hi
是以runcmd
的方式退出的,内部进程退出了,永远不会调用fork
派生子进程来运行管道的右端。
这种错误的行为可通过为了内部进程不以rumcmd
的方式调用exit
来修正,但是这种修复会使代码变得复杂,因为此时runcmd
需要判断其是否是一个内部进程。
当没有派生子进程来runcmd(p->right)
时,也会增加复杂性。比如,仅做刚才的修改,sleep 10 | echo hi
将会立即输出hi
,而不是10秒钟以后输出,因为echo
立即运行并退出,并没有等待sleep
完成。
由于sh.c
的目标是尽可能地简单,所以它不会避免创建内部进程。
注:
管道看起来似乎并没有临时文件功能强大,比如流水线
echo hello world | wc
可被实现成没有管道的形式:
echo hello world >/tmp/xyz; wc </tmp/xyz
管道跟临时文件相比,至少有4点优势:
- 管道会自动的清理数据。使用了文件重定向,shell要仔细地移除
/tmp/xyz
; - 管道可传递任意长度的数据流,而文件重定向要求磁盘上有足够的空闲空间来存储所有的数据;
- 管道允许并行流水线的状态并行执行,但是文件重定向要求:在第二个程序完成前,第一个程序必须完成;
- 如果正在实现进程间通信,则管道的阻塞读和阻塞写回被文件的非阻塞语义更有效。
网友评论