管道是linux中一个很重要的命令行操作,因为linux的每个命令都以完成一个任务为目的,只有把些命令组合起来才能做复杂的工作,当然这种方式也提供了很大的灵活性。那么bash中的管道是通过什么实现的呢? 先看一个简单的管道的例子:
#include unistd.h>
int fd[2];
void run_ls()
{
dup2(fd[1],1);
close(fd[0]);
close(fd[1]);
execve("/bin/ls",NULL,NULL);
}
void run_wc()
{
dup2(fd[0],0);
close(fd[0]);
close(fd[1]);
execve("/usr/bin/wc",NULL,NULL);
}
int main()
{
pipe(fd);
if(fork()==0)
run_ls();
else
run_wc();
return 0;
}
这个程序的执行结果等效于ls|wc,从代码上看,主程序调用pipe来创建一个管道,如下图:
对fd[1]进行写操作时会把数据写到内核中的pipe对应的buffer中,而对fd[0]进行读操作时,就是从pipe的buf中读取数据,这样就把fd[0]和fd[1]连接成一个"管道".
继续看main函数中的代码,fork调用创建出子进程,子进程会继承父进程的文件描述符表,创建子进程后管道的状态是:
两个进程中的fd[0],fd[1]的值是相同的,它们所指向的内核中的表示文件的结构体也是相的。先不看dup2调用,在子进程关闭fd[1],父进程关闭fd[0]后:
这样在子进程和父进程之间就建立起一个管道,子进程向fd[1]写的数据,父进程可以通过读fd[0]读出。再看dup2调用,父进程中又把标准输入对应
的文件结构体复制成了fd[0]对应的,子进程中把标准输出对应的文件结构体复制成了fd[1]对应的,这个结构体中包含了对文件的操作函数。这样子进程
中写0号文件描述符的数据实际就会写到内核中的pipe的buf中,而父进程从1这个文件描述符对应的文件读取数据,现在1已经不再指向标准输出,而是指
向了pipe的buf。
虽然这个例子很简单,但已经可以说明bash中管道的原理。从上面的分析我们可以看出,linux中创建进程是从父进程"fork"出来,然后再
execve,而不是在创建时就指定它要运行的函数,完成独立地创建,这样天然的进程的继承关系,为管道的实现提供了很大的方便,因为管道的实现利用了子
进程继承父进程的文件描述符表这一特性。
阅读(877) | 评论(0) | 转发(1) |