文章目录
一.进程间通信是什么?
两个或多个进程实现数据层面的交互。
因为进程具有独立性,导致进程通信成本较高。
- 进程通信的本质:必须让不同的进程看到同一份“资源”.
- 这个资源的提供,一般是操作系统,不能是通信的进程中的一个。为什么呢?假设一个进程提供这个资源,这个资源归该进程独有。
- 一般操作系统会有一个独立的通信模块,它隶属于文件系统——IPC通信模块
- 进程间通信是有标准的:systeamV && posix
二.管道
管道是基于文件级别的通信方式
1.匿名管道
匿名管道只能用于具有血缘关系的进程通信,常用于父子。
匿名管道的生命周期随进程,当通信的进程都退出,管道随即被操作系统回收.
匿名管道的原理
- 匿名管道本质就是文件的页缓冲区。
- 当我们父进程打开文件后,就有对应的文件描述符表端口指向打开的文件。
- 当父进程fork创建子进程后,子进程会把父进程的这些描述父进程的结构体包括文件描述符表也拷贝一份。
- 此时父子进程就可以看到同一份“资源”了。
- 管道虽然用的是文件的方案,但操作系统一定不会把进程进行通信的数据刷新到磁盘当中,因为这样做有IO参与会降低效率。
- 我们把这种文件叫做内存级文件。
系统接口
- pipefd是一个有两个int元素的数组,它是一个输出型参数,它会把文件的文件描述符下标带出来,让用户使用.
- 管道只能进行单向通信,所以我们只能让一个进程进行写操作,另一个进程进行读操作。
- pipefd[0]:读下标
- pipefd[1]:写下标
- 由于管道属于文件,所以我们还是使用write和read来写入和读取数据。
2.命名管道
命名管道可以用于两个毫不相关的进程进行通信。
- 通信的本质时让进程看到同一份资源。
- 命名管道是通过路径和文件名来确定唯一性的。通过这个唯一性来让不同的进程看到这同一份资源。
- 可以通过系统接口和指令两种方式来创建命名管道
系统接口
- 第一个参数是一个路径+你要创建匿名管道的文件名
- 第二个参数是文件的权限.
指令
- 使用mkfifo [filename] 来创建命名管道
- 使用unlink接口删除命名管道
- 也可以使用unlink指令来删除。
- 管道的通信本质是通过内核中一块缓冲区(内存)时间数据传输,而命名管道的管道文件只是一个标识符,用于让多个进程能够访问同一块缓冲区。可以看到命名管道的文件一直为0.
3.管道的特征
- 具有“血缘关系”的进程进行通信。命名管道可以实现任意进程通信,它通过文件标识符来确定唯一性。
- 管道只能单向通信,如果想要双向通信:建立多个管道.
- 父子进程是会进程协同的,同步与互斥——保护管道文件的数据安全.
- 管道是面向字节流的
- 管道是基于文件的,而文件的生命周期是随进程的。
4.管道的四种情况
- 读写端正常,管道如果为空,读段会阻塞
- 读写段正常,管道如果被写满,写端就阻塞
- 读段正常读,写端关闭,读段就会读到0,表明了读到文件pipe结尾,不会阻塞。我们可以在写端read返回值判断。为0就break
- 写端正常,读段关闭,操作系统就要杀掉正在写入的进程。通过13号信号杀掉.
5.补充
- 管道是由固定大小的
- 在2.6.11之前管道是4KB
- 从2.6.11开始管道是64KB
- pipe_BUF为4KB
- 不同的内核里,大小有差别。
三.systeamV共享内存
1.原理
- 首先通过操作系统(系统调用)在物理内存上申请一块内存
- 再把这块内存的地址挂接到我们的进程地址空间上。它会映射到共享区中
- 在通过页表的映射,这样两个进程就可以看到同一份资源了。
2.系统接口
- 要想让进程通信,前提是让他们看到同一份资源,匿名管道是通过父子进程结构体相同做到的,命名管道是通过路径+文件名来确定同一份资源的。那么我们的共享内存就是通过这里的第一个参数key来确定的。
- key值需要通过ftok函数来生成。
- 第一个参数为一个路径,第二个参数为项目ID(提供一个int类型的数据即可).
- ftok是一套算法,通过我们提供的这两个参数进行数值计算,以此来生成key值,key为int类型整数.
- 这里的连个参数可以理解为用户共同约定的,及通信双方进程约定的,通信双方进程提供相同的参数来生成出相同的key。
- 那么他们就会访问到同一个共享内存.
1.申请共享内存
- 这里的第二个参数为共享内存的大小(字节),我们一般把这个大小设置成4096的整数倍,如果我们给这里的参数设置为4097,操作系统会给你申请4096*2字节的空间,但是你还是只能访问你的4097字节的大小.
- 第三个参数:
- IPC_CREAT:如果申请的内存不存在,就创建,存在,就返回这块共享内存。
- IPC_CREAT|IPC_EXCL:不存在就返回,存在就出错返回,它可以确保我们创建的共享内存是最新的。这里也可以|上权限值,代表共享内存的权限.
- shmget的返回值是shmid,它是我们用户层面来表示资源唯一性的,我们在调用接口对共享内存进行操作时时就是用的这个shmid,
- key则是我们操作系统内标定唯一性的.
2.挂接到进程地址空间
- 它的返回值为挂接到共享去的虚拟地址空间。我们往这个地址写入,即可进行通信。
- 第一个参数为shmid,及shmget的返回值。
- 第二个参数为你要共享内存挂接到共享区的什么位置,我们给NULL,让操作系统指定
- 第三个参数为你想按什么权限挂接,(只读/只写),我们默认给0
如果我们要释放共享内存:
1.去关联
2.释放共享内存
3.去关联
- 这里需要传入shmat的返回值,即映射到地址空间的起始地址。
- 因为描述这个共享内存的结构体中有对应这个共享内存的大小,共享内存是连续的,所以我们只需要传入其实位置。
4.释放共享内存
- 第一个参数传入你要释放的共享内存的shmid
- 第二个参数传入IPC_RMID,代表删除
- 第三个参数传入管理共享内存的数据结构,用以修改它的属性,因为我们是要删除这个共享内存,所以我们不在乎它的属性,传入NULL
3.共享内存的特性
- 共享内存没有同步互斥之类的保护机制
- 共享内存是所有进程间通信中,速度最快的。原因是拷贝少。
- 共享内存内部的数据,由用户自己维护。
可以使用管道为共享内存添加同步特性。
- 共享内存的生命周期是随内核的。
- ipcs -m 查看所有的共享内存资源
- 我们可以使用ipcrm -m [shmid] 来删除共享内存资源
四.IPC在内核中的数据结构设计
在操作系统中,所有的IPC资源,都是整合进操作系统的IPC模块中的。
- 共享内存/消息队列/信号量这三个结构体中都有一个相同的结构体,里面存放着管理资源的属性。
- 这三种资源都是放进一个数组里面存放的,这个数组的下标就是我们的xxxid(shmid/msqid/semid)。
- 这个xxxid它是线性增长的,当我们申请一个资源他就会往后增长,所以我们看到的id值都会很大,当这个id值到数组的最大值后,他会回绕到起始下标。
五.信号量
对于共享内存,当我们的进程A正在写入,写入了一部分,就被进程B读走了,导致双方发和收的数据不完整——数据不一致问题。
- A和B看到的同一份资源,如果不加保护,会导致数据不一致问题。
- 加锁——互斥访问,任何时刻,只允许一个执行流访问共享资源——互斥
- 任何时刻只允许一个执行流(就是执行访问代码)访问的资源——临界资源
- 举例:100行代码,只有5-10行代码才访问临界资源,我们访问临界资源的代码——临界区
理解信号量
- 信号量本质是一把计数器,PV操作,原子的。
- 执行流访问资源,必须先申请信号量资源,得到信号量之后,才能访问临界资源。
- 信号量为0/1两态的,二元信号量,就是互斥功能。
- 申请信号量的本质:是对临界资源的预定机制。
信号量为什么是进程通信的一种?
- 通信不仅仅是通信数据,互相协同也是。
- 要协同,本质也是通信,信号量首先要被通信进程看到。