文件描述符管理、进程间通信(管道)
进程间通信 管道 dup dup2 pipe
一,首先介绍一下两个重要函数,dup, dup2这两个重要的系统调用可以创建打开的文件描述符的拷贝。
#include <unistd.h>int dup(int oldfd);int dup2(int oldfd, int newfd);
dup返回最小的未使用的文件描述符, dup的这个规则,现在很容易将管道的文件描述符改为标准输入和标准输出了。
假定当前进程是一个shell,而它调用fork()产生两个子进程,从而创建一个两阶段管道,具体步骤如下:
1,使用pipe() 创建管道。必须首先做到这一点,以便主进程创建的两个子进程能够继承打开的文件描述符。
2,调用fork(),产生我们的“生产者进程”。该进程的标准输出流向管道。在该子进程中进行如下操作:
a, 因为生产者进程不需要管道的读取端所以使用 close(pipefd[0]) 关闭它。
b,使用close(STDOUT_FILENO),(即1)关闭最初的标准输出。
c,使用dup(pipefd[1])将管道的写入端改变为文件描述符1.
d,我们不需要打开的文件描述符的两份拷贝,所以使用close(pipefd[1])关闭一个。
(注意关闭未使用的管道文件描述符的重要性。因为直到最后一个打开的文件描述符关闭,文件才真正关闭,
即使多个进程共享文件描述符时也是如此。因为只有所有的写入端拷贝都被关闭,从管道读取数据 的进程才
会知道是否到达文件末尾,所以关闭未使用的文件描述符至关重要。)
e,调用exec()启动运行的程序。
3,调用fork()产生我们的“消费者进程“,该进程的标准输入来自于管道。该子进程中的和生产者的步骤相似,
具体细节如下:
a,因为右侧的子进程不需要管道的输入端所以使用close(pipefd[1]) 关闭它。
b,使用close(STDIN_FILENO)关闭最初的标准输入。STDIN_FILENO为 0
c,使用dup(pipefd[0])将管道的读取端(输入)改变为文件描述符0.
d,使用close(pipefd[0])关闭文件描述符的一个拷贝。
e,调用exec()启动运行的程序。
4,父进程关闭管道的两端close(pipefd[0]),close(pipefd[1])。
5,最后,在父进程中使用wait等待两个子进程的结束。
源码如下:
/* fork two processes into their own pipeline */#include <stdio.h>#include <errno.h>#include <sys/types.h>#include <sys/wait.h>#include <unistd.h>int pipefd[2];void producer(void);void consumer(void);/* main --- fork children, wait for them to finish */int main(int argc, char **argv){pid_t producer_pid, consumer_pid;pid_t ret;int status;if (pipe(pipefd) < 0) {/* create pipe, very first thing */perror("pipe");exit(1);}if ((producer_pid = fork()) < 0) {/* fork producer child */perror("fork");exit(1);} else if (producer_pid == 0)producer();if ((consumer_pid = fork()) < 0) {/* fork consumer child */perror("fork");exit(1);} else if (consumer_pid == 0)consumer();close(pipefd[0]);/* close parent's copy of pipe */close(pipefd[1]);while ((ret = wait(& status)) > 0) {/* wait for children */if (ret == producer_pid)printf("producer child terminated, status: %x\n", status);else if (ret == consumer_pid)printf("consumer child terminated, status: %x\n", status);elseprintf("yow! unknown child %d terminated, status %x\n",ret, status);}return 0;}/* producer --- do the work for the left child */void producer(void){static char *left_argv[] = { "echo", "hi", "there", NULL };close(pipefd[0]);close(1);dup(pipefd[1]);close(pipefd[1]);execvp("echo", left_argv);_exit(errno == ENOENT ? 127 : 126);}/* consumer --- do the work for the right child */void consumer(void){static char *right_argv[] = { "sed", "s/hi/hello/g", NULL };close(pipefd[1]);close(0);dup(pipefd[0]);close(pipefd[0]);execvp("sed", right_argv);_exit(errno == ENOENT ? 127 : 126);}
程序输出:wang@wang-OptiPlex-320:~/linux_programming$ ./pipeline
+ ./pipelineproducer
child terminated, status: 0
hello
thereconsumer child terminated, status: 0
wang@wang-OptiPlex-320:~/linux_programming$ ./pipeline
+ ./pipeline
hello there
consumer child terminated, status: 0
producer child terminated, status: 0
在编写多进程的代码时,应该小心避免对进程顺序的假定,尤其是调用wait函数系列的代码。
待续....