信号量

信号量及信号量上的操作是 E.W.Dijkstra 在 1965 年提出的一种解决同步互斥问题的较通用的方法,并在很多操作系统中得以实现,Linux 改进并实现了这种机制。1

流程

一般流程2如下:

  1. 通过 ftok 获得确定的 IPC Key
  2. 使用 semget 获得用于信号量的特定 IPC ID
  3. 使用 semop 操作信号量
  4. 使用 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:在新创建的集合中信号量的个数,最大值 2503
  • semflg:使用 | 连接
    • 权限:可使用 0666 等表示,也可使用 openmode
    • IPC_CREAT:若不存在则创建
    • IPC_EXCL:若存在则返回错误(和 IPC_CREAT 一起使用)
  • return:-1,错误;非负数,信号量 ID

  1. 系统中查看存在的信号量可通过 ipcs -s 命令
  2. 最大值可通过 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 成员的值设置信号量集合中单个信号量的值
  • 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 获得 ID

  • sops:数组,结构如下

      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:进程退出自动解锁信号量,防止死锁
  • nsops:执行数组操作个数

理解

  1. 保护资源时,可以用 sem=0 时表示空闲;那么加锁动作即 [0,1],等待 sem 空闲并加一;解锁动作 [-1],sem 减一返回空闲状态。
  2. 同步或生产消费问题,可以用 sem 加减表示,sem 加一表示生产一件或该操作完成可以进行下一步,消费方则等待减一 sem 又回到 0 状态。

实例

互斥锁

使用信号量来保护关键部分5

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <sys/types.h>
#include <sys/sem.h>
#include <sys/ipc.h>
#include <string.h> /* For strerror(3c) */
#include <errno.h> /* For errno */
#include <unistd.h> /* rand(3c) */
#include <stdio.h>
#include <stdlib.h>

int main (int argc, char **argv)
{
key_t ipckey;
int semid;
struct sembuf sem[2]; /* sembuf defined in sys/sem.h */

/* Generate the ipc key */
ipckey = ftok("main.c", ':');

/* Set up the semaphore set. 4 == READ, 2 == ALTER */
semid = semget(ipckey, 1, 0666 | IPC_CREAT);
if (semid < 0) {
printf("Error - %s\n", strerror(errno));
_exit(1);
}

/* These never change so leave them outside the loop */
sem[0].sem_num = 0;
sem[1].sem_num = 0;
sem[0].sem_flg = SEM_UNDO; /* Release semaphore on exit */
sem[1].sem_flg = SEM_UNDO; /* Release semaphore on exit */
int loop = 10;
while(loop--) { /* loop forever */
printf("[%s] Waiting for the semaphore to be released\n", argv[1]);
/* Set up two semaphore operations */
sem[0].sem_op = 0; /* Wait for zero */
sem[1].sem_op = 1; /* Add 1 to lock it*/
semop(semid, sem, 2);
printf("[%s] I have the semaphore\n", argv[1]);

sleep(rand() % 3); /* Critical section, sleep for 0-2 seconds */

sem[0].sem_op = -1; /* Decrement to unlock */
semop(semid, sem, 1);
printf("[%s] Released semaphore\n", argv[1]);

sleep(rand() % 3); /* Sleep 0-2 seconds */
}
}

执行程序:

./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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <sys/types.h>
#include <sys/sem.h>
#include <sys/ipc.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main (void)
{
key_t ipckey;
int semid;
struct sembuf sem[2];

ipckey = ftok("/tmp/test", ':');

semid = semget(ipckey, 1, 0600|IPC_CREAT|IPC_EXCL);
if (semid < 0) {
printf("Sem Create Error - %s\n", strerror(errno));
exit(1);
}

sem[0].sem_num = 0;
sem[1].sem_num = 0;
sem[0].sem_flg = SEM_UNDO;
sem[1].sem_flg = SEM_UNDO;
int loop = 10;
while(loop--) {
sem[0].sem_op = 0;
sem[1].sem_op = 1;
semop(semid, sem, 2);
printf("I produce\n");
sleep(3);
}
}

消费者:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <sys/types.h>
#include <sys/sem.h>
#include <sys/ipc.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main (void)
{
key_t ipckey;
int semid;
struct sembuf sem[1];

ipckey = ftok("/tmp/test", ':');

semid = semget(ipckey, 1, 0600|IPC_CREAT);
if (semid < 0) {
printf("Sem Create Error - %s\n", strerror(errno));
exit(1);
}

sem[0].sem_num = 0;
sem[0].sem_flg = SEM_UNDO;
int loop = 10;
while(loop--) {
sem[0].sem_op = -1;
semop(semid, sem, 1);
printf("I consume\n");
sleep(0);
}
if (semctl(semid, 0, IPC_RMID) == -1) {
printf("Sem Delete Error - %s\n", strerror(errno));
exit(1);
}
printf("Sem Delete!\n");
}

执行程序:

./produce & ./consume &

执行结果:

I produce
I consume
I produce
I consume
I produce
I consume

  1. 1.深入分析Linux内核源代码,7.3.1
  2. 2.使用 UNIX System V IPC 机制共享应用程序数据
  3. 3.深入分析Linux内核源代码,7.3.1.2,P279
  4. 4.深入分析Linux内核源代码,表7.2,P282
  5. 5.使用 UNIX System V IPC 机制共享应用程序数据,清单5
  6. 6.REDHAT AS4内核调优之kernel.sem的设置(原创)

信号量
https://wishlily.github.io/article/linux/2016/09/01/undefined/
作者
Wishlily
发布于
2016年9月1日
许可协议