1. 什么是进程间通信
通俗来讲, 进程间通信就是: 多个进程之间的数据交互
进程都有自己独立的虚拟地址空间, 导致进程之间的数据交互变得十分困难, 通信复杂了, 但是安全性提高了;
进程间通信的本质: 多个进程之间是否可以访问同一块内存 / 缓冲区
命令: ipcs: 显示 IPC 信息 ipcrm: 手动删除 IPC 资源
2. 进程间通信的目的
数据传输: 一个进程在某些情况下需要将自己的数据发送给另一个进程
资源共享: 多个进程之间共享同样的资源
通知事件: 一个进程可能向其他进程或进程组发送消息, 表示发生了某个事件 (如进程终止时会向父进程发送 SIGCHILD 信号)
进程控制: 有些进程, 例如 DUBUG 进程, 希望完全控制另一个进程的执行, 及时知道进程的状态改变
3. 进程间通信的方式
1) 管道: 内核中的一块缓冲区, 用于传输数据资源
匿名管道: 内核开辟这段缓冲区的时候没有任何标记, 操作系统只返回了该缓冲区的文件描述符供进程使用, 对于其他进程, 不知道这个文件描述, 因此无法发问到这个缓冲区,
但是对于 fork 出来的子进程, 由于子进程复制了父进程的程序地址空间, 因此也复制了该缓冲区的文件描述符, 也就意味着子进程可以和父进程进行数据传输, 进行通信;
匿名管道的特点: 只能用于具有亲缘关系的进程间通信, 单向通信 (半双工), 面向字节流
int pipe(int fd[2]); // 用于创建匿名管道
参数: fd 是文件描述符数组, 其中放到 fd[0] 表示读端, fd[1] 表示写端
因为管道是单向通信, 所以操作系统无法决定到底是读还是写, 所以返回两个文件描述符, 一个用于读, 一个用于写, 这样一来, 进程对管道是读还是写将由用户决定
选择读则关闭写, 选择写则关闭读
命名管道: 一类特殊的文件, 可以用于同一机器上的所有进程之间的通信, 拥有匿名管道的所有特性, 但是命名管道需要用户自己打开文件;
创建命名管道:
命令行创建: mkfifo [filename]
int mkfifo(const char* filename, mode_t mode)
第一个参数: 管道文件名称, 第二个参数文件访问权限
命名管道和匿名管道的区别: 打开方式不同, 命名管道需要自己调用 open 打开, 但匿名管道在调用 pipe 后, 自动打开;
命名管道的打开规则:
当以读的方式打开命名管道时, 阻塞知道有进程以写的方式打开文件; 同样的如果以写的方式打开命名管道, 则会阻塞知道有进程以写的方式打开
2) 消息队列
操作系统内核中的队列, 传输的是数据块, 这个数据块是有类型的
操作系统为消息队列维护了一个结构体, 这个结构体中有两个指针, msg_first 与 msg_last, 分别指向消息队列的首部和尾部
消息队列函数:
int msgget(key_t key, int msgflg);
功能: 用来创建和访问一个消息队列
参数: 第一个参数, 某个消息队列的名字, 第二个参数, 权限, 类似于文件的权限
返回值: 成功返回消息队列的标识码 (非负整数), 失败返回 - 1;
int msgctl(int msgqid, int cmd, struct msqid_ds *buf);
参数: 第一个参数: 由 msgget 函数返回的消息队列标识码, cmd: 将要采取的动作 (IPC_STAT, IPC_SET,IPC_RMID)
返回值: 成功返回 0, 失败返回 - 1
msgsnd: 此函数的作用, 将一条消息添加到消息队列中
msgrcv: 此函数表示将要从一个消息队列中接收消息
3) 共享内存
进程间通信最快的一种方式, 共享内存是在物理内存中开辟一段空间, 映射到自己的虚拟地址空间, 如果多个进程都进行了映射, 则这些进程可以通过共享内存进行通信
实现数据共享
因为共享内存是直接对映射到虚拟地址的物理地址进行操作, 少了两步由用户空间到内核空间, 再由内核空间到用户空间数据的拷贝过程, 因此是最快的;
共享内存的函数:
shmget: 用来创建共享内存
int shmget(key_t key, size_t size, int shmflg);
参数: key: 共享内存段的名字, size: 共享内存的大小, shmflg: 权限
shmat: 将共享内存段连接到进程地址空间
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数: shmid: 共享内存标识 shmaddr: 指定连接地址, 通常置为 NULL,shmflg:SHM_RND SHM_RDONLY(只读)
返回值: 成功返回指向共享内存的指针, 指向共享内存的第一个字节; 失败返回 - 1
shmdt: 解除映射关系, 将共享内存与当前进程脱离
int shmdt(const void* shmaddr);
参数: 由 shmat 返回的指针
shmctl: 用于控制共享内存
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数: shmid 为由 shmget 返回的共享内存表示码; cmd 为将要采取的动作; buf: 指向一个保存着共享内存模式状态和访问权限的数据结构
共享内存的操作步骤:
1. 创建共享内存
2. 将内存空间映射到虚拟地址空间
3. 通过虚拟地址对内存进行操作
4. 接触虚拟地址空间中的映射关系
5. 删除共享内存
6. 查看共享内存: ipcs -m
7. 删除共享内存: ipcrm -mid
8. 共享内存的使命周期随内核
4) 信号量
信号量 = 计数器 + 等待队列
信号量这个计数器实际就说明现在是否可以访问资源, 及表明当前有多少资源, 信号量 < 0, 表明需要等待, 当有资源时唤醒等待, 再进行操作
信号量的 PV 原语
互斥: P,V 在同一进程中
同步: P,V 在不同进程中
来源: https://www.cnblogs.com/love-you1314/p/10496984.html