这篇博客写得很详细https://www.cnblogs.com/UnfriendlyARM/p/10148520.html
一、进程通信基本概念(IPC,Inter-Process Communication)
1. 定义:进程通信时进程进行通信和同步的机制
2. IPC提供两个基本操作
- 发送操作:send(message)
- 接收操作:receive(message)
3. 进程通信流程
- 在通信进程间建立通信链路
- 通过send/reveive交换信息
4. 进程链路特征
- 物理(如,共享内存,硬件总线)
- 逻辑(如,逻辑属性)
5. 通信方式
分为直接通信和间接通信
5.1 直接通信
(1)进程必须正确地命名对方
- send(P,message)——发送信息到进程P
- receive(Q,message)——从进程Q接受信息
(2)通信链路的属性
- 自动建立链路;
- 一条链路恰好对应一对通信进程;
- 每对进程之间只有一个连接存在;
- 连接时可以是单向的,但通常为双向的。
5.2 间接通信
(1)通过操作系统维护的消息队列实现进程间的消息接受和发送
- 每个消息队列都有一个唯一的标识;
- 只有共享了相同消息队列的进程才能通信。
(2)通信链路的属性
- 只有共享了相同消息队列的进程,才建立连接;
- 连接可以时是单向或双向;
- 消息队列可以与多个进程相关联;
- 每对进程可以共享多个消息队列。
(3)通信流程
- 创建一个新的消息队列;
- 通过消息队列发送和接受消息;
- 销毁消息队列。
(4)基本通信操作
- send(A,message)——发送消息队列到A
- receive(A,message)——从队列A接受消息
6. 阻塞与非阻塞通信
进程通信可划分为阻塞(同步)或非阻塞(异步)
(1) 阻塞通信
- 阻塞发送:发送者在发送消息后进入等待,知道接收者成功收到;
- 阻塞接收:接受者在请求接受消息后进入等待,知道成功收到一个消息。
(2)非阻塞通信
- 非阻塞发送:发送者在消息发送后,可立即进行其他操作;
- 非阻塞接收:没有消息发送时,接收者在请求接收消息后,接受不到任何消息。
7. 通信链路缓冲
进程发送的消息在链路上可能有3种缓冲方式
- 0容量:发送方必须等待接收方
- 有限容量:通信链路缓冲队列满时,发送方必须等待
- 无限容量:发送方不需要等待
二、信号
1. 定义:
信号是进程间的软件中断通知和处理机制,如:SIGKILL,SIGSTOP,SIGCONT等。
2. 信号的接收处理
- 捕获(catch):执行进程指定的信号处理函数被调用。
- 忽略(ignore):执行操作系统指定的缺省处理,例如:进程终止,进程挂起等。
- 屏蔽(Mask):禁止进程接收和处理信号,可能是暂时的(当处理同样类型的信号)。
3. 信号的实现
4. 不足:传送的信息量小,只有一个信号类型
5. 信号使用示例
二、管道
1. 定义:进程间基于内存文件的通信机制
- 子进程从父进程继承文件描述符
- 缺省文件描述符:0 stdin,1stdout,2 stderr
进程不知道(或不关心)的另一端:可能从键盘、文件、程序读取;可能写入到终端、文件、程序。
2. 管道的数据结构图
3. 与管道相关的系统调用
- 读管道:read(fd, buffer, nbytes),如 scanf() 是基于它实现的。
- 写管道:write(fd, buffer, nbytes),如 printf() 是基于它实现的。
- 创建管道:pipe(rgfd),rgfd是2个文件描述符组成的数组,rgfd[0]是读文件描述符,rgfd[1]是写文件描述符。
3. 管道示例
三、消息队列
1. 定义:消息队列是由操作系统维护的以字节序列为基本单位的间接通信机制。每个消息(Message)是一个字节序列,相同标识的消息组成按先进先出顺序组成一个消息队列(Message Queues)
2.为什么引入消息队列机制
信号量和PV操作可以实现进程的同步和互斥,但是这种低级通信方式并不方便,而且局限性较大。当不同进程之间需要交换更大量的信息时,甚至是不同机器之间的不同进程需要进行通信时,就需要引入更高级的通信方式——消息队列机制。
3.信箱
消息队列的难点在于,发送方不能直接将要发送的数据复制进接收方的存储区,这时就需要开辟一个共享存储区域,可供双方对这个存储区进行读写操作。这个共享区域就叫做信箱。每个信箱都有一个特殊的标识符。每个信箱都有自己特定的信箱容量、消息格式等。信箱被分为若干个分区,一个分区存放一条消息。
4.重要的两条原语:
原语具有不可分割性,执行过程不允许被中断。
(1)发送消息原语(send):如果信箱就绪(信箱还未存满),则向当前信箱指针指向的分区存入一条消息,否则返回状态信息(非阻塞式)或者等待信箱就绪(阻塞式)。
(2)接收消息原语(receive):如果信箱就绪(信箱中有消息),则从当前信箱指针指向的分区读取一条消息,否则返回状态信息(非阻塞式)或者等待信箱就绪(阻塞式)。
注:在信箱非空的情况下,每读取一次信箱,信箱中的消息就会少一条,直到信箱变为空状态。
5.消息通信的原理
(1)如果一个进程要和另外一个进行通信,则这两个进程需要开辟一个共享存储区(信箱);
(2)消息通信机制也可以用在一对多通信上,一个server和n个client通信时,那么server就和这n个client各建立一个共享存储区;
(3)一个进程可以随时向信箱中存储消息,当然一个进程也可以随时从信箱中读取一条消息。
6.消息机制的同步作用
采用消息队列通信机制,可以实现进程间的同步操作。在介绍同步功能之前,需要先介绍两个名词,阻塞式原语和非阻塞式原语。阻塞式原语是指某进程执行一个指令时,如果当前环境不满足执行条件,则该进程会在此停止,等待系统环境满足执行条件,然后继续向下执行。非阻塞式原语是指某进程执行一个指令时,如果当前环境不满足执行条件,则立即返回一个状态信息,并继续执行接下来的指令。
(1)非阻塞式发送方+阻塞式接收方:两个进程开始运行后,接收方会进入等待状态,等待发送方给接收方发送一条消息,直到接收到相应的消息后,接收方进程才会继续向下执行。
(2)非阻塞式发送方+非阻塞式接收方:发送方和接收方共享一个信箱,发送方随时可以向信箱中存入一条消息,接收方可以随时从信箱读取一条消息。当信箱满时,发送方进入阻塞状态;当信箱空时,接收方进入阻塞状态。
7. 消息队列的系统调用
7.1 msgget()函数
msgget()函数的作用是创建一个新的或打开一个已经存在的消息队列,此消息队列与key相对应。函数格式为
int msgget(key_t key, int msgflag);
参数key是用户指定的消息队列的名称;参数flag是消息队列状态标志,其可能的值有:IPC_CREAT(创建新的消息队列)、IPC_EXCL(与IPC_CREAT一同使用,表示如果要创建的消息队列已经存在,则返回错误)、 IPC_NOWAIT(读写消息队列要求无法满足时,不阻塞);返回值是创建的消息队列标识符,如果创建失败则则返回-1。函数调用方法是:
msgget(key,IPC_CREAT|0777);
0777是存取控制符,表示任意用户可读、可写、可执行。如果执行成功,则返回消息队列的ID号(注意和队列KEY值作区分,这二者不同),否则返回-1。
7.2 msgsnd()函数和msgrcv()函数
msgsnd()函数的作用是将一个新的消息写入队列,msgrcv()函数的作用是从消息队列读取一个消息。函数格式是
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
参数msqid是消息队列的ID号;参数msgp是指向消息缓冲区的指针,此位置用来暂时存储发送和接收的消息,是一个用户可定义的通用结构,形态如下
struct msgbuf {
long mtype; /* 消息类型,必须 > 0 */
char mtext[1]; /* 消息文本 */
};
参数msgsz是消息大小;参数msgtyp是消息类型(大于0则返回其类型为msgtyp的第一个消息,等于0则返回队列的最早的一个消息,小于0则返回其类型小于或等于mtype参数的绝对值的最小的一个消息),msgflag这个参数依然是是控制函数行为的标志(取值0,表示忽略,那么进程将被阻塞直到函数可以从队列中得到符合条件为止;取值IPC_NOWAIT,表示如果消息队列为空,则返回一个ENOMSG,并将控制权交回调用函数的进程)。
7.2 msgctl()函数
msgctl()函数的作用是对相应消息队列进程控制操作。函数格式是
int msgctl(int msqid,int cmd,struct msqid_ds *buf);
参数msqid表示消息队列ID号;cmd表示对队列的控制操作,其可能值有IPC_STAT(读取消息队列的数据结构msqid_ds,并将其存储在buf指定的地址中)、IPC_SET(设置消息队列的数据结构msqid_ds中的ipc_perm元素的值,这个值取自buf参数)、IPC_RMID(从系统内核中移走消息队列);参数*buf用来表示队列的当前状态,可以设置为空。
四、共享内存
1. 定义
共享内存是把同一个物理内存区域同时映射到多个进程的内存地址空间的通信机制。对于进程来说,每个进程都有私有内存地址空间,每个进程的内存地址空间需要明确设置共享内存段。对于线程来说,同一进程中的线程总是共享相同的内存地址。
2. 优点:快送、方便地共享数据
3. 不足:必须用额外的同步机制来协调数据访问
4. 共享内存的实现
5. 共享内存系统调用
- shmget(key, size, flags),创建共享段
- shmat(shmid, *shmaddr, flags),把共享段映射到进程地址空间
- shmdt(*shmaddr),取消共享段到进程地址空间的映射
- shmctl(...),共享段控制
- 需要信号量等机制协调共享内存的访问冲突