进程间通信-信号量 秒速五厘米 2022-08-03 14:44 139阅读 0赞 信号量 简介:信号量与其他的ipc(管道 fifo 消息队列)不同,他是一个计数器,来计数可以访问共享资源的进程数 信号量操作 1.创建一个信号量 2.对信号量进行初始化 3.测试控制共享资源的信号量 4.若信号量为正则进程可以使用该资源信号量减一 5.若信号量为0则进程阻塞(如果设置成非阻塞则出错返回)直至信号量变正.信号量唤醒,返回第3步执行 6.若进程不再需要使用共享资源,则信号量加1,如果有进程在阻塞等待该资源,那么该进程被唤醒使用该资源 注意:当多个线程在等待信号量变为非0时,这时信号量变为非0,多个等待线程的启动顺序不定。 实现过程 首先调用semget来创建一个信号量集,然后调用semctl来对信号量进行初始化,最后调用semop对信号量进行操作。 注意:SEM\_UNDO标志,如果多个进程阻塞在等待某个进程释放它占用的信号量,而该进程已经结束,那么其他几个进程会一直阻塞。这时可以用SEM\_UNDO标志,该标志可以杜绝此类现象。 创建的信号量需调用命令删除,不会随进程结束而删除,如果多个进程阻塞在等待某个信号量,指定参数IPC\_RMID 调用semctl删除信号量,则会把所有阻塞等待该信号量的进程唤醒,然后返回错误提示 信号量已删除 系统对于信号量限制情况 文件/proc/sys/kernel/sem内容是关于信号量限制,一般格式如下显示: 250 32000 32 128 250表示信号集中最大信号量数目 32000信号量最大数目 32每个信号最大可被操作的进程数目 128最大信号集数目 函数名 semget - get a semaphore set identifier semget 获取信号量的标识 调用格式 \#include <sys/types.h> \#include <sys/ipc.h> \#include <sys/sem.h> int semget(key\_t key, int nsems, int semflg); 函数返回指定key的信号量的标识,如果参数key设置为IPC\_PRIVATE或者semflg为IPC\_CREAT且对应key的信号量不存在则会创建一个新的信号量,如果semflg设置为IPC\_CREAT和IPC\_EXCL但是key 对应的信号量已经存在,则semget出错返回errno设置为EEXIST。与open中组合O\_CREAT | O\_EXCL情况相似。 参数semflg低9位定义成信号量的读写权限,这些位的格式以及含义与open函数的设置模式的参数一样 (执行权限对于信号量不可用). 新创建的信号量的值是不确定的,尽管在linux中,系统会把信号量的值初始化为0,但是不能依赖这个不确定的特性,必须调用相关函数来明确的给信号量赋值 当创建一个信号量时,函数semget会初始化semid\_ds 结构体: sem\_perm.cuid 和 sem\_perm.uid会被初始化为调用进程的有效用户id sem\_perm.cgid 和 sem\_perm.gid初始化为调用进程的组id sem\_perm.mode低9位会初始化为semflg低9位的值 sem\_nsems值等于nsems sem\_otime初始化为0 sem\_ctime初始化为当前时间 获取一个信号量时,参数nsems设置成0(不关注) 创建一个信号量,参数nsems取值范围大于0且小于或等于信号量最大值 如果信号量存在且具有对该信号量的相应权限,返回值 1.成功 返回信号量的标识id 2.失败 返回-1,设置error EACCES key对应的信号量存在,但是调用进程没有访问该信号量的权限,并且不具备绕过系统操作信号量的权限 EEXIST key对应的信号量存在,且参数semflg设置为IPC\_CREAT | IPC\_EXCL EINVAL 参数nesms值小于0或者大于信号量的边界值,或者信号量已经存在且参数nesms大于原先设置的值 ENOENT 对应的key的信号量不存在,且参数semflg没有指定IPC\_CREAT ENOMEM 系统没有更多的内存来创建新的信号量 ENOSPC 创建的信号量已经超过系统对信号量数的最大限制值了 注意:IPC\_PRIVATE 不是标志位而是key\_t的类型,如果使用这个作为参数,则创建一个新的信号量忽略其他的参数除了semflg低9位的权限位,参数key为IPC\_PRIVATE具有不确定性,最好用IPC\_NE代替 ,semget()并不会对创建的信号量进行初始化,需要调用semctl函数SETVAL 或者 SETALL 来进行初始化,当多个信号量不知道那个需要初始化的时候,可以通过semctl的IPC\_STAT操作来获取属性结构体 检查sem\_otime是否为非0 函数 semctl - semaphore control operations semctl-对信号量进行操作控制 调用格式 \#include <sys/types.h> \#include <sys/ipc.h> \#include <sys/sem.h> int semctl(int semid, int semnum, int cmd, ...); 简介 semctl通过参数semid和semnum对指定的信号量来进行操作控制,semid是信号量集的标识,semnum是信号量集中信号量的标识,信号量集中信号量标识是从0开始的 函数有3个还是4个形参,依赖于参数cmd,如果有第四个参数,那么第四个参数的类型是union semun,调用进程必须如下定义这个结构体: union semun \{ int val; /\* 信号量的值\*/ struct semid\_ds \*buf; /\* 用于IPC\_STAT, IPC\_SET \*/ unsigned short \*array; /\* GETALL, SETALL \*/ struct seminfo \*\_\_buf; /\* IPC\_INFO(Linux-specific) \*/ \}; 结构体struct semid\_ds定义如下: struct semid\_ds \{ struct ipc\_perm sem\_perm; /\* 权限和用户信息 \*/ time\_t sem\_otime; /\* 最后一次semop操作时间\*/ time\_t sem\_ctime; /\* 最后一次修改时间\*/ unsigned short sem\_nsems; /\* 要设置的信号量的标识 \*/ \}; 结构体struct ipc\_perm定义如下: struct ipc\_perm \{ key\_t \_\_key; /\* 信号量的key\*/ uid\_t uid; /\* Effective UID of owner \*/ gid\_t gid; /\* Effective GID of owner \*/ uid\_t cuid; /\* Effective UID of creator \*/ gid\_t cgid; /\* Effective GID of creator \*/ unsigned short mode; /\* 读写权限\*/ unsigned short \_\_seq; /\* 序号 \*/ \}; 参数cmd可设置的值: IPC\_STAT 获取semid的信号量的相关结构体struct semid\_ds,调用进程必须具有对该信号量读权限,参数semnum忽略 IPC\_SET 更新结构体struct semid\_ds的值,参数semnum忽略 IPC\_RMID 删除信号量,并唤醒所有调用semop而阻塞的进程(semop会返回EIDRM),删除信号量的进程的用户id权限须大于或等于信号量的用户id权限,或者是具有特权的,参数semnum是会被 忽略的 IPC\_INFO 返回系统关于信号量的限制,结构体定义如下: struct seminfo \{ int semmap; /\* 可用信号量数 \*/ int semmni; /\* 最大可设置的信号量集数目\*/ int semmns; /\* 最多可设置的信号量数目\*/ int semmnu; /\* 最大可用于undo设置的数目 \*/ int semmsl; /\* 信号量集中最多信号量数目\*/ int semopm; /\* 最大数用于semop操作 \*/ int semume; /\* 每个进程可取消的信号量最大数 \*/ int semusz; /\* struct sem\_undo结构体大小 \*/ int semvmx; /\* 最大信号量的值 \*/ int semaem; /\* 最大的信号量可调整的值 \*/ \} SEM\_INFO (只限于linux)返回结构体seminfo,和IPC\_INFO返回的信息一样,除了以下几个结构体成员的值不一样: 成员semusz的值为当前系统存在的信号量的个数,成员semaem 返回所有系统中信号量集的信号量个数 SEM\_STAT (只限于linux) 和参数IPC\_STAT类似,返回结构体semid\_ds。但是参数semid不单单指一个信号量标识,而是一个内部关于所有的信号量的信息数组中的标识 GETALL 返回当前系统设置的所有信号量的值(数组形式),参数semnum无效,调用进程必须有对改信号量读权限 GETNCNT 返回等待指定的信号量的值增加的进程数,调用进程必须对信号量具有读权限 GETPID 返回最后一次通过semop操作指定的信号量的进程id,调用进程对信号量要有读权限 GETVAL 返回指定的信号量的值,调用进程必须对信号量有读权限 GETZCNT 返回等待指定信号量的值变为0的进程数,调用进程对信号量须有读权限 SETALL 设置信号量集中所有信号量的值(数组形式),同时也会更新结构体semid\_ds中sem\_ctime的值,修改信号量时,所有被进程能使用的信号量都会无效。对信号量值修改时允许其他进 程阻塞在对该信号量的操作上,修改完成唤醒这些阻塞的进程。参数semnum无效。调用进程必须具有对信号量写权限。 SETVAL 设置信号量集中指定semnum信号量的值,同时会更新semid\_ds结构体中的sem\_ctime, 返回值 失败返回-1,设置error 成功返回大于或等于0 返回值大于0依赖参数cmd的设置 失败原因 EACCES 参数cmd的值是GETALL, GETPID, GETVAL, GETNCNT, GETZCNT, IPC\_STAT, SEM\_STAT, SETALL, or SETVAL其中一个,但是调用进程既不是超级用户,也没有对指定信号量具有相应的权限 EFAULT 参数arg.buf和arg.array地址无效 EIDRM 操作的信号量已被删除 EINVAL 参数cmd 或semid的值无效。或者在SEM\_STAT操作中,指定的信号量数组下标当前是未使用的 EPERM 参数cmd的值为IPC\_SET or IPC\_RMID,调用进程不是创建信号量的进程或父进程,且调用进程没有超级用户权限 ERANGE 参数cmd的值为SETALL或者SETVAL,信号量的值小于0或者大于边界值 注意 参数IPC\_INFO, SEM\_STAT,SEM\_INFO是用来命令ipcs程序提供当前系统环境信息 在早些glibc版本中,联合semun定义在sys/sem.h,但是在POSIX.1-2001指出需调用这自己定义联合体semun 信号量值默认最大为32767,具体依赖于实现 semctl四个参数最好都有指定,这样做是为了更好的移植性 函数 semop, semtimedop - semaphore operations 信号量操作函数 函数调用格式 \#include <sys/types.h> \#include <sys/ipc.h> \#include <sys/sem.h> int semop(int semid, struct sembuf \*sops, unsigned nsops); int semtimedop(int semid, struct sembuf \*sops, unsigned nsops, struct timespec \*timeout); 概述 在信号量集中的每个信号量都有如下属性值: unsigned short semval; /\*信号量的值\*/ unsigned short semzcnt; /\* 等待信号量值变为0的进程个数\*/ unsigned short semncnt; /\*等待信号量值大于0的进程个数\*/ pid\_t sempid; /\*最后一次操作信号量的进程id\*/ semop() 操作参数semid指定信号量集,参数nsops指定参数sops的数组大小,参数nsops的结构体类型如下: unsigned short sem\_num; /\* 信号量集中的信号量标识 \*/ short sem\_op; /\* 信号量操作 \*/ short sem\_flg; /\* 操作标志\*/ sem\_flg 可设置的值有IPC\_NOWAIT 和SEM\_UNDO。如果指定SEM\_UNDO, 进程结束时会自动释放占用的资源 对信号量操作的信息包含在参数sops的数组中,是原子操作(不会被中断)。对信号量的操作是否会立马执行依赖于有没有设置sem\_flg = IPC\_NOWAIT标志。 每个操作都是针对信号量集中的信号量,信号量集中第一个信号量的标号是0,根据sem\_op值不同有三种操作类型: sem\_op>0,信号量的值加上sem\_op,如果设置了SEM\_UNDO,系统会更新该信号量的进程相关数目(该信号有多个进程设置了相关sem\_undo属性的,当进程结束,会释放该信号量),这个操作是立 即执行的不会阻塞,调用进程需对该信号量具有写权限 sem\_op=0,进程对信号量必须具有读权限。如果信号量值semval = 0,这个操作会立即执行。semval!=0, 如果sem\_flg设置为IPC\_NOWAIT,则semop()返回失败EAGAIN。如果sem\_flg没有设置 IPC\_NOWAIT,等待信号量值为0的进程数增1, 且进程阻塞直到以下几种情况发生: ①信号量值变为0 与此同时等待信号量值为0的进程数减1 ②信号量被删除 则semop失败返回EIDRM. ③调用进程获取到一个信号 等待信号量值为0的进程数递减1,semop失败返回EINTR ④semtimedop()指定的超时时间已到,semop()失败返回EAGAIN sem\_op<0, 进程必须对信号量具有写权限,如果信号量的值大于或等于sem\_op的绝对值,那么函数会立即执行,信号量值会减去sem\_op的绝对值,如果sem\_undo设置了,那么系统会更新该信号量 的进程释放数(进程结束,该进程用到的信号量会被释放)。如果信号量的值小于sem\_op的绝对值,设置了IPC\_NOWAIT,semop() 失败返回EAGAIN 。如果没有设置IPC\_NOWAIT那么等待该信号量资源的进 程数增1,且阻塞知道以下几种情况: ①信号量的值变为大于或等于sem\_op的绝对值,相应的等待该信号量资源的进程数减1,信号量的值=信号量的值-sem\_op,如果指定了IPC\_NOWAIT, 则更新进程关联信号量释放数目 ②信号量被删除 semop()失败返回 errno为EIDRM ③调用进程捕捉到一个信号,阻塞等待信号量的进程数减1,semop()失败返回,errno=EINTR ④semtimedop()设置的超时时间到期,失败返回,errno设置为EAGAIN 成功返回,sem\_otime则会被设置成当前时间 semtimedop()类似semop(),他可以通过参数timeout来指定进程睡眠时间。如果指定的时间到达,那么semtimedop()返回失败,errno设置为EAGAIN,如果参数timeout为空,semtimedop()等同于 semop()。 返回值 成功返回0.失败返回-1,并且设置error的值,含义如下: E2BIG 参数nsops的值大于SEMOPM,SEMOPM即系统每次调用允许最多操作的数量 EACCES 进程没有权限去操作信号量 EAGAIN 调用设置了IPC\_NOWAIT或者超时时间已到 EFAULT 参数sops或timeout无效 EFBIG sops结构体中成员sem\_num小于0或者大于等于信号量集中的信号量个数 EIDRM 信号量已删除 EINTR 进程阻塞于调用该函数,但是进程此时捕捉到一个信号 EINVAL 要操作的信号量不存在,semid小于0,nsops是个负值 ENOMEM 参数sem\_flg指定了SEM\_UNDO,但是系统没有更多的内存来存储相关信息 ERANGE sem\_op+信号量值大于创建时设置的值 semtimedop()最先实现出现在Linux 2.5.52,同时也支持内核2.4.22。Glibc最早支持该函数是2.3.3 注意: 1.结构体sem\_undo在父子进程(fork)之间是不会继承的,但是调用execve()来创建子进程,则会继承。 2.semop()操作执行时被中断,中断后,它是不会重新启动执行的,即使设置了SA\_RESTART(该标志来表明系统调用被信号中断,中断处理程序完成后,重新调用被中断的函数)标志 3.每个进程都有一个semadj的数值,它表示该进程semop操作了设置为SEM\_UNDO 标志的信号量个数。如果进程调用了semctl,设置了SETVAL或者SETALL,semadj值被清0 4.每个信号量semval, sempid, semzcnt,semnct的值都可以通过调用semctl获取 系统限制: 1.SEMOPM 在semop操作中,最多允许操作的信号量数 2.SEMVMX 最大允许的信号量值,默认32767 BUGS 进程结束时,该进程关联的结构体semadj会释放所有该进程上使用到的有设置SEM\_UNDO标志的信号量。有可能会导致当一个或多个在操作该信号量时可能会是信号量值变为负数。有三种方法来避 免这种情况:1.阻塞直到所有该信号量的操作完毕才执行释放操作。这种方法不可取,因为它会阻塞进程终止任意长的时间 2.当进程终止时,信号量值调整的操作都会被忽略,出错返回。(类似操作信号量,设置了IPC\_NOWAIT标志) 3.进程终止时尽可能快的执行信号量递减操作,原子性。linux中采用的是这种方法 在内核2.6.x(x<=10)之前,即使信号量的值已经变为0了,但是在阻塞等待该信号量变为0的进程还是一直阻塞等待。在内核2.6.11版本已修复
还没有评论,来说两句吧...