信号量
信号量及信号量上的操作是 E.W.Dijkstra 在 1965 年提出的一种解决同步、互斥问题的较通用的方法,并在很多操作系统中得以实现,Linux 改进并实现了这种机制。1
流程
一般流程2如下:
- 通过
ftok
获得确定的 IPC Key - 使用
semget
获得用于信号量的特定 IPC ID - 使用
semop
操作信号量 - 使用
semctl
移除信号量
函数
semget
建立/获得信号量。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg)
- key:键值,一般使用
ftok
获得 - nsems:在新创建的集合中信号量的个数,最大值 250。3
- semflg:使用
|
连接- 权限:可使用
0666
等表示,也可使用open
中mode
宏 - IPC_CREAT:若不存在则创建
- IPC_EXCL:若存在则返回错误(和
IPC_CREAT
一起使用)
- 权限:可使用
- return:-1,错误;非负数,信号量 ID
注:
- 系统中查看存在的信号量可通过
ipcs -s
命令 - 最大值可通过
ipcs -ls
查看「max semaphores per array」,echo "256 32000 32 128" > /proc/sys/kernel/sem
修改 6
semctl
控制信号量集合,设置、获取、删除。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, ...)
- semid:
semget
获得 ID - semnum:集合中哪个信号量,从 0 开始
- cmd:常用如下4
- 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 成员的值设置信号量集合中单个信号量的值
- IPC_STAT:从信号量集合上检索
- args:
union semun
- return:-1,错误
注:
系统中存在的信号量可通过 ipcrm -s/S
删除
semop/semtimedop
#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)
semid:
semget
获得 IDsops:数组,结构如下
struct sembuf { unsigned short sem_num; /* 信号量集合索引 */ short sem_op; /* 操作数 */ short sem_flg; /* 操作标识 */ }
- sem_op:
- 零:如果
semval
为 0,则进行下一步,如果semval
不为 0,则等待(IPC_NOWAIT
跳过) - 正数:信号量的值加上该值
- 负数:
semval
大于等于该值绝对值则相加,semval
小于该值绝对值则等待
- 零:如果
- sem_flag:
- IPC_NOWAIT:非阻塞
- SEM_UNDO:进程退出自动解锁信号量,防止死锁
- sem_op:
nsops:执行数组操作个数
理解:
- 保护资源时,可以用 sem=0 时表示空闲;那么加锁动作即 [0,1],等待 sem 空闲并加一;解锁动作 [-1],sem 减一返回空闲状态。
- 同步或生产消费问题,可以用 sem 加减表示,sem 加一表示生产一件或该操作完成可以进行下一步,消费方则等待减一 sem 又回到 0 状态。
实例
互斥锁
使用信号量来保护关键部分5
1 |
|
执行程序:
./daemon a & ./daemon b & ./daemon c &
执行结果:
[a] I have the semaphore
[b] Waiting for the semaphore to be released
[c] Waiting for the semaphore to be released
[a] Released semaphore
[b] I have the semaphore
[b] Released semaphore
[c] I have the semaphore
[a] Waiting for the semaphore to be released
[b] Waiting for the semaphore to be released
[c] Released semaphore
[a] I have the semaphore
[a] Released semaphore
[b] I have the semaphore
[b] Released semaphore
[b] Waiting for the semaphore to be released
[b] I have the semaphore
[c] Waiting for the semaphore to be released
[a] Waiting for the semaphore to be released
[b] Released semaphore
同步
生产者:
1 |
|
消费者:
1 |
|
执行程序:
./produce & ./consume &
执行结果:
I produce
I consume
I produce
I consume
I produce
I consume
- 1.深入分析Linux内核源代码,7.3.1 ↩
- 2.使用 UNIX System V IPC 机制共享应用程序数据 ↩
- 3.深入分析Linux内核源代码,7.3.1.2,P279 ↩
- 4.深入分析Linux内核源代码,表7.2,P282 ↩
- 5.使用 UNIX System V IPC 机制共享应用程序数据,清单5 ↩
- 6.REDHAT AS4内核调优之kernel.sem的设置(原创) ↩