进程间通信(IPC)
为什么要进程通信?
为什么要进程间通信:大项目的模块化,协同运行
-
数据传输
一个进程需要将它的数据发送到另一个进程
-
资源共享
多个进程之间共享同样的资源
-
通知事件
一个进程要向另一个程序发送通知消息,通知它们发生了某种事件,比如子进程终止时要通知父进程
-
进程控制
有些进程希望完全控制另一个进程的执行(比如 : DEGUG),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够知道他们状态的改变。
为什么进程通信需要操作系统提供接口:因为独立性
- 操作系统如何能够让进程之进行通信:给多个进程之间提供一个公共的,都能访问到的媒介
- 操作系统因为提供进程间通信的使用场景不同,因此提供的进程间通信方式也有多种,各自也有各自的特点,本质上都是提供多个进程都能访问的缓冲区
进程间通讯的发展
- 管道
- 匿名
- 命名
- SystemV 标准 (系统调用使用)
- 消息队列(数据传输)
- 共享内存(资源共享)
- 信号量(事件通知)
- POSIX 标准 (标准c库使用)
- 消息队列
- 共享内存
- 互斥量
- 条件变量
- 信号量
- 读写锁
管道
- 用于在进程间传输数据资源
- 对于管道这块缓冲区的操作,和io操作使用同一套接口
- 管道的特性
- 管道时半双工通讯(单向通讯)
- 读写特性
- 如果管道中没有数据,则read会阻塞,直到读取出数据
- 如果管道中数据满了,则write会阻塞,直到有数据被读出去
- 如果管道的所有写端都被关闭,那么读端读完管道中的数据之后,返回0
- 如果管道的所有读端都被关闭,那么写端写入数据的时候会触发异常,退出进程
- 管道自带同步与互斥保护操作
- 当对管道的读写数据大小不大于PIPE_BUF时候,将保证数据读写的原子性
- 提供字节流服务(数据传输灵活,但是有可能造成数据的粘连)
- 生命周期随进程
- 匿名管道:只能用于具有亲缘关系的进程
- 匿名管道
- 创建的缓冲区没有标识,因此只能用于具有亲缘关系的进程间通信,创建一个管道,返回两个文件描述符,这时创建一个子进程,子进程复制父进程,因此子进程也有相同的描述符指向内核相同的缓冲区,这是就可以通信了
int pipe (int fds[2]);
- 创建的缓冲区没有标识,因此只能用于具有亲缘关系的进程间通信,创建一个管道,返回两个文件描述符,这时创建一个子进程,子进程复制父进程,因此子进程也有相同的描述符指向内核相同的缓冲区,这是就可以通信了
- 一个例子
- 命名管道
可见于文件系统,所以所有的进程可以通过打开文件,获取到内核管道这块缓冲区对应的描述符.因此命名管道可以用于同一机器上的任一进程间通讯
- 创建命名管道
mkfifo test.fifo
- int mkfifo(const char *parhname, mode_t mode)
- 8功能:创建一个管道文件
- 参数:
- pathname : 路径
- mode : 权限
- 返回值:成功0 失败 -1
- 命名管道的特性
- 命名管道与匿名管道各种特性都一样(除了亲缘间进程通信)
- 命名管道的打开特性
- 如果管道文件只读打开,将阻塞,直到有这个文件被以写的方式打开
- 如果管道文件只写打开,将阻塞,直到有这个文件被以读的方式打开
- 如果管道文件以读写打开,则就不阻塞
- 同步:对临界资源操作的时序可控性
- 互斥:对临界资源在同一时间的唯一访问性
消息队列
内核创建一个队列,实现进程间的数据传输
传输的是有类型的数据块,类型(整数)可以用于定义优先级,用于区分进程,用于区分数据功能
限制:最大的占用字节数大小限制
- 获取/创建消息队列
返回值:消息队列的id ,相当于文件描述符#include <sys/msg.h> #include <sys/ipc.h> int msgget( key_t key , // 相当于文件名 int flags // 创建 IPC_CREAT | 0644(权限) // 打开 0 );
- 查看IPC对象
- ipcs -q //查看消息队列 查看IPC对象
- ipcrm -Q key //删除指定key的消息队列 删除IPC对象
- 系统中最多可以创建多少个消息队列?
- cat /proc/sys/kernel/msgmni //最多创建个数
- cat /proc/sys/kernel/msgmax //一条消息最多装多少字节
- cat /proc/sys/kernel/msgmnb //一个消息队列中一个消息的总字节数是多少
- 发送消息
返回值 :失败 -1 成功 0int msgsnd(int msgid, //msgget的返回值 const void* msgp, //消息的位置必须是类似于 //msgbuf的结构体 size_t len , //消息的长度不包括type的大小 int flag //消息的标志位 );
struct msgbuff{ long type ; //消息类型 必须 >= 1 必须有 //随便 //随意类型,写上自己的消息 }
- 接收消息
ssize_t msgrcv( int msgid, //msgget的返回值 void *msgp, //取出消息存放位置 size_t msgsz, //装消息位置的大小,不包括类型 long msgtype, //取那个类型的消息 int msgflag //选项,一般填0,表示没有消息就在等,有了就取出来 );
- 返回值:失败:-1 成功 :实际拷贝的字节数
共享内存
- 特点
- 进程间通讯最快的方式
-
创建或者打开共享内存
shmget( key_t key, //共享内存的标识符(可以使用ftok创建) size_t size, //共享内存的大小 int flag //创建IPC_CREAT|0664 打开0 );
- 若描述符key存在,则后两个参数无效
- 返回值:失败 -1 成功 返回操作句柄
-
让共享内存与本进程建立关系(将共享内存映射到进程中)
void *shmat( key_t key, const char* shmaddr, //让操作系统挂载到地址空间的 位置若为NULL,这让操作系统自己选择 int flag // 0 );
- 返回值:成功返回 实际挂载到虚拟地址空间的起始位置 失败返回 (void *) -1;
-
卸载掉共享内存(解除映射关系)
int shmdt(void * shmaddr);
-
删除共享内存
int shmctl( key_t key, int cmd, //IPC_RMID 删除共享内存 NULL //本来这里是个结构体,但是删除用不上,直接填NULL )
- 删除共享内存,并不会直接删除,先是等待 nattch(将该共享内存映射到自己地址空间的进程个数) 变成 0,并在等待期间,拒绝新的映射链接
信号量
-
是什么?
其实就是一个计数器 + 等待队列 --> 用于对资源进行计数
- 获取一个资源,计数 - 1,如果没有资源,计数为零,为了获取资源因此死等
- 回收一个资源,计数 + 1,唤醒死等,唤醒的是信号量等待队列上的进程
-
功能就是等待与唤醒:用于实现进程间的同步与互斥
- 同步:对资源操作时,如果计数为0,标示没有资源,代表不能操作则死等,资源回收后计数 + 1,被唤醒才能进行操作
- 互斥:计数只有0和1,要不然有资源操作,要不然没有资源操作
- 别人拿走资源,计数变成0,不能操作则死等,意味着别人对资源操作期间谁都无法堆资源进行操作
由于本人才疏学浅,若有疏漏还望不吝赐教。
@YeLing0119