翼度科技»论坛 云主机 LINUX 查看内容

linux环境编程(2): 使用pipe完成进程间通信

6

主题

6

帖子

18

积分

新手上路

Rank: 1

积分
18
1. 写在前面

linux系统内核为上层应用程序提供了多种进程间通信(IPC)的手段,适用于不同的场景,有些解决进程间数据传递的问题,另一些则解决进程间的同步问题。对于同样一种IPC机制,又有不同的API供应用程序使用,目前有POSIX IPC以及System V IPC可以为应用程序提供服务。后续的系列文章将逐一介绍消息队列,共享内存,信号量,socket,fifo等进程间通信方法,本篇文章主要总结了管道相关系统调用的使用方式。文中代码可以在这个代码仓库中获取,代码中使用了我自己实现的一个单元测试框架,对测试框架感兴趣的同学可以参考上一篇文章
2. pipe介绍

在linux环境进行日常开发时,管道是一种经常用到的进程间通信方法。在shell环境下,'|'就是连接两个进程的管道,它可以把一个进程的标准输出通过管道写到另一个进程的标准输入,利用管道以及重定向,各种命令行工具经过组合之后可以实现一个及其复杂的功能,这也是继承自UNIX的一种编程哲学。
除了在shell脚本中使用管道,另一种方式是通过系统调用去操作管道。使用pipe或者pipe2创建管道,得到两个文件描述符,分别是管道的读端和写端,有了文件描述符,进程就可以像读写普通文件一样对管道进行readwrite操作,操作完成之后调用close关闭管道的两个文件描述符即可。可以看到,当完成创建之后,管道的使用和普通文件相比没有什么区别。
管道有两个特点: 1) 通常只能在有亲缘关系的进程之间进行通信; 2) 阅后即焚;有亲缘关系是指,通信的两个进程可以是父子进程或者兄弟进程,这里的父子和兄弟是一个广义的概念, 子进程可以是父进程调用了多次fork创建出来的,而不仅局限在只经过一次fork,总之,只要通信双方的进程拿到了管道的文件描述符就可以使用管道了。说”阅后即焚“是因为管道中的数据在被进程读取之后就会被管道清除掉。有一个形象的比喻说,管道就像某个进程家族各个成员之间传递情报的中转站,情报内容阅后即焚。
3. pipe的基本使用

在使用管道时,需要注意管道中数据的流动方向,通常都是把管道作为一个单向的数据通道使用的。虽然通信双方可以都持有管道的读端和写端,然后使用同一个管道实现双向通信,但这种方式实际上很少使用。下面通过几段代码说明几种使用管道的方法:
3.1 自言自语

管道虽然时进程间通信的一种手段,但一个进程自言自语也是可以的,内核并没有限制管道的两端必须由不同的进程操作。下面的代码展示了一个孤独的进程怎样通过管道自言自语,代码中使用了自己实现的测试框架cutest。执行之后它将从管道的另一头收到前一个时刻发给自己的消息。
  1. CUTEST_CASE(basic_pipe, talking_to_myself) {
  2.     int pipefd[2];
  3.     pipe(pipefd);
  4.     const char *msg = "I'm talking to myself";
  5.     write(pipefd[1], msg, strlen(msg));
  6.     char buf[32];
  7.     read(pipefd[0], buf, 32);
  8.     printf("talking_to_myself: %s\n", buf);
  9.     close(pipefd[0]);
  10.     close(pipefd[1]);
  11. }
复制代码
3.2 父进程向子进程传递数据

自言自语始终是太过无聊,是时候让父子进程之间聊点什么了。因为fork之后的子进程会继承父进程的文件描述符,fork之前父进程向管道写入的数据,子进程可以在管道的另一端读到。
  1. CUTEST_CASE(basic_pipe, parent2child) {
  2.     int pipefd[2];
  3.     pipe(pipefd);
  4.     const char *msg = "parent write, child read";
  5.     write(pipefd[1], msg, strlen(msg));
  6.     if (fork() == 0) {
  7.         close(pipefd[1]);
  8.         char buf[64];
  9.         memset(buf, 0, 64);
  10.         read(pipefd[0], buf, 64);
  11.         printf("parent2child: %s\n", buf);
  12.         exit(0);
  13.     }
  14.     close(pipefd[0]);
  15.     close(pipefd[1]);
  16. }
复制代码
3.2 自进程向父进程传递数据

管道的方向是由通信双方操作的文件描述符决定的,子进程同样可以传递消息给父进程。
  1. CUTEST_CASE(basic_pipe, child2parent) {
  2.     int pipefd[2];
  3.     pipe(pipefd);
  4.     if (fork() == 0) {
  5.         close(pipefd[0]);
  6.         const char *msg = "parent read, child write";
  7.         write(pipefd[1], msg, strlen(msg));
  8.         close(pipefd[1]);
  9.         exit(0);
  10.     }
  11.     close(pipefd[1]);
  12.     char buf[64];
  13.     memset(buf, 0, 64);
  14.     read(pipefd[0], buf, 64);
  15.     printf("child2parent: %s\n", buf);
  16.     close(pipefd[0]);
  17. }
复制代码
3.3 父进程向多个子进程传递数据

当有多个子进程时,只要它们持有了管道的文件描述符,就可以利用管道通信,把父进程写进管道的数据读取出来。当然,在具体的应用中需要考虑子进程的读取顺序等因素,下面的例子只是简单的创建了多个子进程,每个进程读取一个int类型的数据,开始阶段由父进程向管道写入数据,需要说明一点,三个子进程并没有将管道内的数据都读完,当所有引用了这个管道的文件描述符都关闭了之后,内核也会在适当的时机销毁自己维护的管道。
[code] void fork_child_read(int id, int pipefd[2], const char *msg_pregix) {     if (fork() == 0) {         close(pipefd[1]);         int n;         read(pipefd[0], &n, sizeof(int));         printf("%s: child %d get data %d\n", msg_pregix, id, n);         close(pipefd[0]);         exit(0);     } }  CUTEST_CASE(basic_pipe, parent2children) {     int pipefd[2];     pipe(pipefd);      for (int i = 1; i

举报 回复 使用道具