Linux Program进程间通信:管道

Posted Jiamings

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux Program进程间通信:管道相关的知识,希望对你有一定的参考价值。

系列文章:

  • 文件操作
  • 数据管理
  • 进程和信号
  • POSIX 线程
  • 进程间通信:管道
  • 信号量共享内存和消息队列
  • 套接字


文章目录


之前我们可以使用信号在两个进程之间发送消息,但传送的信息只限于一个信号值。在本章,我们将介绍管道,通过它在进程之间可以交换更有用的数据。

  • 管道的定义
  • 进程管道
  • 管道调用
  • 父进程和子进程
  • 命名管道:FIFO
  • 客户/服务器架构

1. 什么是管道

当从一个进程连接数据流到另一个进程时,我们使用术语管道(pipe),通常是把一个进程的输出通过管道连接到另一个进程的输入。对于 shell 命令来说,命令的连接是通过管道字符来完成的:​​cmd1 | cmd2​​,cmd1 的标准输入来自终端键盘、cmd1 的标准输出传递给 cmd2,作为它的标准输入、cmd2 的标准输出连接到终端屏幕。

shell 所做的工作实际上是对标准输入和标准输出流进行了重新连接,使数据流从键盘输入通过两个命令最终输出到屏幕上。

2. 进程管道

可能最简单的在两个程序之间传递数据的方法就是使用 popen 和 pclose 函数了,原型如下:

#include <stdio.h>
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。

读取外部程序的输出

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

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

将输出送往外部程序

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

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​​ 中读取数据,该进程输出的数据有多少事先无法直到,所以我们必须对管道进行多次读取。

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

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 源文件的总行数进行统计

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

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 来解释请求的命令,同时提供了对读写数据的更多的控制。

#include <unistd.h>
int pipe(int file_descriptor[2]);

pipe 函数的参数是一个由两个整数类型的文件描述符组成的数组的指针,该函数在数组中填上两个新的文件描述符后返回 0。

两个返回的文件描述符以一种特殊的方式连接起来,写到 ​​file_descriptor[1]​​​ 的所有数据都可以从 ​​file_descriptor[0]​​ 读回来,数据基于 FIFO 进行处理。

这里使用的是文件描述符而非文件流,必须使用底层的 read 和 write 来访问数据,而非文件流库函数 fread 和 fwrite。

使用 pipe函数来创建一个管道

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main()

int data_processed;
int file_pipe[2];
const char some_data[] = "123";
char buffer[BUFSIZ + 1];

memset(Linux进程间通信——使用消息队列

Linux进程间通信——消息队列

[转]Linux进程间通信——使用消息队列

Linux Program套接字

Linux进程间通信 -- 消息队列 msgget()msgsend()msgrcv()msgctl()

Linux系统编程--进程间通信 ---管道篇