介绍linux进程通信的几种方式,以及每一种方式的适用场景。并给出了相应示例。
—— By Jihan
前言
需要一定linux的使用背景以及C语言基础。
什么是进程通信:
进程间通信(InterProcess Communication, IPC)是指在不同进程之间传播或交换信息。
IPC的方式通常有管道(包括无名管道和命名管道)、消息队列、信号量、共享内存、Socket、Streams等。其中 Socket和Streams支持不同主机上的两个进程IPC。本文只针对经典的IPC,即:管道、消息队列、信号量及共享内存。
进程通信
管道
我们常说的管道,都指匿名管道,FIFO通常会用命名管道一词
管道的使用会存以下两个限制:
- 管道的通信都是半双工的(即数据只能在一个方向流动)
- 管道只能在具有公共祖先的两个进程之间通信。通常一个管道进程
fork
出子进程后,管道就能在父子进程之间通信了。
管道创建函数:
1 |
|
fd[0]
为读打开,即输出,fd[1]
为写打开,即为输入。一个典型的工作场景:
即进程先调用pipe
,再进行fork
然后我们根据需要各自关闭父子进程的读/写通道。
示例
我们在程序中需要执行一个命令,并且获得命令输出的结果。比如我们要执行date
命令:
1 |
|
执行结果:
1 | $ ./main |
execv
函数参考
当然上述功能也可以用popen函数实现。更加简单。
适用场景
由于管道的限制,常常用于父进程和子进程之间的通信。
最为常见的应用就是shell
中使用的|
管道符了。
命名管道
命名管道(FIFO),相比于管道来说,他的限制只有一个:
- 通信都是半双工的(即数据只能在一个方向流动)
这就意味着它能用于不相干的进行之间进行数据通信。
FIFO本质上就是一种文件类型,可通过查看文件的stat
结构中的st_mode
编码得知。比如FIFO文件,我们执行stat file.name
会看到fifo
字样。
FIFO创建函数(有点类似文件创建):
1 |
|
mkfifo
函数与open
函数类似。
mkfifoat
函数则用来在fd
文件描述符表示的目录相关的位置创建一个FIFO
函数具体参数可参考man手册
我们也可以可以通过mkfifo
命令来创建FIFO文件
同样,对FIFO的读写操作,类似于对文件的读写。
当open
一个FIFO文件时,可指定是否阻塞:O_NONBLOCK
标记。
- 不指定:读/写都会阻塞,直到有对应的写/读到来
- 指定:如果读/写时没有对应的写/读,会返回相应的错误码
如果是多个进程进行写操作,为了防止写入数据交叉,需要设置操作原子性,PIPE_BUF
就是对应原子性数据的大小。
我们常常遇到的一种情况:
示例
我们创建两个子进程写入数据,父进程读出数据(当然不一定要父子进程):
1 |
|
执行结果:
1 | $ ./main |
也可以用命令行达到上面的效果:
1 | echo "hello1" >> fifo & |
注意,使用命令的时候,都是阻塞的,读写不需同时存在,否则会阻塞。
适用场景
- 数据缓冲,缓存来不及处理的数据。
- 时钟域隔离。
- 用于不同宽度的数据接口。
后两种可参考网上解释
消息队列
消息队列是消息的链接表,存储在内核中,有消息队列表示符标识。
意味着内核里有现成的消息队列,让你可直接使用。
msgget
则是创建或者打开一个现有队列。msgget详解
1 |
|
每个队列都包含一个msqid_ds
的结构:
1 | struct msqid_ds |
我们可以通过msgctl
函数获取或设置msqid_ds
操作
1 |
|
可以使用msgsnd
来添加消息到队列尾,使用msgrcv
获取消息,当然不一定要先入先出,也可以按照消息类型来。
1 |
|
同样可以通过设置__msgflg
来确定是阻塞还是非阻塞IO操作。
msgsnd
中的__msgp
可以是如下结构:
1 | struct mymesg { |
其中队列的最大消息数是根据最大队列数和最大数据量来决定的。可以通过msqid_ds
中的msg_qbytes
得知
示例
还是常见示例,做一个父子进程的消峰值处理:
1 |
|
适用场景
从速度上来说,消息队列
和unix sock
并没有太大差别,消息队列的主要用途:
- 应用解耦
- 异步消息
- 流量削锋
信号量
和之前的IPC
不同,信号量是一个计数器,用于为多个进程提供共享数据对象的访问。
示例
适用场景
共享内存
简介
示例
适用场景
各个进程通信比较
进程对于共享资源的访问互斥方式
- 信号量,就是标记共享资源还有多少可使用,使用了就-1。本质上就是存在于内核的一个计数器,可以通过
semget
系列函数来获取信号量。 - 使用记录锁,即创建一个空文件,并用改文件的第一个字节(不一定存在)作为锁字节。获取和释放资源时会进行写锁和释放锁。进程终止时,内核会自动释放该锁。
- 互斥量,需要将共享资源加载到内存,互斥量在文件的相同偏移出初始化互斥量。
区别和比较
性能:信号量和记录锁的性能差别不大,互斥量则有量级的性能提升。
这里通常使用的还是记录锁,信号量相比记录锁操作更为繁琐,而互斥量的稳定性和适配则不如记录锁。除非异常追求性能,不然通常都选择记录锁。