Linux进程间通信(IPC)的六种方式

1. 什么是进程间通信:

每个进程的用户地址空间都是独立的,一般而言不能互相访问,但内核空间是每个进程都共享的,所以进程之间要通信必须通过内核。

在32位Linux系统下,进程的虚拟地址空间大小为 4G,其中高地址的 1G是内核地址空间,其余 3G是用户空地址空间。用户地址空间对于每个进程来说是独立的,但每个进程的内核地址空间关联的都是相同的物理内存。

进程在用户态时,只能访问用户地址空间的内存;只有切换到内核态后,才可以访问内核空间的内存。

Linux进程间通信(IPC)的六种方式_第1张图片

2. 进程间通信的六种方式:

2.1 管道(pipe):

管道分为无名管道(pipe)和有名管道(fifo)两种,通常指的是无名管道。
管道是Unix系统最古老的进程间通信方式。

  1. 无名管道(PIPE):

管道是一种半双工的通信方式,数据只能单向传输,而且只能在有亲缘关系的进程间使用(父子进程间)。如果想要进行全双工的双向传输,则需要建立两个管道。

例如 Linux命令中的这个竖线 “|” 就是一个管道,它的功能是前一个命令(ps aux) 的输出,作为后一个命令(grep mysql)的输入(由此也可将管道传输是单向的):

ps -aux | grep mysql

无名管道的创建,需要通过下面这个系统调用:

int pipe(int fd[2]);

这里会创建两个文件描述符fd[0]、fd[1],随后,通过调用fork()创建子进程,创建的子进程会复制父进程的文件描述符,这样就能做到了两个进程各有两个文件描述符fd[0]和fd[1]。

为了避免父子进程同时写入和读出造成的消息混乱,一般的做法是父进程关闭读取的fd[0],只保留写入的fd[1],子进程关闭写入的fd[1],只保留读取的fd[0]。

所以如果需要双向通信,则应该创建两个管道(在调用fork之前创建两个管道)。

  1. 命名管道(FIFO):

命名管道也是半双工的通信方式,但是它允许无亲缘关系的进程间的通信。

所谓的管道,就是内核里的一串缓存,从管道的一端写入的数据,实际上是缓存在内核中的,另一端读取,也是从内核中读取这段数据。 由于管道的数据传递需要发送端从 用户空间 将消息拷贝到 内核空间,再由接收方将消息从 内核空间 拷贝出到 用户空间,所以通信效率很低。

另外,管道传输的数据是 无格式的流大小受限

2.2 消息队列(message queue):

由于管道的通信方式效率很低,不适合进程间频繁的交换数据,因此引入了消息队列的进程间通信方式。

消息队列是保存在内核中的消息链表, 在发送数据时,会分成一个一个独立的数据单元,也就是消息体(数据块),消息体是用户自定义的数据类型,消息的发送方和接收方要约定好消息体的数据类型,所以每个消息都是固定大小的存储块,不像管道是无格式的字节流数据。

消息队列的缺点是:通信不及时、附件有大小限制。

消息队列不适合比较大的数据的传输,这是因为内核中每个消息体都有一个最大长度的限制,且队列中所有消息的总长度也有限制。

消息队列通信过程同样存在用户态与内核态之间的数据拷贝开销。

2.3 共享内存(shared memory):

共享内存是速度最快的进程间通信方式。

前面提到的管道、消息队列的方式,由于在消息收发过程中存在用户态到内核态的数据拷贝开销,导致通信效率低,共享内存解决了这个问题。

共享内存机制的实现原理是,由一个进程初始化一块共享内存地址,其他进程也都将虚拟地址空间中的一块映射到相同的物理内存地址上去,这样发消息时就不需要将数据从用户空间拷贝到内核空间,接收消息时只需要去对应的地址上去读消息即可。

共享内存存在一个问题,由于多个进程可以同时操作同一块物理内存地址空间,有可能会发生写冲突(写覆盖)。为解决多进程竞争共享资源,需要引入信号量的机制。

Linux进程间通信(IPC)的六种方式_第2张图片

共享内存API函数:

#include 
#include 

int shmget(key_t key, size_t size, int shmflg);

void *shmat(int shmid, const void *shmaddr, int shmflg);

int shmdt(const void *shmaddr);

int shmclt(int shmid, int cmd, struct shmid_ds *buf);


2.4 信号量(semophore):

信号量的主要作用在于解决使用共享内存时存在的多进程写冲突的问题。

信号量本质上是一个整型计数器,主要用来实现进程间的互斥与同步,而不是用来缓存进程间通信的数据。

信号量表示资源的数量,控制信号量有两种原子操作:
一个是 P操作,将信号量的个数 -1;
一个是 V操作,将信号量的个数 +1。

P操作用在进入共享资源之前,V操作用在离开共享资源之后,这两个操作必须是成对出现的

2.5 信号(signal):

信号是唯一的 异步 进程间通信机制。

Linux系统中为了响应各种各样的事件,提供了几十种信号,使用 kill -l 命令可以进行查看:
Linux进程间通信(IPC)的六种方式_第3张图片

例如,在shell终端进程中,通过 Ctrl+C 可以产生 SIGINT信号,表示终止该进程;通过 Ctrl+Z 可以产生 SIGTSTP信号,白哦是停止该进程,但还未结束,等。

使用kill命令发送信号的格式是:

kill + signo + pid

例如:
kill -9 1050

一旦有信号产生,用户进程对信号的处理方式有三种:

  1. 执行默认操作:
    Linux对每种信号都规定了默认的操作,例如SIGTERM信号表示终止进程等。
  2. 捕捉信号:
    可以为信号定义一个信号处理函数,当信号发生时,程序就会执行相应的信号处理函数。
  3. 忽略信号:
    如果不希望处理某些信号,就可以忽略该信号,不做任何处理。
    有两个信号是应用进程无法捕捉和忽略的:
    SIGKILL(9)SIGSTOP(19),它们用于在任何时候中断或结束某一进程。

2.6 套接字(socket):

套接字也是一种进程间通信机制,与其他通信机制不同的是,它可以用于不同机器间的进程间通信。

你可能感兴趣的