一,为什么需要进程通信?
进程之间具有独立性,每个进程有自己的虚拟地址空间。进程A并不知道进程B的虚拟地址空间中的数据内容。而它们之间由于需要进行以下的交互,如:数据传输,资源共享,通知事件,进程控制。而这些都需要两个进程能联系上才能够进行。
二,进程间通信方式
一,匿名管道
1,什么是管道
管道本质上就是一块内存(构成一个队列),是内核当中的缓冲区。使用一对文件描述符来进行访问这个内存,读文件描述符就是在队列中读取数据,写文件就是往队列中插入数据。
2,如何创建一个管道
int pipe(int pipefd[2]);
//参数是出参,意味着我们要传入一个int类型的数组,数组的大小是2,每个数组元素保存的是文件描述符。
//pipefd[0] :从 pipefd[0]这个文件描述符中去读
// pipefd[1] :从 pipefd[1]这个文件描述符中去写
//返回值:
//0-->成功
//-1------>失败
3,管道的大小
由于管道的实质就是一块内存,那我们需要计算的就是这块内存的大小,如果我们一直往里面写,而不读数据,最终管道会满,这样我们就知道管道的大小了。通过测算,管道的大小如下:
pipe_size:大概是65535字节-----64k
pipe_buf :
8*512=4096字节------4k 如果写入的数据大于4k,则不保证数据的原子性(原子性:当前操作不被打断,换句话说,在管道的读写操作是不可以被打断的)
4,匿名管道的特性
1.只能用于有亲缘关系的进程(父子进程,兄弟进程…)要处理同一块资源才能用管道
2.管道是一个半双工通信,数据只能有一个流向。
3.管道提供流式服务(字节流)
4.内核对管道操作存在同步与互斥机制(互斥:同-时间,保证只能有-一个进程访问临界资源。同步:保证临界资源访问的合理性。临界资源:同一时间,当前资源只能被一个进程所访问)
5.生命周期随进程,当所有引用管道的进程都结束了才能释放(引用计数)
6.如果管道为空,尝试读,就会在read函数处阻塞。
7. 如果管道满了,尝试写,就会在write函数处阻塞。
8.如果管道的读端被关闭,则写端往管道当中写数据的的时候,会造成管道破裂,从而导致进程收到SIGPIPE信号,从而进程终止
9.如果管道的写端被关闭掉了,则读端读完管道的数据之后, read不会陷入阻塞状态,是返回。执行代码的正常流程
二,命名管道
1.命名管道是具有标识符的管道(内核当中的内存,内存其实叫做缓冲区)
2.创建命名管道的两种方式
(1)使用命令去创建命名管道文件
mkfifo [命名管道文件名称] : 创建出来的文件类型是p
(2)使用函数去创建命名管道文件
mkfifo(char* pathname, mode_ _t mode)
当创建出来命名管道文件的时候,可以使用文件操作来访问命名管道, 可以通过命名管道文件来访问内核当中的缓冲区
3.特性:
(1) 具有了标识符,可以满足不同进程进行进程间通信
(2)生命周期跟随进程
三,共享内存
一,共享内存的原理:
共享内存本质上就是两个进程共用相同的内存。创建共享的时候,首先在物理内存当中创建一块内存,各个进程都通过页表结构将该段内存映射到自己的虚拟地址空间上的共享区,各个进程通过映射的地址来进行通信。
二,特性:
1,共享内存是最快的进程间通信的方式
2,共享内存是不带同步和互斥功能的
3,写入数据是按照覆盖的方式进行的
三,ftok函数
ftok生成key:
key_t ftok(const char *pathname, int proj_id);
四,共享内存的使用方式:
1,在内核中先创建出共享内存对象。(创建一块物理内存交给shmid管理)
int shmget(Key_t key , size_t size , int shmflg)
// 共享内存标识符 共享内存大小 IPC_CREAT IPC_EXCL
//IPC_CREAT:如果共享内存不存在,则创建共享内存,如果已经存在,则放回共享内存
// IPC_CREAT | IPC_EXCL:如果共享内存依然存在,则报错,如果不存在则创建
//加上权限:按位或上权限,权限相当于文件的权限,使用八进制的数字
//返回值: 成功返回共享内存的操作句柄
2,多个进程附加到这个共享内存对象上,shmat–>attach(将物理内存核虚拟内存建立联系),就可以直接读写这个共享内存了
void *shmat(int shmid , const void *shmaddr , int shmflg);
// 共享内存的操作句柄 映射到共享区的哪个地址 0--->可读可写 IPC_RDONLY--->只读
// char* p=(char*)shmat( shmid,NULL,0);
//返回值:返回映射到哪个地址上
3,分离进程和共享内存
int shmdt(const void * shmaddr);
// shmat返回的地址
//成功返回0,失败返回-1
4,共享给内存的销毁
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
//共享内存的操作句柄 要使用什么操作 出参,结构体当中是共享内存的一些信息
// cmd:
// IPC_STAT:获取当前共享内存的状态,要搭配shmid_ds一起使用
// IPC_RMID: 删除共享内存,标记共享内存给删除状态
五,共享内存的基本操作
ipcs -m 查看系统中的共享内存
ipcrm -m [shmid] :删除共享内存
六,共享内存的生命周期
共享内存的生命周期随内核.
如果删除了一个有进程附加的共享内存,操作系统的做法是,先标记当前共享内存为destroy的状态,并且将key设置为0x00000000,表示当前的共享内存不能再被其他进程所附加,同时也会释放资源。也就导致了正在附加到该共享内存上的进程有奔溃的风险,一般禁止这样去操作。当附加的进程退出的时候,操作系统就会将该共享内存清理掉。
四,消息队列
消息队列(带类型的队列):每次按照指定类型来出队列。出队列的时候按照指定类型来先进先出的。
消息队列服务器集群(中间件:和业务无关的基础设施 数据库、在线存储)
特性:生命周期跟随进程,如果用户进程不删除消息队列资源,则该资源一直在操作系统内核当中,自带同步与互斥
五,信号量
1,什么是信号量
信号量是一个计数器,主要是负责进程间的同步和互斥
这个计数器描述了可用资源的个数,通过信号量来判断当前资源是否可用
每次有进程想申请一个可用资源的时候,计数器-1 P(是对信号量进行±1操作)
每次有进程想释放一个可用资源的时候,计数器+1 V
如果计数器已经是0了,这个时候再有进程想申请资源:
1,进程会挂起等待
2,进程会放弃申请资源
2,信号量如何实现互斥:
1,实现互斥的时候,信号量只有两个取值,也就是只有0或1,0表示当前资源不可用,1表示当前资源可用。
2,当进程访问一个临界资源的时候,会先访问信号量,预计算信号量的值(先对当前的信号进行预-1操作,判断当前的信号是否小于0,如果小于0,则表示当前资源不可用,将当前进程放到PCB等待队列中去,如果等于0,访问临界资源,并将信号量-1)
3,如果已经访问完成,则需要结束对临界资源的访问,对信号量进行+1操作
4,-1操作称之为p操作 +1操作称之为v操作
3,信号量如何实现同步:
同步保证了合理性
这会描述的信号量是一个计数,是对资源的计数,信号量取值:资源的数量
前提:信号量的值不是0或者1了,信号量大于0的时候,表示有多少个资源可以使用,当信号量小于0的时候,小于0的数值的绝对值表示有多少个进程在等待资源。
如果需要访问一个资源的时候,对信号量进行-1操作,访问资源。如果不访问该资源的时候,信号量+1.
如果当当前的信号量的值是小于0的,表示当前PCB等待队列当中还有进程在等待资源,当一个进程结束对该资源的访问时,应当唤醒PCB等待队列当中的一个进程,