进程间通信
进程间通信(InterProcess Communication又名IPC),就是多个进程之间的通信。需要考虑三个问题
- 怎么把信息从一个进程传递到另一个进程
- 必须保证两个或更多的进程之间不会相互影响
- 当两个进程之间有依赖时,需要有一个合适的队列关系
线程间通信跟进程间通信类似
进程间通信,囊括来看,只有三种:消息传递,共享内存和远程过程调用(remote procedure call, RPC)。很多其他的,比如管道、信号、消息队列等都是属于消息传递,而临界区等是共享内存范畴
消息传递
消息传递是两个进程之间通过发送和接收来做到消息传递,其主要包括管道和消息队列,管道又分为有名管道、匿名管道、全双工管道
管道
管道是最初的UNIX的IPC形式,最早是匿名管道,只能由有亲缘关系的进程使用,后面引入了FIFO(有时称为有名管道)。无论是有名管道还是匿名管道,都是使用read
和write
函数访问的
自从进程间可以传递描述符后,匿名管道也能用于无亲缘关系进程间,但是,匿名管道通常还是用于具有亲缘关系的进程间
匿名管道用pipe
函数创建,返回0表示成功,返回-1表示出错。入参传入两个文件描述符,成功后,fd[0]
用来读,fd[1]
用来写。后续的UNIX版本支持全双工管道,即,两个文件描述符都可以用来读写
#include <unistd.h>
int pipe(int fd[2]);
某些系统提供全双工管道:SVR4的pipe
函数以及很多内核提供的socketpair
函数。全双工管道的实现如图,是两个独立的数据流,即两个半双工的管道组成的
标准I/O提供了两个函数popen
和pclose
,创建一个管道并启动另外一个进程,该进程要么从该管道读出标准输入,要么往该管道写入标准输出
#include <stdio.h>
FILE *popen(const char* command, const char* type); // 成功返回一个文件指针,失败返回NULL
int pclose(FILE *stream); // 出错为-1
匿名管道在不考虑文件描述符传递时,只能用于亲缘关系的IPC通道,而FIFO是UNIX实现的一个有名管道,是先进先出的模式,同样也是一个半双工管道,但是每一个管道都有一个名称,FIFO管道由mkfifo
函数创建
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char* pathname, mode_t mode); // 成功为0, 错误为-1
FIFO管道(有名管道),只能用来读或者写,因为它是一个半双工管道
消息队列
消息队列可以看做是一个消息链表,每条消息就是一个记录。在某个进程往一个队列写入消息之前,并不需要另外某个进程在该队列上等待消息的到达,注意这点与管道的区别,管道是从缓冲区等待读写的,管道除非读出者已存在,否则先有写入者是没有意义的。
不同的系统实现的消息队列略有差别,主要实现是Posix
消息队列和System V
消息队列,两者在函数组上相似,主要有以下差别
- Posix消息队列总是返回最高优先级中最早的消息;System V可以返回任意指定优先级的消息
- Posix消息队列允许产生一个信号或启动一个线程;System V不提供类似的机制
Posix消息队列函数组说明
函数名 | 功能描述 | 返回值说明 |
---|---|---|
mq_open | 创建一个新的消息队列或者打开一个已经存在的消息队列 | 成功返回队列描述符,失败为-1 |
mq_close | 关闭消息队列 | 成功返回0,出错为-1 |
mq_unlink | 删除一个消息队列 | 成功为0,出错为-1 |
mq_getattr | 获取消息队列属性 | 成功为0,出错为-1 |
mq_setattr | 设置消息队列属性 | 成功为0,出错为-1 |
mq_send | 发送消息 | 成功为0,出错为-1 |
mq_receive | 接收消息 | 成功为消息中字节数,出错为-1 |
mq_notify | 指定队列建立或删除异步通知事件 | 成功为0,出错为-1 |
System V消息队列函数组说明
函数名 | 功能描述 | 返回值说明 |
---|---|---|
msgget | 创建一个新的或打开一个已经存在的消息队列 | 成功为非负标识符,出错为-1 |
msgsnd | 发送消息 | 成功为0,出错为-1 |
msgrcv | 接收消息 | 成功为字节数,出错为-1 |
msgctl | 控制操作 | 成功为0,出错为-1 |
共享内存
共享内存是IPC形式中最快的。一旦这样的内存区映射到共享它的进程的地址空间,这些进程间数据的传递就不再涉及内核。然而往该共享内存区存放信息或从中取走信息的进程间通常需要某种形式的同步。同步机制主要是互斥锁、条件变量、信号量、读写锁等,下一个主题会讲
管道的通信方式,通常需要在内核中复制数据,这样就增加了消耗,使用共享内存,能够减少内核切换次数,从而提高效率。
共享内存主要是通过mmap
, munmap
和msync
函数来实现的
#include <sys/mmap.h>
void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset); // 成功返回映射区起始地址,失败为MAP_FAILED
int munmap(void *addr, size_t len); // 成功为0,失败为-1
int msync(void *addr,size_t len,int flags); // 成功则为0,出错则为−1
共享内存,一旦内存映射了一个文件,我们就不再使用read、write和lseek来访问该文件,而只是存取已由mmap映射到该文件的内存位置。把显式的文件I/O操作变换成存取内存单元往往能够简化我们的程序,有时候还能改善性能
远程过程调用
远程过程调用,是跟本地调用不同的,一般是用在不同的机器之间的调用,当然也可以用于相同机器上不同进程的调用。主要是通过socket
一些列函数进行操作,其主要原理是建立在TCP
通信的基础之上的
本文作者:xnzone
本文链接:https://www.cnblogs.com/xnzone/p/15892906.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。