愿你一生努力,一生被爱。
最想要的都拥有,得不到的都释怀!
目录
7. 进程间通信IPC
7.1 IPC介绍
7.1.1 定义
- 简单来说就是进程间的沟通交流。
7.1.2 实现原理
- 让多个进程通过访问到相同的缓冲区来实现通信,Linux 下遵循一切皆文件的思想。
7.1.3 本质
- 内核中的一块缓冲区。
7.1.4 操作系统为什么要为用户提供进程间通信方式
- (1)进程的独立性(每个进程都操作的是自己的虚拟地址空间中的虚拟地址,无法访问到别人的地址,因此无法直接通信);
- (2)IPC的应用场景不同,因此提供了几种不同的IPC方式。
7.1.5 进程间通信的目的
- (1)数据传输:一个进程需要将它的数据发送给另一个进程;
- (2)资源共享:多个进程之间共享同样的资源;
- (3)通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
- (4)进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。
7.1.6 IPC分类
- (1)管道
- (2)System V IPC
- (3)POSIX IPC
7.2 通信方式
7.2.1 管道(实现数据传输)
(1)分类
匿名管道pipe
- 定义
只能用于具有亲缘关系的进程间通信,子进程通过复制父进程的文件描述符获取管道的操作句柄。 - 实现原理
其实就是创建了一个子进程,子进程复制了父进程的描述符表,因此也就具有了两个描述符,并且他们指向的是同一个管道,这时候因为他们都能访问到这个管道,就可以实现进程间通信了。 - 接口实现
原型:int pipe(int fd[2]);
参数:fd 文件描述符数组,其中fd[0]表示读端,fd[1]表示写端
返回值:成功返回0,失败返回错误代码-1; - 说明
一个进程创建匿名管道,操作系统在内核中重建一块缓冲区,并且返回两个文件描述符的作为管道的操作句柄(一个用于读,一个用于写,方向的选择权交给用户,但是这个缓冲区在内核中没有标识)。
命名管道fifo
- 定义
命名管道可以应用于同一主机上的任意进程间通信,是一种特殊类型的文件即文件系统可见。 - 实现原理
一个进程创建命名管道,操作系统在内核中创建一块缓冲区,并且返回两个文件的描述符作为管道的操作句柄,但是匿名管道在内核中这块缓冲区中是有标识的,意味着所有的进程可以通过这个标识找到缓冲区实现通信;
命名管道的标识实际上是一个文件,可见于文件系统,意味着所有进程都可以通过打开文件进而访问到内核中的缓冲区。 - 接口实现
从命令行创建
$ mkfifo filename;
int mkfifo(const char* filename, mode_t mode);路径,权限
(2)本质
内核中的一块缓冲区
(3)特性
-
管道的读写特性;
-
半双工通信(有选择的单向通信);
-
自带同步与互斥(内核会对管道操作进行同步与互斥);
同步:保证操作的时序合理性;
互斥:保证操作在同一时间的唯一性; -
提供流式服务,传输灵活,但是存在粘包问题(即数据之间没有明显的间隔);
-
生命周期随进程, 一般而言,进程退出,管道释放;
(4)管道的读写规则
- 当管道没有数据可读时
如果描述符是默认的阻塞属性,读取将会阻塞挂起等待,直到管道有数据;
如果描述符被设置为非阻塞属性,读取操作将不具备条件,直接报错返回EAGAIN; - 当管道数据满的时候
如果描述符是默认的阻塞属性,写入操作将会阻塞挂起等待,直到有数据被取走;
如果描述符被设置为非阻塞属性,写入操作将不具备条件,直接报错返回EAGAIN; - 如果所有管道写端对应的文件描述符被关闭,则read返回0;
- 如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE,竟而可能导致write进程退出;
- 当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性;
- 当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。
7.2.2 共享内存(实现数据共享)
(1)本质
- 开辟一块物理内存,映射到各个进程的虚拟地址空间(通信进程都可访问)。
(2)最快的IPC
- 共享内存是直接将一块物理内存映射到虚拟地址空间中,因此进行数据传输的事就相较于其他通信方式,少了两步用户态与内核态之间数据拷贝的过程,因此共享内存是最快的IPC。
(3)操作流程(实现原理)
- 开辟共享内存
在物理内存中开辟一块内存空间; - 建立映射关系
将这块内存空间通过页表映射到进程的虚拟地址空间中; - 内存的数据操作
进程可以直接通过进程虚拟地址访问这块物理内存进行操作(若多个进程映射到同一块物理内存,就可以实现相互通信); - 解除映射关系
- 删除共享内存
(4)共享内存函数
1. shmget函数
- 功能:用来创建共享内存
- 原型:int shmget(key_t key, size_t size, int shmflg);
- 参数
key:这个共享内存段名字
size:共享内存大小
shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的。 - 返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1。
2. shmat函数
- 功能:将共享内存段连接到进程地址空间
- 原型:void *shmat(int shmid, const void *shmaddr, int shmflg);
- 参数
shmid:共享内存标识
shmaddr:指定连接的地址
shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY - 返回值:成功返回一个指针,指向共享内存第一个节;失败返回-1
3. shmdt函数
- 功能:将共享内存段与当前进程脱离
- 原型:int shmdt(const void *shmaddr);
- 参数
shmaddr:由shmat所返回的指针 - 返回值:成功返回0;失败返回-1
- 注意:将共享内存段与当前进程脱离不等于删除共享内存段
4. shmctl函数
- 功能:用于控制共享内存
- 原型:int shmctl(int shmid, int cmd, struct shmid_ds *buf);
- 参数
shmid:由shmget返回的共享内存标识码
cmd:将要采取的动作(有三个可取值)
buf:指向一个保存着共享内存的模式状态和访问权限的数据结构 - 返回值:成功返回0;失败返回-1
7.2.3消息队列(数据块的传输)
(1)本质
- 操作系统在内核为我们创建的一个队列,多个进程可以通过向队列中添加结点或获取结点来记录数据传输。
(2)特性
- 有类型的数据块
- 消息队列是一个全双工通信,可读可写(可以发送数据,也可以接受数据)
- 生命周期随随内核
创建消息队列(msgget)
发送数据、接受数据 (msgsnd/msgrcv)
释放消息队列 (msgctl)
(3)查看IPC
- ipcs -m 查看共享内存
- ipcs -s 查看信号量
- ipcs -q 查看消息队列
7.2.4 信号量(实现进程间的相互控制,实现同步互斥)
(1)本质
- 是内核中的一个具有等待队列的计时器(资源计数,统计现在有多少资源,用于判断是
否能够进行操作) - 信号量实际上也是一个临界资源
- 原子操作
(2)功能
- 实现进程间的同步与互斥;
(3)实现原理
- 在对资源进行访问之前,先判断信号量计数(是否有资源能够操作);
- 若计数<=0,则进行等待(等待别人创建资源),计数-1;等待在等待队列上;
- 若计数>0,则直接返回(按照程序流程就可以直接操作资源了),计数-1,其他进程创建了资源,计数+1,并且唤醒等待队列上的那些进程。