Linux 中用到的信号量有 3 种:ststem-V 信号量、POSIX 有名信号量和 POSIX 无名信号量)。他们虽然有很多显著不同的地方,但是最基本的功能室一致的:用来表征一种资源的数量,当多个进程或者线程争夺这些稀缺资源的时候,信号量用来保证他们合理地、秩序地使用这些资源,而不会陷入逻辑谬误之中。
用一个例子来说明什么是“旗语”——红绿灯, 一个繁忙的十字路口,稀缺资源就是通过十字路口的权限,为了避免撞车规定每次只能是对开方向的车通过路口不能转弯),此时另外两个方向的车必须停下来等待,直到红绿灯变换为止
一些基本概念如下:
1,多个进程或线程有可能同时访问的资源(变量、链表、文件等等)称为共享资源,也叫临界资源(critical resources)。
2,访问这些资源的代码称为临界代码,这些代码区域称为临界区(critical zone)。
3,程序进入临界区之前必须要对资源进行申请,这个动作被称为 P 操作,这就像你要把车开进停车场之前,先要向保安申请一张停车卡一样,P 操作就是申请资源,如果申请成功,资源数将会减少。如果申请失败,要不在门口等,要不走人。
4,程序离开临界区之后必须要释放相应的资源,这个动作被称为 V 操作,这就像你把车开出停车场之后,要将停车卡归还给保安一样,V 操作就是释放资源,释放资源就是让资源数增加。所有一起访问共同临界资源的进程都必须遵循以上游戏规则,否则大家就都乱套了,但是值得注意的是:这些规则是自愿的,如果有进程就是胡来——在访问资源之前不申请,那么将会可能导致逻辑谬误,就像开车压死保安直接撞进停车场一样,虽然于情于理都不可以,物理上阻止不了这种行为。
2相关API创建信号量时,还受到以下系统信息的影响:
1,SEMMNI:系统中信号量的总数最大值。
2,SEMMSL:每个信号量中信号量元素的个数最大值。
3,SEMMNS:系统中所有信号量中的信号量元素的总数最大值。
Linux 中,以上信息在/proc/sys/kernel/sem 中可查看。
使用以上函数接口需要注意以下几点:
1,信号量操作结构体的定义如下:
struct sembuf{unsigned short sem_num; /* 信号量元素序号(数组下标) */short sem_op; /* 操作参数 */short sem_flg; /* 操作选项 */};
请注意:信号量元素的序号从 0 开始,实际上就是数组下标。
2,根据 sem_op 的数值,信号量操作分成 3 种情况:
A) 当 sem_op 大于 0 时:进行 V 操作,即信号量元素的值(semval)将会被加上 sem_op 的值。如果 SEM_UNDO 被设置了,那么该 V 操作将会被系统记录。V 操作永远不会导致进程阻塞。
B) 当sem_op等于0时:进行等零操作,如果此时semval恰好为0,则semop( )立即成功返回,否则如果 IPC_NOWAIT 被设置,则立即出错返回并将 errno 设置为EAGAIN,否则将使得进程进入睡眠,直到以下情况发生:
B1) semval 变为 0。
B2) 信号量被删除。(将导致 semop( )出错退出,错误码为 EIDRM)
B3) 收到信号。(将导致 semop( )出错退出,错误码为 EINTR)
C) 当 sem_op 小于 0 时:进行 P 操作,即信号量元素的值(semval)将会被减去 sem_op 的绝对值。如果 semval 大于或等于 sem_op 的绝对值,则 semop( )立即成功返回,semval 的值将减去 sem_op 的绝对值,并且如果 SEM_UNDO 被设置了,那么该 P 操作将会被系统记录。如果 semval 小于 sem_op 的绝对值并且设置了IPC_NOWAIT,那么 semop( )将会出错返回且将错误码置为 EAGAIN,否则将使得进程进入睡眠,直到以下情况发生:
C1) semval 的值变得大于或者等于 sem_op 的绝对值。
C2) 信号量被删除。(将导致 semop( )出错退出,错误码为 EIDRM
C3) 收到信号。(将导致 semop( )出错退出,错误码为 EINTR)
3代码展示sem_p.c(进行减操作)
#include #include #include #include #include union semun { int val; /* Value for SETVAL */ struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */ unsigned short *array; /* Array for GETALL, SETALL */ struct seminfo *__buf; /* Buffer for IPC_INFO (Linux-specific) */ };int main(void){ key_t key; int sem_id; int retval; //获取一个IPC对象 key = ftok(".", 1); if(key == -1) { perror("获取IPC对象的key值出错"); goto get_ipc_key_err; } //获得信号量ID,其中如果信号量没有则创建出来,1代表创建1个信号量 sem_id = semget( key, 1, IPC_CREAT|0644); if(sem_id == -1) { perror("获取信号量出错"); goto get_semid_err; } union semun semarg;//这个是semctl函数说明的,如果第四个参数需要定义的一个共用体 semarg.val = 0;//设置信号量初值为0 /* 设置信号量初值 sem_id:设置的信号量ID 0:设置这个信号量当中的第几个信号量 SETVAL:命令,设置单个信号量的初值 semarg:设置的共用体 */ semctl(sem_id, 0, SETVAL, semarg); struct sembuf op_sem; op_sem.sem_num = 0;//第几个信号量 op_sem.sem_op = -1;//-1操作 op_sem.sem_flg = 0;//按照默认的信号量操作(当减到即将成为一个负数的时候陷入睡眠) //进行减操作 /* sem_id:操作的信号量 op_sem:做何种操作 1:一次性操作1个信号量 */ semop( sem_id, &op_sem, 1); printf("减操作成功\n"); //删除整个信号量集合 //semctl(sem_id, 0, IPC_RMID); return 0;get_semid_err:get_ipc_key_err: return -1;}
sem_v.c(进行加操作)
#include #include #include #include #include union semun { int val; /* Value for SETVAL */ struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */ unsigned short *array; /* Array for GETALL, SETALL */ struct seminfo *__buf; /* Buffer for IPC_INFO (Linux-specific) */ };int main(void){ key_t key; int sem_id; int retval; //获取一个IPC对象 key = ftok(".", 1); if(key == -1) { perror("获取IPC对象的key值出错"); goto get_ipc_key_err; } //获得信号量ID,其中如果信号量没有则创建出来,1代表创建1个信号量 sem_id = semget( key, 1, IPC_CREAT|0644); if(sem_id == -1) { perror("获取信号量出错"); goto get_semid_err; } struct sembuf op_sem; op_sem.sem_num = 0;//第几个信号量 op_sem.sem_op = 1;//+1操作 op_sem.sem_flg = 0;//按照默认的信号量操作(当减到即将成为一个负数的时候陷入睡眠) //进行加操作 /* sem_id:操作的信号量 op_sem:做何种操作 1:一次性操作1个信号量 */ semop( sem_id, &op_sem, 1); //删除整个信号量集合 //semctl(sem_id, 0, IPC_RMID); return 0;get_semid_err:get_ipc_key_err: return -1;}
记录
点点滴滴的笔记
欢迎关注,共同学习
小浩笔记