进程间通信-信号量



信号量

简介:信号量与其他的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版本已修复

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值