共享内存
本质原理: 多个进程要是映射同一块物理内存,就可以通过这块内存实现数据共享
- 在物理内存上开辟一块内存空间
- 多个进程可以将这块物理内存映射到自己的虚拟地址空间
- 通过虚拟地址进行页表映射直接访问这块物理内存,实现数据共享
特性:
- 最快的进程间通信方式
- 生命周期随内核
注意事项:
共享内存的操作是不安全的(并不会自动具备同步与互斥关系,需要操作用户进行控制)
操作: 代码操作流程/具体的接口+命令操作ipcs/ipcrm
为什么共享内存是最快的进程间通信方式?
对于管道这种通信方式,涉及到两次用户态与内核态之间的数据拷贝,将数据写入管道,从管道读取数据;对于共享内存这种通信方式,它是直接通过虚拟地址访问物理内存实现数据共享,相较于管道少了两次用户态与内核态之间的数据拷贝操作,因此速度最快。
注意: 共享内存没有同步和互斥特性,操作会存在安全隐患!!!
共享内存的生命周期:
随内核,在物理内存开辟空间,信息存储在内核;共享内存是属于内核的一个进程间通信的资源;不会随着进程的退出而退出;内核重启或者手动释放会释放该空间,否则一直存在于内核。
共享内存的操作流程:
- 创建共享内存(开辟物理空间—具有标识符)
- 将共享内存映射到各个进程的虚拟地址空间
- 直接通过虚拟地址进行内存操作
- 解除映射关系
- 删除共享内存
共享内存函数
- shmget函数
int shmget(key_t key, size_t size, int shmflg);
功能: 用来创建共享内存
参数:
- key: 内核中共享内存的标识符,多个进程通过相同的标识符可以打开同一块共享内存
- size: 共享内存大小 — 以内存页为单位进行分配
- shmflg: IPC_CREAT - 存在则打开,不存在则创建 | IPC_EXCL 与 IPC_CREAT 同时使用,若存在则报错,不存在则创建 | 权限
返回值: 返回一个非负整数 - 共享内存的操作句柄,失败返回-1
生成一个key值:
key_t futon(const char *pathname, int prom_id) - 通过inode节点号与projid合成一个key
- shmat函数
void *shmat(int shmid, void *shmaddr, int shmflg);
功能: 将共享内存段连接到进程地址空间
参数:
- shmid: shmget反回的共享内存标示
- shmaddr:映射到虚拟地址空间的首地址,通常置 NULL,(指定连接地址)
- shmflg: 映射成功之后对共享内存可以进行的操作 SHM_RDONLY-只读(前提是有读的权限)/ 0-默认可读可写
返回值: 成功返回共享内存映射的虚拟空间的首地址,通过这个地址对内存进行操作,失败返回-1
- shmdt函数
int shmdt(const void *shmaddr);
功能: 解除映射关系
参数:
- shmaddr:映射到虚拟地址空间的首地址(shmat所返回的指针)
返回值: 成功返回0,失败返回-1
注意:
当删除共享内存的时候,共享内存并不会立即被删除,(因为有可能会造成正在访问的进程崩溃),只是将其状态置为被销毁状态,移除标示。(表示这块共享内存不再继续接受映射链接,当这块共享内存的映射链接数为0的时候,则才会真正被删除)
- shmctl函数
int shmctl(int shmid,int cmd, struct shmid_ds *buf)
**功能:**用于控制共享内存
参数
- shmid:由shmget返回的共享内存标识码
- cmd:对共享内存想要进行的操作 — IPC_RMID-删除共享内存
- buf:用于获取/设置共享内存信息的结构,不使用则置NULL
返回值: 成功返回0;失败返回-1
操作系统中进程间通信资源的命令操作:
- ipcs 查看进程间通信资源
- -m 共享内存
- -q 查看消息队列
- -s 查看信号量
- ipcrm 删除进程间通信资源(ipcrm -m shmid)
消息队列
本质:
内核中的一个具有标识符的优先级队列,多个进程通过向同一个队列中添加节点和获取节点实现通信
- 消息队列提供了一个从一个进程向另外一个进程发送一块数据的方法
- 每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型值
特性:
- 自带同步与互斥
- 生命周期随内核
- 数据传输自带优先级
注意事项:
struct msgbuf{ int type; char buf[***]}; 这个结构体需要用户自己定义
msgget 创建队列
msgsnd 添加节点
msgrcv 获取节点
信号量
用于实现进程间的同步与互斥(共享内存本身是不提供同步与互斥的,操作存在安全隐患,因此需要使用信号量搭配)
- 互斥:保证同一时间只有一个进程访问临界资源实现临界资源的互斥访问保证安全性
- 同步:通过一种条件的判断,不能访问则等待,能访问再唤醒,实现对临界资源访问的合理性
本质:
内核中的计数器+pcb等待队列(对资源进行计数)
同步的实现: 信号量是一个对资源的计数,可以通过计数判断是否能够获取一个资源进行处理;若计数 <= 0,则表示不能获取,则需要等待(加入pcb队列),这时候若其他进程生产一个资源,再唤醒;若计数 > 0表示能获取
互斥的实现: 通过只有0/1的计数器,实现对临界资源访问状态的标记;在访问临界资源之前先获取信号量,计数-1;若计数<0则使进程等待(将进程pcb加入队列中);否则则可以对临界资源进行访问并且在访问完毕之后,则对计数+1,唤醒一个进程(将一个pcb出队,置位运行状态)
知识点习题
- 如果信号量的当前值为-4,则表示系统中在该信号量上有()个进程等待。
A. 4
B. 3
C. 5
D. 0
正确答案:A
答案解析:
信号量当前值为-(m-1),当前为-4,表示有5个进程共享同一临界资源,但是临界资源只能由一个进程享用,因此等待的进程数为4,其实等待的进程数就是(m-1)。
- 在互斥模型中,下列说法正确的是( )
A. 对同一信号的PV操作在同一进程
B. 对同一信号的PV操作在不同进程
C. 信号量S初始为1
D. 信号量S初始为0
正确答案: BC
答案解析:
PV操作:
- P操作和V操作是执行时不被打断的两个操作系统原语。执行P操作P(S)时信号量S的值减1,若结果不为负则P(S)执行完毕,否则执行P操作的进程暂停以等待释放。执行V操作V(S)时,S的值加1,若结果不大于0则释放一个因执行P(S)而等待的进程.
- P操作:也称为down()/wait()操作,使S=S-1,若S<0,进程暂停执行,放入信号量的等待队列.
- V操作:也称为up()/signal()操作,使S=S+1,若S<=0,唤醒等待队列中的一个进程.
- 下面那些机制可以用于进程间通信?
A. 信号量
B. 事件
C. 命名管道
D. 共享内存
E. 套接字
正确答案:A、C、D
- 使用PV操作实现进程互斥下列说法错误的是()
- 统一信号量的P、V操作成对出现,他们出现在不同的进程中
- 信号量的初值与P、V操作在程序中出现的位置有关
- 信号量的初值与其相应的资源无关
- P、V操作应分别紧靠临界区,不可以出现死循环
正确答案: C
答案解析:
PV操作是典型的同步机制之一。用一个信号量与一个消息联系起来,当信号量的值为0时,表示期望的消息尚未产生;当信号量的值非0时,表示期望的消息已经存在。用PV操作实现进程同步时,调用P操作测试消息是否到达,调用V操作发送消息。
使用PV操作实现进程同步时应该注意的是:
(1)分析进程间的制约关系,确定信号量种类。在保持进程间有正确的同步关系情况下,哪个进程先执行,哪些进程后执行,彼此间通过什么资源(信号量)进行协调,从而明确要设置哪些信号量。
(2)信号量的初值与相应资源的数量有关,也与P、V操作在程序代码中出现的位置有关。
(3)同一信号量的P、V操作要成对出现,但它们分别在不同的进程代码中。
利用信号量和PV操作实现进程互斥的一般模型是:
进程P1 进程P2 …… 进程Pn
…… …… …… ……
P(S); P(S); P(S);
临界区; 临界区; 临界区;
V(S); V(S); V(S);
…… …… …… ……
其中信号量S用于互斥,初值为1。
使用PV操作实现进程互斥时应该注意的是:
(1)每个程序中用户实现互斥的P、V操作必须成对出现,先做P操作,进临界区,后做V操作,出临界区。若有多个分支,要认真检查其成对性。
(2)P、V操作应分别紧靠临界区的头尾部,临界区的代码应尽可能短,不能有死循环。
(3)互斥信号量的初值一般为1。
- 以下那些关于信号量的说法是正确的?
A. 若进程崩溃退出,其信号量资源仍然存在。
B. 信号量死锁会导致CPU利用率飙升
C. 可用于多线程多进程临界区保护
D. 被信号量保护的临界区只能一个线程访问。
正确答案: CD
答案解析:
因为System V的信号量生命周期随内核,所以A错,信号量的等待采用的是阻塞等待,所以不会占用过多cpu资源
如果本篇博文帮助到了您,留个赞呐~~