写点什么

Android C++ 系列:Linux 进程间通信 (一)

作者:轻口味
  • 2021 年 11 月 24 日
  • 本文字数:1942 字

    阅读完需:约 6 分钟

每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不 到,所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程 1 把数据从用 户空间拷到内核缓冲区,进程 2 再从内核缓冲区把数据读走,内核提供的这种机制称为进程 间通信(IPC,InterProcess Communication)。


pipe 管道

管道是一种最基本的 IPC 机制,由 pipe 函数创建:


#include <unistd.h>int pipe(int filedes[2]);
复制代码


管道作用于有血缘关系的进程之间,通过 fork 来传递


调用 pipe 函数时在内核中开辟一块缓冲区(称为管道)用于通信,它有一个读端一个 写端,然后通过 filedes 参数传出给用户程序两个文件描述符,filedes[0]指向管道的读 端,filedes[1]指向管道的写端(很好记,就像 0 是标准输入 1 是标准输出一样)。所以管道 在用户程序看起来就像一个打开的文件,通过 read(filedes[0]);或者 write(filedes[1]);向这个文件读写数据其实是在读写内核缓冲区。pipe 函数调用成功返回 0,调用失败返 回-1。开辟了管道之后如何实现两个进程间的通信呢?比如可以按下面的步骤通信。



1.父进程调用 pipe 开辟管道,得到两个文件描述符指向管道的两端。


2.父进程调用 fork 创建子进程,那么子进程也有两个文件描述符指向同一管道。


3.父进程关闭管道读端,子进程关闭管道写端。父进程可以往管道里写,子进程可以从管道里读,管道是用环形队列实现的,数据从写端流入从读端流出,这样就实现了进程间通信。


例 pipe 管道


#include <stdlib.h> #include <unistd.h> #define MAXLINE 80int main(void) {  int n;  int fd[2];  pid_t pid;  char line[MAXLINE];  if (pipe(fd) < 0) {     perror("pipe");    exit(1);   }  if ((pid = fork()) < 0) {    perror("fork");    exit(1);   }  if (pid > 0) { /* parent */     close(fd[0]);    write(fd[1], "hello world\n", 12);    wait(NULL);  } else { /* child */    close(fd[1]);    n = read(fd[0], line, MAXLINE);     write(STDOUT_FILENO, line, n);  }  return 0; }
复制代码


使用管道有一些限制:


两个进程通过一个管道只能实现单向通信,比如上面的例子,父进程写子进程读,如果有时候也需要子进程写父进程读,就必须另开一个管道。请读者思考,如果只开一个管道,但是父进程不关闭读端,子进程也不关闭写端,双方都有读端和写端,为什么不能实现双向通信?


管道的读写端通过打开的文件描述符来传递,因此要通信的两个进程必须从它们的公共 祖先那里继承管道文件描述符。上面的例子是父进程把文件描述符传给子进程之后父子进程 之间通信,也可以父进程 fork 两次,把文件描述符传给两个子进程,然后两个子进程之间通 信,总之需要通过 fork 传递文件描述符使两个进程都能访问同一管道,它们才能通信。


使用管道需要注意以下 4 种特殊情况(假设都是阻塞 I/O 操作,没有设置 O_NONBLOCK 标 志):


  1. 如果所有指向管道写端的文件描述符都关闭了(管道写端的引用计数等于 0),而仍 然有进程从管道的读端读数据,那么管道中剩余的数据都被读取后,再次 read 会返回 0,就 像读到文件末尾一样。

  2. 如果有指向管道写端的文件描述符没关闭(管道写端的引用计数大于 0),而持有管 道写端的进程也没有向管道中写数据,这时有进程从管道读端读数据,那么管道中剩余的数 据都被读取后,再次 read 会阻塞,直到管道中有数据可读了才读取数据并返回。

  3. 如果所有指向管道读端的文件描述符都关闭了(管道读端的引用计数等于 0),这时 有进程向管道的写端 write,那么该进程会收到信号 SIGPIPE,通常会导致进程异常终止。讲 信号时会讲到怎样使 SIGPIPE 信号不终止进程。

  4. 如果有指向管道读端的文件描述符没关闭(管道读端的引用计数大于 0),而持有管 道读端的进程也没有从管道中读数据,这时有进程向管道写端写数据,那么在管道被写满时 再次 write 会阻塞,直到管道中有空位置了才写入数据并返回。


管道的这四种特殊情况具有普遍意义。


  • 非阻塞管道, fcntl 函数设置 O_NONBLOCK 标志

  • fpathconf(int fd, int name)测试管道缓冲区大小,_PC_PIPE_BUF

fifo 有名管道

创建一个有名管道,解决无血缘关系的进程通信, fifo:



qingkouwei@ubuntu:~$ mkfifo xwpqingkouwei@ubuntu:~$ ls -l xwpprw-rw-r-- 1 qingkouwei qingkouwei 0 9月 15 18:34 xwp
复制代码


mkfifo 既有命令也有函数


#include <sys/types.h> #include <sys/stat.h>int mkfifo(const char *pathname, mode_t mode);
复制代码


  • 当只写打开 FIFO 管道时,如果没有 FIFO 没有读端打开,则 open 写打开会阻塞。

  • FIFO 内核实现时可以支持双向通信。(pipe 单向通信,因为父子进程共享同一个 file 结构体)

  • FIFO 可以一个读端,多个写端;也可以一个写端,多个读端。(请测试)

总结

本文介绍了 Linux 进程通信的概念:解决任何一个进程的全局变量在另一个进程中都看不到的问题,以及最常用的进程同行机制:管道。

发布于: 14 小时前阅读数: 5
用户头像

轻口味

关注

🏆2021年InfoQ写作平台-签约作者 🏆 2017.10.17 加入

Android、音视频、AI相关领域从业者。 邮箱:qingkouwei@gmail.com

评论

发布
暂无评论
Android C++系列:Linux进程间通信(一)