【Linux】System V信号量与IPC资源管理简易讲解
本文承接上文:【Linux】System V共享内存实战:零拷贝加速进程通信!-CSDN博客
信号量基础概念与操作函数
信号量概念
信号量是一种用于进程间同步的机制,可以类比为一个计数器,用于控制多个进程或线程对共享资源的访问。System V信号量属于IPC(进程间通信)的一种,允许多个进程协调操作,防止竞态条件和数据不一致。
信号量操作函数
semget函数:用于创建或获取一个信号量集,函数原型为
int semget(key_t key, int nsems, int semflg);
,其中key
是信号量集的键值,nsems
是要创建的信号量数目,semflg
是权限标志。semop函数:用于对信号量集中的一个或多个信号量执行操作,函数原型为
int semop(int semid, struct sembuf *sops, unsigned nsops);
,其中semid
是信号量集的标识符,sops
是指向sembuf
结构数组的指针,nsops
是操作数目。semctl函数:用于控制信号量集,如获取或设置信号量值、删除信号量集等,函数原型为
int semctl(int semid, int semnum, int cmd, ... /* union semctl_arg arg */ );
,其中semid
是信号量集的标识符,semnum
是信号量编号,cmd
是控制命令。信号量的优缺点与最佳实践
优点
原子操作:
semop
函数可原子性地执行多个信号量操作,确保同步的可靠性。持久性:信号量在进程退出后仍存在,需显式删除,适用于需要长期存在的同步机制。
灵活性:支持多个信号量组成信号量集,可同时控制多个资源的访问。
缺点
复杂性:使用需要多个函数配合,且需处理多种错误情况,代码相对复杂。
性能开销:每次操作都需要与内核交互,频繁操作可能导致性能下降。
最佳实践
合理设置初始值:根据资源的可用数量初始化信号量值,如互斥访问时设为1。
避免死锁:在多个信号量操作中,确保所有进程按相同顺序操作,防止死锁。
及时删除信号量集:进程结束时显式删除不再需要的信号量集,避免资源泄漏。
结合共享内存使用:利用信号量保护共享内存中的数据访问,实现高效的数据共享与同步。
IPC指令操作
ipcs指令:用于显示和管理IPC对象(包括信号量、共享内存和消息队列)。例如,
ipcs -s
显示所有信号量集,ipcs -s -i <semid>
显示指定信号量集的详细信息。ipcrm指令:用于删除System V IPC对象。例如,
ipcrm -s <semid>
删除指定的信号量集。操作系统对IPC资源的统一管理
操作系统维护了一个可变长指针数组
struct kern_ipc_pern* p[0]
,数组中的每个指针分别指向共享内存、消息队列、信号量的结构体中的第一个成员。这种设计实现了对IPC资源(共享内存、消息队列、信号量)的统一管理,通过指针数组的偏移访问不同类型的IPC资源,体现了C语言中的多态特性。在操作系统中,通过遍历
kern_ipc_pern* p[0]
数组,检查每个结构体中的key
值来避免IPC资源的冲突。同时,ipc_perm
结构体中的mode
字段用于区分不同的IPC资源类型。
先理解一些概念:
公共资源:多个执行流(进程),能看到的一份资源:共享资源
临界资源:被保护起来的资源---保护的方式:同步和互斥 --- 用互斥的方式保护共享资源
互斥: 任何时刻只能有一个进程在访问公共资源
资源:一定需要被程序员访问 --- 资源被访问,就是通过代码访问
代码 = 访问共享资源的代码 (临界区)+ 不访问共享资源的代码(非临界区)
所谓的对共享资源进行保护 这个共享资源变成临界资源,本质是对访问共享资源的代码(临界区)进行保护
信号量概念:
信号量,信号灯是用于保护共享资源的,共享资源经过保护变成临界资源。
信号量:本质是一个计数器。对临界资源进行预先申请的计数器。申请成功,那么公共资源当中就一定有一部分是这个进程的。
将一个整体的公共资源,不再整体使用,分成许多块,在一个进程每个小块被不同的多个进程进行访问,允许不同的多个执行流进来访问同一个共享资源的局部性资源。在公共资源的基础上保证一定的并发度。信号量的本质:就是在这种情况的时候如何对对这些小资源的管理
类比:
看电影买票的本质
对资源的预定机制,对于电影院的一场电影本来只有25个座位,但是却卖出了26张票,最担心超过资源个数的卖票。在票不够的时候就需要等待。
电影院:共享资源(临界资源)
买票:申请信号量
票数:信号量的初始值
申请信号量的本质就是对公共资源的一种预定机制
因此当需要访问共享内存的时候
1.申请信号量
2.访问共享内存
3.释放信号量
现在存在着一个超级VIP厅(公共资源),只有一个座位,当一个人买下之后(申请信号量),在看电影的时候,任何人就不能再购买购买这个厅的电影票(任何进程再无法申请信号量)
这就是互斥的概念,公共资源整体被使用,在我访问期间,其余进程无法访问这个资源,这里的信号量计数器只会有1,0--->这就是二元信号量。
信号量分为:
二元信号量:把整个资源当成一个整体,在一个进程申请成功并访问的时候,其他进程无法申请信号量。 ---> 互斥
多元信号量:一个整体的资源被分成很多分信号量计数器 > 1
是否能使用全局变量gcount = 25来表示信号量,不可以
1.全局变量不能被所有的进程看到
2.gcount++--,非原子
原子:
平时洗澡的时候,有洗澡前,洗澡中,洗澡后,三个状态 ---> 非原子性
在做一些事情,要么就不做,要做就做完,没有任何中间状态 --->原子性
对于int a =10,此时开辟空间,将空间里的值赋为10,这个动作在开辟空间时,就已经初始化为10—> 这个在汇编语句上实际上只需要一句,这就是原子性,而++,--,会存在多条,首先需要把全局变量mov到cpu,在cpu内部再进行inc 加1,再写回内存,因此不具有原子性。
对于IPC信号量,就和共享内存一样,需要让不同的进程看到同一个“计数器”。
这意味着,信号量也是一个公共资源,用于保护临界资源安全的,前提是先要保护自己的安全。
保证信号量的安全
申请信号量 对应 --操作 保证信号量是安全的 因此被设置为原子操作 P操作
访问公共资源(共享内存)
释放信号量 对应 ++操作 保证信号量是安全的 因此被设置为原子操作 V操作
举例:
现在的P = 1,1号进程对共享资源进行访问P = 0,2号进程来访问,由于P = 0,这个进程就需要等待,1号进程退出V = 1,P = 0,这个时候2号进程才能对资源进行访问
信号量操作函数
1. semget函数:创建或获取信号量集
semget
函数用于创建一个新的信号量集或获取已存在的信号量集。在linux系统中,允许用户申请多个信号量,多个信号量就称之为信号量集,使用数组来维护的。其函数原型为:
int semget(key_t key, int nsems, int semflg);
key
:信号量集的键值,类似于共享内存的键值,用于唯一标识一个信号量集。可以通过ftok
函数生成。
nsems
:要创建的信号量数目。若获取已存在的信号量集,此参数应设为0。
semflg
:权限标志,指定对信号量集的访问权限,如0666
表示读写权限。若创建新信号量集,可使用IPC_CREAT
标志。
2. semop函数:执行信号量操作-对信号量集中的一个或多个信号量执行操作
semop
函数用于对信号量集中的一个或多个信号量执行操作,其函数原型为:int semop(int semid, struct sembuf *sops, unsigned nsops);
semid
:信号量集的标识符,由semget
返回。
sops
:指向sembuf
结构数组的指针,每个结构定义一个操作。
nsops
:操作数目,即sops
数组的元素个数。
sembuf
结构定义如下:struct sembuf { unsigned short sem_num; // 信号量编号 short sem_op; // 操作值 short sem_flg; // 操作标志 };
sem_num
:指定信号量集中的哪个信号量。
sem_op
:操作类型,若为负值表示等待(P操作),若为正值表示发送(V操作),若为0表示等待信号量变为0。
sem_flg
:操作标志,常用IPC_NOWAIT
(若操作不能立即完成则返回错误)和SEM_UNDO
(系统自动记录操作,进程异常终止时恢复信号量值)。
3. semctl函数:控制信号量集-获取或设置信号量值、删除信号量集
semctl
函数用于对信号量集执行控制操作,如获取或设置信号量值、删除信号量集等。其函数原型为:int semctl(int semid, int semnum, int cmd, ... /* union semctl_arg arg */ );
semid
:信号量集的标识符。
semnum
:信号量编号,指定操作针对的信号量。
cmd
:控制命令,常见的有:
GETVAL
:获取指定信号量的当前值。
SETVAL
:设置指定信号量的值。
IPC_RMID
:删除信号量集。
IPC_STAT
:获取信号量集的状态信息。
arg
:可选参数,根据cmd
不同而不同。
优点
原子操作:
semop
函数可原子性地执行多个信号量操作,确保同步的可靠性。持久性:信号量在进程退出后仍存在,需显式删除,适用于需要长期存在的同步机制。
灵活性:支持多个信号量组成信号量集,可同时控制多个资源的访问。
缺点
复杂性:使用需要多个函数配合,且需处理多种错误情况,代码相对复杂。
性能开销:每次操作都需要与内核交互,频繁操作可能导致性能下降。
最佳实践
合理设置初始值:根据资源的可用数量初始化信号量值,如互斥访问时设为1。
避免死锁:在多个信号量操作中,确保所有进程按相同顺序操作,防止死锁。
及时删除信号量集:进程结束时显式删除不再需要的信号量集,避免资源泄漏。
结合共享内存使用:利用信号量保护共享内存中的数据访问,实现高效的数据共享与同步。
IPC指令操作:
ipcs
:用于显示和管理 IPC对象(包括信号量、共享内存和消息队列)。
显示信号量集:
ipcs -s
该命令列出系统中所有的信号量集,显示其键值、信号量集ID、所有者等信息。
显示指定信号量集的详细信息:
ipcs -s -i <semid>
其中
<semid>
是信号量集的标识符,该命令显示信号量集的详细信息,如信号量数、权限、创建者等。
ipcrm
:用于删除System V IPC对象。
删除信号量集:
ipcrm -s <semid>
该命令删除指定的信号量集,需确保有足够权限。
OS是如何统一管理共享内存(shm),消息队列(msg),信号量(sem)
设计了一套标准来管理。
1.System V
2.XXXget,XXXctl
3.XXXid_+ds,struct ipc_perm
操作系统维护了一个可变长指针数组:struct kern_ipc_pern* p[0],数组中的每一个指针分别指向共享内存、消息队列、信号量,结构体中的第一个成员,也就是存的他们的第一个成员的地址,这意味着我们把IPC资源:共享内存(shm),消息队列(msg),信号量(sem)的管理统一了,变成了对kern_ipc的管理。他们结构体中所存的第一个成员变量,例如shmid也就是struct kern_ipc_pern* p[0]数组的下标。
观察到key所在的位置,在操作系统中,他是怎么知道这个ipc资源和其他的资源是否冲突?
只需要遍历kern_ipc_pern* p[0]数组,检查每个结构体当中的kern_ipc_pern当中的key值是否冲突。想访问不同ipc资源结构体中的变量时,直接强转指针数组里的指针,就能访问到。这就是使用C语言实现的多态。
这个数组的指针怎么知道自己指向的是哪一种ipc资源,因此ipc_perm里一定会有字段,指向目标类型,这个字段叫做mode。具体实现可以参照:这篇博客当中的设计传递位图标记位的函数。【Linux】 基础IO之操作与文件描述符fd全解析:从C语言到系统调用底层实现_io导出0kb文件-CSDN博客
结语:
随着这篇关于题目解析的博客接近尾声,我衷心希望我所分享的内容能为你带来一些启发和帮助。学习和理解的过程往往充满挑战,但正是这些挑战让我们不断成长和进步。我在准备这篇文章时,也深刻体会到了学习与分享的乐趣。
在此,我要特别感谢每一位阅读到这里的你。是你的关注和支持,给予了我持续写作和分享的动力。我深知,无论我在某个领域有多少见解,都离不开大家的鼓励与指正。因此,如果你在阅读过程中有任何疑问、建议或是发现了文章中的不足之处,都欢迎你慷慨赐教。
你的每一条反馈都是我前进路上的宝贵财富。同时,我也非常期待能够得到你的点赞、收藏,关注这将是对我莫大的支持和鼓励。当然,我更期待的是能够持续为你带来有价值的内容,让我们在知识的道路上共同前行。