特殊文件——管道
管道与重定向
概述
对于一些需要进程间的协作来解决问题的场景,进程间的通信是必要的。而最简单的UNIX进程通信机制就是管道,他是由特殊文件表示的。调用者可以通过文件描述符fd[0]和fd[1]来访问它,从fd[1]写入的数据可以按照先进先出的顺序从fd[0]中读出。
文件描述符:在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。在程序设计中,一些涉及底层的程序编写往往会围绕着文件描述符展开。但是文件描述符这一概念往往只适用于UNIX、Linux这样的操作系统。
我们要明白,每个Unix进程(除了可能的守护进程)应均有三个标准的POSIX文件描述符,对应于三个标准流:
整数值 | 名称 | <unistd.h>符号常量 | <stdio.h>文件流 |
---|---|---|---|
0 | Standard input | STDIN_FILENO | stdin |
1 | Standard output | STDOUT_FILENO | stdout |
2 | Standard error | STDERR_FILENO | stderr |
例如:$ls -l | head -5
中,|
就表示一个管道,ls
的标准输出被通过中间通信缓冲区被“连接”到了head
的标准输入中。
实现重定向
了解了文件描述符的概念后,我们就能利用dup2
函数来实现重定向。下面我们来用c语言模拟$ls -l | head -5
。
源代码
#include <errno.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main(int argc, char *argv[])
{
pid_t child;
int fd[2];
if ((pipe(fd) == -1) || ((child = fork()) == -1)) {
perror("Failed to fork.");
return 1;
}
if (child == 0) { /* 子进程 */
if (dup2(fd[1], STDOUT_FILENO) == -1)
perror("Failed to redirect stdout of ls");
else if ((close(fd[0]) == -1) || (close(fd[1]) == -1))
perror("Failed to close extra pipe descriptors on ls");
else {
execl("/bin/ls", "ls", "-l", NULL);
perror("Failed to execute ls");
}
}
/* 父进程 */
if (dup2(fd[0], STDIN_FILENO) == -1)
perror("Failed to redirect stdin of head");
else if ((close(fd[0]) == -1) || (close(fd[1]) == -1))
perror("Failed to close extra pipe descriptors on head");
else {
execl("/usr/bin/head", "head", "-5", NULL);
perror("Failed to execute head");
}
return 0;
}
过程图示
fork
函数在创建一个子进程时,子进程就会继承父进程的环境和上下文中的大部分内容的一份拷贝,包括信号状态
,调度参数
和文件描述符表
。由于子进程在被创建时会收到父进程文件描述符表的一份拷贝,因此对于在创建子进程前就打开的文件来书,父进程和子进程将共享同一个文件的偏移量。
调用fork
后,文件描述符表状态如下:
两个dup2
函数执行完毕后,文件描述符表的状态如下:
调用close
完毕后,文件描述符表的状态如下:
编译运行
$ gcc redirect_demo.c
$ ./a.out
total 4888
-rwxrwxr-x 1 chris chris 8576 3月 5 21:23 a.out
drwxrwx--- 10 chris chris 4096 3月 5 21:23 ccls
-rw-rw-r-- 1 chris chris 637 3月 5 21:23 coc-25627.vim
-rw-rw-r-- 1 chris chris 25829 3月 5 15:06 coc-ultisnips-c7dd5acb3d1574c377e86b60ee637c3c.py