进程间通信(IPC),指多个进程中线程的数据和信号共享、发送。每个进程都有自己独立的系统资源,和其他进程是隔离的。为了使协调不同进行对资源的使用,进程间需要通信以保持对资源的使用上协调。

IPC的目的有多种,包括数据传输、共享数据、事件通知、进行控制。

在操作系统概念中,高级进程通信可以归为四大类:共享存储器、管道通信、消息传递、C/S方式。进程间通信指进程之间的信息交换。同步和互斥也可以看作IPC,但这种通信是低级通信,它的通信方式效率低、对用户不透明,所以这里不讨论。

在Linux下,IPC有管道、命名管道、消息队列、信号、信号量、共享内存、套接字。接下来会分别解释这几种类型IPC。

共享内存

共享内存能够让两个进程访问同一个逻辑内存,是在两个正在运行的进程之间共享和传递数据的一种最有效的方式,不同进程之间共享的内存通常安排为同一段物理内存。

(1)共享数据结构的通信方式:进程间共用指定数据结构,通过数据结构作缓冲容器。例如生产者-消费者问题中的有界缓冲区。当然数据结构部分还是要开发者自己实现,这样一来,这种通信方法更灵活,根据不同的需求,开发者定制不同的数据结构。

(2)基于共享存储区的通信方式:为了传送大量数据,在内存中划分出一块共享存储区域,进程间可以通过对该区域读写交换换信息,从而实现进程间通信。具体过程是进程先向操作系统申请内存区,并将该内存区附加到自己的地址空间中。

管道

管道指用于一个读进程和一个写进程以实现它们之间通信的一个共享文件,有名pipe文件。管道并不是进程资源,而是和套接字一样属于操作系统的。这种通信方式以字符流的形式实现进程间的高效通信。为了协调双方的通信,管道机制必须提供三方面的协调能力:

(1)互斥,当一个进程对pipe管道进行读/写时,另一进程必须等待。

(2)同步,当进程写入一定数量的数据后,边去睡眠等待,直到另外进程读取数据后唤醒。当读进程发现pipe管道为空时,也去休眠,等待pipe不为空而唤醒。(3)确定通信对方是否存在,存在才进行通信。这三点可以类比到生产者-消费者模型上理解。

如果只使用一个管道实现全双工通信,那么难点在管道中的数据是不属于任何进程的,写入进程可以重新读取自己写入的数据,很难形成有效的通信。也就是说,无论管道中的数据是拿个进程写入的,后面先读的进程会把数据取走。这个实例通过两个管道实现进程间的全双工通信。每个管道负责单向的通信。有时间把配套弄上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <stdio.h>
#include <unistd.h>
#define BUF_SIZE 30

int main(int argc, char *argv[]) {
int fds1[2];
int fds2[2]; //用于存储pipe保存的两个文件描述符
char str1[] = "who are you?";
char str2[] = "I am allenwind";
char buf[BUF_SIZE];
pid_t pid;

pipe(fds1);
pipe(fds2); //两个文件描述符分别表示通信管道出入口两端
pid = fork(); //fork复制的子进程和符进程一样拥有一套相同的文件描述符
if(pid==0) {
write(fds1[1], str1, sizeof(str1));
read(fds2[0], buf, BUF_SIZE);
printf("child process output: %s\n", buf);
}
else {
read(fds1[0], buf, BUF_SIZE);
printf("parent process output: %s\n", buf);
write(fds2[1], str2, sizeof(str2));
sleep(3); //等待接受子进程的数据
}
return 0;
}

消息队列

以格式化的消息为单位,将通信的数据封装在消息中,并利用操作系统提供的一组通信命令(原语),在进程间进行消息传递,完成进程间的通信。该方式隐藏了通信实现的细节,使通信过程对用户透明化,降低了通信程序设计的负责性和错误率,成为当前应用最为广泛的一类进程间通信机制。例子:网络通信中,数据被封装成报文再通过网络传递。微内核操作系统中,微内核和服务器之间的通信无一例外都采用消息通信机制。这种通信机制可以很好支持多处理器系统、分布式系统和计算机网络。在具体实现上,它有两种实现方式:(1)直接通信方式:进程利用操作系统提供的发送原语直接把消息发送给目标进程。(2)间接通信方式:发送和接收进程,都通过共享中间实体(称为邮箱)的方式进行消息的接收和发送,从而实现进程间的通信。邮箱可以看作是大量消息的缓冲区,同时可以在上面实现丰富的消息管理机制,比如路由。现代消息队列就采用间接的通信方式。

基于Socket的C/S方式

在网络环境中,为了使不同操作系统中的进程实现通信,通常采用客户机-服务器系统的通信机制。从实现方法上可以分为三类:套接字(socket)、远程过程调用和远程方法调用。
套接字:包括两个类型,文件类型、网络类型。

socket := (IP, PORT)
文件类型:进程一些与同一系统中,套接字被关联到一特殊文件中。
网络类型:非对称的通信方式,即发送者需要提供接收者命名。典型的是HTTP服务器客户端通信方式。
远程过程调用:RPC。

信号

软中断信号(signal,又简称为信号)是进程间通信机制中唯一的异步通信机制,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候到达。

信号是Linux操作系统的特性,它可以通知正在运行的程序一个信号(事件),后让程序异步处理这个事件。信号可以看作是一种进程间通信,从一个进行发送到另外一个进程。另外还可以由操作系统生成,发送给指定的进行。这些接收到信号(来自操作系统或其他进程)进程可能会中断当前的控制流而去处理其他事情总的来说,信号以进程间通信的方式提供一种异步处理事件的能力。

信号量

信号量(Semaphore)是进程间通信的方式之一,通过P操作,信号量减1;V操作信号量加1。如果信号量为零,阻塞操作的进程。可以参考最新写的关于信号量的实现Go实现信号量Semaphore

分布式锁概念

在单进程多线程下,程序需要通过“锁”来实现操作的互斥以避免出现数据的不一致。在操作系统概念中,我们把需要互斥操作的资源称为临界资源,访问临界资源的代码段称为临界区。多个线程在访问临界资源时,只能有一个线程进入临界区,实现单线程进入临界区的最常见方法是锁:只有获取锁的线程才能进入临界区,然后操作临界资源,操作完成后离开临界区,把获取的锁释放。通常我们说锁的粒度,是指对锁对临界区进行互斥的范围:解锁和解锁的代码段。而该代码段也反映了它要操作的资源的范围。

在单主机多进程下,进程间需要对资源进行互斥访问,需要操作系统提供的同步原语、同步方法,例如Linux下有原子操作、自旋锁、读写自旋锁、Semaphore、Mutex、Barrier等。这些同步方法有效的前提是多进程位于同一主机中,因为同步方法是由操作系统提供的,不能脱离操系统。但是,在多主机下,进程间的同步使用操作系统提供的内核同步方法就会失效,我们需要分布式锁来解决多主机下多进程同步的问题。

转载请包括本文地址:https://allenwind.github.io/blog/1649
更多文章请参考:https://allenwind.github.io/blog/archives/