💐 🌸 🌷 🍀 🌹 🌻 🌺 🍁 🍃 🍂 🌿 🍄🍝 🍛 🍤
📃个人主页 :阿然成长日记 👈点击可跳转
📆 个人专栏: 🔹数据结构与算法🔹C语言进阶🔹C++🔹Liunx
🚩 不能则学,不知则问,耻于问人,决无长进
🍭 🍯 🍎 🍏 🍊 🍋 🍒 🍇 🍉 🍓 🍑 🍈 🍌 🍐 🍍
文章目录
- 前言:
- 一、什么是临界区和临界资源?
- 二、SystemV信号量引出
- 三、什么是SystemV信号量?
- 四、 SystemV信号量的创建和控制以及操作
- 1.semget()函数:
- 2.semctl()函数:
- 3.semop()函数:
前言:
上一篇博客讲解了System V共享内存,在最后说它的缺点时提到,他没有提供进程同步机制,那么为了弥补这个缺点,所以引入了信号量sem机制。
一、什么是临界区和临界资源?
-
临界资源:多个进程共同使用的一份资源,例如共享内存就是一个临界资源
-
临界区:不同进程内部,访问临界资源的那段代码
二、SystemV信号量引出
- 打个比方:这场电影一共有五十个位置,设置一个信号量n,n = 50;看电影的人必须买票,买一张票信号量n就会减一,当n=0时,也就代表资源已经耗尽,没有位置了。但是如果有人退票的话,n会加1。买了票才能进入观影。
相应的,每一个进程想进入临界资源,访问临界资源的一部分,不能让进程直接去使用临界资源(不能让用户直接去电影院抢占座位),而是先得申请 信号量(先得买票)。
这样说的话,我们只需要一个int型的变量就可做到计数器的功能了,那还大费周章的提出一个信号量干嘛呢?
1.不是局部变量
- 这个变量肯定不是局部变量,那么使用一个全局变量可以吗?
2.不是全局变量
- 如果使用全局变量,父子进程在申请信号量时,会发生写时拷贝,导致这个变量父子进程各自一份,也不太行。
3.不在共享内存中
- 那就使用最近刚刚学习的共享内存不就好了吗。仔细思考也不可以。
- 首先共享内存也就是说变量直接存储在内存中。我们创建的几个程序执行时是需要将指令放入cpu中进行执行。
- 假设只看这个共享区的变量n,它的随着一个进程执行过程如下
1.将内存中的数据n加载到cpu的寄存器
2.n–(分析&&执行指令)
3.将cpu修改完毕的n写回内存。 - 一个进程执行流在执行的时候,在任何时刻都可能被切换 , 被切换的时候,会带走自己的上下文数据(包括n),然后再被切回来的时候,再把自己的上下文数据写入到cpu的寄存器中,继续执行。
- 这样就有了一个问题:
假设有10个进程,分别为1,2,3,4…,9,10,而临界资源一共只有5份,所以信号量n也为5.
假设1先申请信号量,但运气不好执行完第一步,就被切走了,然后2,3,4,5,6都正常申请了信号量,此时信号量已经为0,后面7到10的进程都不能再申请了。
但此时1号又开始继续执行,由于第一次进来时n是5,继续执行第2,3步,n–为4,然后再写回到内存,此时n从0变成了4,这不就差了吗,明明都没有资源了,结果信号量成了4,后面的进程又可以继续申请,这样肯定就出错了。
所以n减减时,因为时序问题导致n有中间状态,可能导致数据不一致、但如果n只有一行汇编,那么该操作就是原子的!
4.提出System V信号量
所以就提出了信号量机制
三、什么是SystemV信号量?
信号量的本质是:是一个描述临界资源的计数器
System V信号量是一种在操作系统中提供的进程间通信(IPC)机制,用于实现进程之间的同步和互斥。它通过对计数器进行操作来控制资源的访问。
System V信号量由一个整型的标识符(semaphore identifier)来标识,每个标识符对应着一个信号量集合
(semaphore set)。信号量集合中可以包含多个单独的信号量,每个信号量都有一个非负整数值。
四、 SystemV信号量的创建和控制以及操作
分别对一个semget()、semctl()和semop()
头文件:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
1.semget()函数:
首先回顾一下ftok():
ftok()函数生成key
头文件:
#include <sys/types.h>
#include <sys/ipc.h>
格式:
key_t ftok(const char *pathname, int proj_id);
- 参数:
pathname指针
:一个字符串,用于标识一个文件的路径名。通常会选择一个已经存在的文件,因为 ftok() 函数将使用该文件的inode编号和 proj_id 参数通过算法来生成键值key。
proj_id
:一个整数,作为用于生成键的项目标识号。该参数通常取一个非负整数。 - 返回值:成功则返回生成的键值,否则返回-1。
格式:
int semget(key_t key, int nsems, int semflg);
参数:
-
key:唯一key值,用于标识要创建或获取的信号量集合。
key值可以使用ftok()
获取 -
nsems
:指定信号量集合中信号量的数量
。你想创建几个信号量。可以想象成一个数组。 -
semflg:标志参数,用于指定信号量的创建方式和访问权限。有下面两个
选项
IPC_CREATE | 创建共享内存,如果底层已经存在,则获取并返回;如果不存在,则创建共享内存然后再返回, |
---|---|
IPC_CREATE | IPC_EXCL | 如果底层不存在,则创建共享内存并返回;如果底层存在,则出错返回。言外之意,如果返回成功,那么一定是一个全新的内存块! |
使用:
通过指定一个键值和其他参数,调用semget()函数可以创建一个新的信号量集合,或者获取一个已经存在的信号量集合。
返回值:
返回值是一个信号量标识符
(semaphore identifier),它用于后续对信号量集合的控制和操作
注意理清一个概念:semget()可以一次申请多个信号量(n1,n2.n3,),其中一个信号量n1就是一个计数器
2.semctl()函数:
int semctl(int semid, int semnum, int cmd, ...);
参数:
- semid:信号量标识符,用于指定要操作的信号量集合。
- semnum:注意与seget()区分清楚。指定具体的信号量在集合中的
索引
,用于标识要操作的信号量。 - cmd:执行的控制命令,用于指定具体的操作。
- arg:根据不同的命令,需要提供的参数。
下面是semctl函数cmd形参说明表
命令 | 解 释 |
---|---|
IPC_STAT | 从信号量集上检索semid_ds结构,并存到semun联合体参数的成员buf的地址中 |
IPC_SET | 设置一个信号量集合的semid_ds结构中ipc_perm域的值,并从semun的buf中取出值 |
IPC_RMID | 从内核中删除信号量集合 |
GETALL | 从信号量集合中获得所有信号量的值,并把其整数值存到semun联合体成员的一个指针数组中 |
GETNCNT | 返回当前等待资源的进程个数 |
GETPID | 返回最后一个执行系统调用semop()进程的PID |
GETVAL | 返回信号量集合内单个信号量的值 |
GETZCNT | 返回当前等待100%资源利用的进程个数 |
SETALL | 与GETALL正好相反 |
SETVAL | 用联合体中val成员的值设置信号量集合中单个信号量的值 |
用法:
semctl()函数用于控制和管理信号量集合。
可以通过指定不同的控制命令(cmd)来实现不同的操作,例如设置
信号量的初始值
、获取或改变
信号量的值,以及删除信号量集合
等。
具体的参数(如arg)根据不同的命令而有所不同。
3.semop()函数:
int semop(int semid, struct sembuf *sops, unsigned nsops);
参数:
- semid:信号量标识符,用于指定要操作的信号量集合。由seget()获取
- sops:指向一个sembuf结构体数组的指针,包含了一组操作。此结构的具体说明如下:(
里面的注释很重呀
)
struct sembuf {
short semnum; 指定要操作的信号量在集合中的索引,从0开始计数。
short op;指定要执行的操作。
如果sem_op的值大于0,则表示进行V(释放)操作,即增加信号量的值。
如果sem_op的值小于0,则表示进行P(等待)操作,即减少信号量的值。
如果sem_op的值等于0,则表示进行Z(零)操作,如果信号量的值为0,则等待。该操作通常用于同步操作,以等待某个特定条件的发生。
short flag; 用于指定操作的标志。
IPC_NOWAIT:如果无法进行操作(例如信号量的值为0且sem_op为负数),则立即返回,不进行等待。
SEM_UNDO:系统在进程意外终止时,会自动撤销该进程对信号量的操作,以避免死锁。
};
- nsops:指定操作的数量。
作用: 用于对指定信号量集
【semid】当中的指定数量的信号量
【nsops:常常设置为1】,对它做操作
【sembuf *sops】