Linux Program进程间通信:管道
Posted Jiamings
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux Program进程间通信:管道相关的知识,希望对你有一定的参考价值。
系列文章:
- 文件操作
- 数据管理
- 进程和信号
- POSIX 线程
- 进程间通信:管道
- 信号量共享内存和消息队列
- 套接字
文章目录
之前我们可以使用信号在两个进程之间发送消息,但传送的信息只限于一个信号值。在本章,我们将介绍管道,通过它在进程之间可以交换更有用的数据。
- 管道的定义
- 进程管道
- 管道调用
- 父进程和子进程
- 命名管道:FIFO
- 客户/服务器架构
1. 什么是管道
当从一个进程连接数据流到另一个进程时,我们使用术语管道(pipe),通常是把一个进程的输出通过管道连接到另一个进程的输入。对于 shell 命令来说,命令的连接是通过管道字符来完成的:cmd1 | cmd2
,cmd1 的标准输入来自终端键盘、cmd1 的标准输出传递给 cmd2,作为它的标准输入、cmd2 的标准输出连接到终端屏幕。
shell 所做的工作实际上是对标准输入和标准输出流进行了重新连接,使数据流从键盘输入通过两个命令最终输出到屏幕上。
2. 进程管道
可能最简单的在两个程序之间传递数据的方法就是使用 popen 和 pclose 函数了,原型如下:
FILE* popen(const char* command, const char* open_mode);
int pclose(FILE* stream_to_close);
2.1 popen 函数
popen 函数允许一个程序将另一个程序作为新进程来启动,并可以传递数据给它或者通过它接收数据。command 字符串是要运行的程序名和相应的参数。open_mode 必须是 r 或者 w。
如果 open_mode 是 r,被调用程序的输出就可以被调用程序使用,调用程序利用 popen 函数返回的 FILE* 文件流指针,就可以通过常用的 stdio 库函数来读取被调用程序的输出。如果 open_mode 是 w,调用程序就可以用 fwrite 调用向被调用程序发送数据,而被调用程序可以在自己的标准输入流上读取这些数据,被调用的程序通常不会意识到自己正在从另一个进程读取数据,它只是在标准输入流上读取数据,然后做出相应的操作。
popen 函数在失败时返回一个空指针,如果想通过管道实现双向通信,最普通的解决方法是使用两个管道,每个管道负责一个方向的数据流。
2.2 pclose 函数
用popen启动的进程结束时,我们可以用pclose函数关闭与之关联的文件流。pclose调用只在popen启动的进程结束后才返回。如果调用 pclose 时它仍在运行,pclose 调用将等待该进程的结束。
pclose 调用的返回值通常是它所关闭的文件流所在进程的退出码。如果调用进程在调用 pclose 之前执行了一个 wait 语句,被调用进程的退出状态就会丢失,因为被调用进程已结束。此时,pclose 将返回 -1 并设置 errno 为 ECHILD。
读取外部程序的输出
int main()
FILE* read_fp;
char buffer[BUFSIZ + 1];
int chars_read;
memset(buffer, \\0, sizeof(buffer));
read_fp = popen("uname -a", "r");
if(read_fp != NULL)
chars_read = fread(buffer, sizeof(char), BUFSIZ, read_fp);
if(chars_read > 0)
printf("Output was: -\\n%s\\n", buffer);
pclose(read_fp);
exit(EXIT_SUCCESS);
exit(EXIT_FAILURE);
这个程序用 popen 调用启动带有 -a 选项的 uname 命令。然后用返回的文件流读取最多 BUFSIZ 个字符的数据,并将它们打印出来显示在屏幕上。
3. 将输出送往 popen
将输出送往外部程序
int main()
FILE* write_fp;
char buffer[BUFSIZ + 1];
sprintf(buffer, "Once upon a time, there was ...\\n");
write_fp = popen("od -c", "w");
if(write_fp != NULL)
fwrite(buffer, sizeof(char), strlen(buffer), write_fp);
pclose(write_fp);
exit(EXIT_SUCCESS);
exit(EXIT_FAILURE);
程序使用带有参数 w
的 popen 启动 od -c
命令,这样就可以向该命令发送数据了,使用下面的命令同样可以达到效果:echo "Once upon a time, there was ... | od -c"
。
3.1 传递更多的数据
有时,我们可能希望能以块方式发送数据,或者我们不知道输出数据的长度,为了避免定义一个非常大的缓冲区,我们用多个 fread 或 fwrite 调用来将数据分为几个部分处理。
从被调用的进程 ps -ax
中读取数据,该进程输出的数据有多少事先无法直到,所以我们必须对管道进行多次读取。
int main()
FILE* read_fp;
char buffer[BUFSIZ + 1];
int chars_read;
memset(buffer, \\0, sizeof(buffer));
read_fp = popen("ps ax", "r");
if(read_fp != NULL)
chars_read = fread(buffer, sizeof(char), BUFSIZ, read_fp);
while(chars_read > 0)
buffer[chars_read - 1] = \\0;
printf("Reading %d:-\\n%s\\n", BUFSIZ, buffer);
chars_read = fread(buffer, sizeof(char), BUFSIZ, read_fp);
pclose(read_fp);
exit(EXIT_SUCCESS);
exit(EXIT_FAILURE);
它连续从文件流中读取数据,直到没有数据可读为止,虽然 ps 命令的执行要花费一些时间,但 Linux 会安排好进程间的调度,让两个程序可以在可以运行时继续运行。如果读进程没有数据可读,它将会被挂起直到读进程读取了一些数据。
3.2 如何实现 popen
请求 popen 调用运行一个程序时,首先启动 shell,将 command 字符串作为一个参数传递给它。
好处是,可以借助 shell 完成参数扩展,即使用 popen 启动非常复杂的 shell 命令,使用 execl 创建进程就十分复杂,调用进程必须自己完成 shell 扩展。
坏处是,针对每个 popen 调用,不仅需要启动一个被请求的程序,还要启动一个 shell,即每个 popen 调用将多启动两个进程,popen 的调用成本较高,而且对目标命令的调用比正常方式慢一些。
对所有.c 源文件的总行数进行统计
int main()
FILE *read_fp;
char buffer[BUFSIZ + 1];
int chars_read;
memset(buffer, \\0, sizeof(buffer));
read_fp = popen("cat *.c | wc -l", "r"); // 在 popen 调用中启动了 shell、cat、wc 程序,并进行了一次输出重定向
if(read_fp != NULL)
chars_read = fread(buffer, sizeof(char), BUFSIZ, read_fp);
while(chars_read > 0)
buffer[chars_read - 1] = \\0;
printf("Reading:-\\n %s\\n", buffer);
chars_read = fread(buffer, sizeof(char), BUFSIZ, read_fp);
pclose(read_fp);
exit(EXIT_SUCCESS);
exit(EXIT_FAILURE);
jiaming@jiaming-pc:~/Documents/test$ ./a.out
Reading:-
262
4. pipe 调用
通过这个函数在两个程序之间传递数据,不需要启动一个 shell 来解释请求的命令,同时提供了对读写数据的更多的控制。
int pipe(int file_descriptor[2]);
pipe 函数的参数是一个由两个整数类型的文件描述符组成的数组的指针,该函数在数组中填上两个新的文件描述符后返回 0。
两个返回的文件描述符以一种特殊的方式连接起来,写到 file_descriptor[1]
的所有数据都可以从 file_descriptor[0]
读回来,数据基于 FIFO 进行处理。
这里使用的是文件描述符而非文件流,必须使用底层的 read 和 write 来访问数据,而非文件流库函数 fread 和 fwrite。
使用 pipe函数来创建一个管道
int main()
int data_processed;
int file_pipe[2];
const char some_data[] = "123";
char buffer[BUFSIZ + 1];
memset(Linux进程间通信——使用消息队列