谈谈Linux中的进程通信

1,059 阅读7分钟

本文正在参与 “走过Linux 三十年”话题征文活动。

写在前面

本文将讲解linux下一些进程之间相互通信的技术--进程间通信。一起了解一下linux进程通信技术吧!

一、信号

信号理解起来比较难,它的作用是通知接收进程某个事件已经发生。主要作为进程间以及同一进程不同线程之间的同步手段。信号是在软件层次上对中断机制的一种模拟,并且还是Linux进程间异步通信的唯一机制。

信号的种类

可分为可靠信号与不可靠信号, 实时信号与非实时信号。 不可靠信号:信号值小于SIGRTMIN(SIGRTMIN=32,SIGRTMAX=63)的信号都沿用了Unix的实现方式,因为会丢失,所以称为不可靠信号。不可靠信号的处理机制类似于中断,同一个信号同时发生多次时,会合并为一个信号,其它都会丢失。不可靠信号都是有预定值的,每个信号都有确定的用途及含义,并且每个信号都有各自缺省的动作。

可靠信号:Linux还支持改进后的可靠信号,新增的32种新的信号, 这些信号都是可靠信号, 表现在信号支持排队, 不会丢失, 发多少次, 就可以收到多少次. 信号值位于 [SIGRTMIN, SIGRTMAX] 区间的都是可靠信号。

常用函数

信号的安装函数有两个:signal()与sigaction()。signal()有两个参数, signum指定要安装的信号, handler指定信号的处理函数,该函数的返回值是一个函数指针, 指向上次安装的handler,signal()常用于非实时信号。sigaction()常用于实时信号,它有更多的选项设置,最重要的是可以为实时信号安装带参数的回调。

信号的发送函数有6个:raise()函数是对kill的简单封装,只能给自己发送信号。abort()是对SIGABRT信号的封装,可用kill()函数代替。alarm()专为SIGALRM设计,特点是信号的延迟发送,可以理解为定时器+信号。setitmer()是更复杂的定时器,不仅实现了非周期定时器与周期定时器,还有更多的定时器类型。kill()与sigqueue()是最基本的信号发送函数,kill()用于发送不带参数的信号,sigqueue()用于发送带参数的信号;

屏蔽信号

屏蔽信号是暂时阻塞信号的递送, 解除屏蔽后, 信号将被递送, 不会丢失。 相关函数有:sigemptyset(),sigismember()等,这里重点讲一下sigprocmask()。sigprocmask()函数能够根据参数来实现对信号集的操作,具体如下:

SIG_SETMASK 更新进程阻塞信号集为set指向的信号集屏蔽整个进程的信号;

SIG_BLOCK 在进程当前阻塞信号集中添加set指向信号集中的信号;

SIG_UNBLOCK 如果进程阻塞信号集中包含set指向信号集中的信号,则解除对该信号的阻塞;

二、管道

管道是最早的Unix IPC形式之一,分为有名管道和无名管道

无名管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有父子关系的进程间使用.进程的亲缘关系一般指的是父子关系。无名管道一般用于两个不同进程之间的通信。当一个进程创建了一个管道,并调用fork创建自己的一个子进程后,父进程关闭读管道端,子进程关闭写管道端,这样提供了两个进程之间数据流动的一种方式。有名管道也是一种半双工的通信方式,但是它允许无亲缘关系进程间的通信。

管道具有以下特点: 只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程);

单独构成一种独立的文件系统:管道对于管道两端的进程而言,就是一个文件,但它不是普通的文件,它不属于某种文件系统,而是自立门户,单独构成一种文件系统,并且只存在与内存中。

数据的读出和写入:一个进程向管道中写的内容被管道另一端的进程读出。写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据。

涉及的pipe函数:

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

返回的fd[0]为读,fd[1]为写。因此,一个进程在由pipe()创建管道后,通常会fork一个子进程,然后通过管道实现父子进程间的通信。

三、FIFO

FIFO又叫命名管道,特点是不相关的进程也能够进行数据交换。FIFO提供一个路径名与之关联,以FIFO的文件形式存在于文件系统中。这样,即使与FIFO的创建进程没有父子关系的进程,只要可以访问该路径,就能够彼此通过FIFO相互通信。

主要函数:int mkfifo(const char * pathname, mode_t mode)

四、system V 提供的三种进程间通信方式

消息队列

消息队列可以理解成是消息链表,存储在内核中,进程可以从中读写数据。与管道相比消息队列的优势在于可以独立于发送和接收进程而存在:进程可以在没有另外一个进程等待读的情况下进行写。还有通过发送消息还可以避免命名管道的同步和阻塞问题:一个进程往消息队列中写入数据后退出,另外一个进程仍然可以打开并读取消息。

信号量

信号量比较特殊,首先它是个计数器,主要提供对进程间共享资源访问控制机制。相当于内存中的标志,进程可以根据它判定是否能够访问某些共享资源,同时,进程也可以修改该标志。除了用于访问控制外,还可用于进程同步。 工作流程是检查控制该资源的信号量,如果信号量值大于0,则资源可用,并且将其减1,表示当前已被使用。如果信号量值为0,则进程休眠直至信号量值大于0。

共享内存

共享内存就是映射一段能被其他进程所访问的内存,允许多个进程共享一个给定的存储区并直接访问,它是针对其它进程间通信方式运行效率低而专门设计的.它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步与通信。

套接字

linux套接字分为UNIX域套接字和网络套接字 UNIX域套接字和套接字很相似,但是它有更高的效率,因为它不需要执行协议处理,例如计算校验和,发送确认报文等等,它仅仅复制数据。 网络套接字利用网络进行通信,与前面所提到的通信方式不同的是,它能用于不同计算机之间的不同进程间通信。

总结

各通信方式之间的关系如下图: linux.PNG

需要注意的是如果传递的信息较少或是需要通过信号来触发某些行为,软中断信号机制不失为一种简捷有效的进程间通信方式。但若是进程间要求传递的信息量比较大或者进程间存在交换数据的要求,那就需要考虑别的通信方式了。

关于共享内存:共享内存的通信方式是通过将共享的内存缓冲区直接附加到进程的虚拟地址空间中来实现的,因此,这些进程之间的读写操作的同步问题操作系统无法实现。必须由各进程利用其他同步工具解决。并且Linux无法严格保证提供对共享内存块的独占访问,使用时需要注意。