摘要
- 管道:pipe、亲缘进程、半双工(单向)
- 有名管道:FIFO、半双工(单向)
- 消息队列:MessageQueue
- 共享内存:SharedMemory、最快
- 信号量:Semaphore、同步
- 信号:Signal
- 套接字Socket:不同机器
对比
| | 单双工 | 进程关系 | | —- | —- | —- | | 管道pipe | 半双工 | 亲缘进程(父子进程) | | 有名管道 | 半双工 | 无 | | 信号量 | 互斥 | | | 消息队列 | | | | 信号 | | | | 共享内存 | | | | 套接字socket | | 跨机器进程 |
名词
System V
存在时间久,支持系统多。接口复杂,平台有区别(ftok实现及限制)
POSIX
新标准,语法简单,跨平台,可移植
方式
管道
UNIX 系统IPC最古老的形式
半双工,即只能单向传输数据,需双向传输时一般创建两个管道。
文件描述符,仅存在内存中,不存在于文件系统,无法查看
进程只能使用本身和祖先创建的管道(父子进程、兄弟进程通信)
固定的读写端,pipefd[0]为读,pipefd[1]为写
命名管道
消息队列
原理
消息队列是链表队列,它通过内核提供一个struct msqid_ds *msgque[MSGMNI]向量维护内核的一个消息队列列表,因此linux系统支持的最大消息队列数由msgque数组大小来决定,每一个msqid_ds表示一个消息队列,并通过msqid_ds.msg_first、msg_last维护一个先进先出的msg链表队列,当发送一个消息到该消息队列时,把发送的消息构造成一个msg结构对象,并添加到msqid_ds.msg_first、msg_last维护的链表队列,同样,接收消息的时候也是从msg链表队列尾部查找到一个msg_type匹配的msg节点,从链表队列中删除该msg节点,并修改msqid_ds结构对象的数据。
struct msqid_ds *msgque[MSGMNI]向量:
msgque[MSGMNI]是一个msqid_ds结构的指针数组,每个msqid_ds结构指针代表一个系统消息队列,msgque[MSGMNI]的大小为MSGMNI=128,也就是说系统最多有MSGMNI=128个消息队列。
在程序中若要使用消息队列,必须要能知道消息队列key,因为应用进程无法直接访问内核消息队列中的数据结构,因此需要一个消息队列的标识,让应用进程知道当前操作的是哪个消息队列,同时也要保证每个消息队列key值的唯一性
特性
与管道相比,区分消息类型,避免命名管道的同步和阻塞问题,无需亲缘进程
每个数据块都有一个最大长度的限制
与命名管道相比,消息队列的优势在于,1、消息队列也可以独立于发送和接收进程而存在,从而消除了在同步命名管道的打开和关闭时可能产生的困难。2、同时通过发送消息还可以避免命名管道的同步和阻塞问题,不需要由进程自己来提供同步方法。3、接收程序可以通过消息类型有选择地接收数据,而不是像命名管道中那样,只能默认地接收。
Linux用宏MSGMAX和MSGMNB来限制一条消息的最大长度和一个队列的最大长度。
适用场景
实现
操作流程图
代码示例
简结
保存在内核中
消息链表,节点类似struct {long type, void *}
有消息类型区分:较管道而言,过滤、无序读取
可做记录,特定格式、特定优先级
权限限制
内存拷贝:4次,输入——>用户空间A——>内核空间——>用户空间B——>输出
操作
- 创建消息队列:每个进程均可,已创建特殊处理
- 控制消息队列:每个进程均可,如删除,一般最后一个进程销毁
- 添加消息到消息队列
- 读取消息并从消息队列中移除
API
System V:ftok、msgget、msgctl、msgsnd、msgrcv
Posix:mq_open、mq_getattr/mq_setattr、mq_send/mq_recv、mq_close/mq_unlinkmsgget
创建/访问一个消息队列
int msgget(key_t key, int msgflg);
msgsend/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);
msgctl
消息队列控制函数
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
mq_open
创建非默认个数大小消息队列
mqd_t mq_open(const char *name, int oflag);
mqd_t mq_open(const char *name, int oflag, mode_t mode, struct mq_attr *attr);
mq_getattr/mq_setattr
设置/获取消息队列的属性
int mq_getattr(mqd_t mqdes, struct mq_attr *attr);
int mq_setattr(mqd_t mqdes, const struct mq_attr *newattr, struct mq_attr *oldattr);
mq_send/mq_recv
int mq_send(mqd_t mqdes, const char *msg_ptr, size_t msg_len, unsigned int msg_prio);
ssize_t mq_receive(mqd_t mqdes, char *msg_ptr, size_t msg_len, unsigned int *msg_prio);
int mq_timedsend(mqd_t mqdes, const char *msg_ptr, size_t msg_len, unsigned int msg_prio,
const struct timespec *abs_timeout);
ssize_t mq_timedreceive(mqd_t mqdes, char *msg_ptr, size_t msg_len, unsigned int *msg_prio,
const struct timespec *abs_timeout);
mq_close/mq_unlink
int mq_close(mqd_t mqdes);
int mq_unlink(const char *name);
共享内存
https://www.cnblogs.com/songhe364826110/p/11530732.html
在内存中开辟一块内存用于共享
每个进程将其映射到进程地址空间
效率高,最快,内存拷贝:2次,输入——>共享内存——>输出,无需进入内核即不使用系统调用
缺少同步机制,如信号量
实现
- 内存映射
-
操作
创建共享内存
- 建立进程与共享内存映射——读写使用
- 解决进程与共享内存映射
- 删除共享内存
API
System V:shmget、shmat、shmdt、mmap、munmap
Posix:shm_open、shm_unlink
shmget
功能
创建共享内存
原型
int shmget(key_t key, size_t size, int shmflg);
参数
参数 | 功能 | 备注 |
---|---|---|
key | 有效地为共享内存段命名 | 非0整数 |
size | 指定需要共享的内存容量 | 单位:字节 |
shmflg | 权限标志 | 与open函数的mode参数一样,与文件的读写权限一样。创建它的话,可以与IPC_CREAT做或操作 |
0644,它表示允许一个进程创建的共享内存被内存创建者所拥有的进程向共享内存读取和写入数据,同时其他用户创建的进程只能读取共享内存。
返回
返回值 | 标志 | 备注 |
---|---|---|
共享内存标识符 | 成功 | 非负整数 |
-1 | 失败 |
shmat
功能
启动对该共享内存的访问,并把共享内存连接到当前进程的地址空间
原型
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数
参数 | 功能 | 备注 |
---|---|---|
shmid | 共享内存标识 | shmget返回 |
shmaddr | 指定共享内存连接到当前进程中的地址位置 | 通常为空,表示让系统来选择共享内存的地址 |
shmflg | 一组标志位 | 通常为0 |
返回
返回值 | 标志 | 备注 |
---|---|---|
指向共享内存第一个字节的指针 | 成功 | |
-1 | 失败 |
shmdt
功能
将共享内存从当前进程中分离。注意,将共享内存分离并不是删除它,只是使该共享内存对当前进程不再可用
原型
int shmdt(const void *shmaddr);
参数
参数 | 功能 | 备注 |
---|---|---|
shmaddr | 共享内存首地址指针 | shmat返回 |
返回
返回值 | 标志 | 备注 |
---|---|---|
0 | 成功 | |
-1 | 失败 |
shmctl
shmctl(2) - Linux manual page (man7.org)
功能
控制共享内存
原型
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数
参数 | 功能 | 备注 |
---|---|---|
shmid | 共享内存标识 | shmget返回 |
cmd | 要采取的操作(IPC_STAT、IPC_SET、IPC_RMID) | |
buf | 指向共享内存模式和访问权限的结构 |
返回
返回值 | 标志 | 备注 |
---|---|---|
0 | 成功 | |
-1 | 失败 |
信号量
计数器,用于互斥同步而非通信,需搭配共享内存
基于操作系统的PV操作,原子操作
最简单的二值信号量即互斥锁
支持二值信号量(0,1)和通用信号量
操作
API
套接字
参考
https://blog.csdn.net/zhaohong_bo/article/details/89552188
进程间通讯的7种方式zhaohong_bo的专栏-CSDN博客进程间通信
谈谈进程间通信的几种方式?_风华绝代-CSDN博客
进程间通信机制_weixin_33878457的博客-CSDN博客
进程间的八种通信方式墨韵纸香的博客-CSDN博客进程间通信的方式
进程间通信之-socket编程原理平凡之路-CSDN博客进程间通讯编程
TODO
- 进程间通信方式的迭代性(历史线),消息队列进一步优化了管道,故思考优缺点是什么(解决了管道什么问题,还有哪些问题未解决)?
- 这一解决方案的实现原理是什么?即系统支持还是用户自建?系统又是如何进行实现和管理的?解决系统提供的API进行思考,画图表示
- 代码实现过程中需要注意什么?实现的流程是什么(流程图)?如何进行优化?
- 如何在技术博客中写库函数/系统调用的相关介绍?是誊抄一份呢,还是提供链接,还是仅说明函数名即可?
- 同类文章如何快速编写,提高效率?