Linux 信号量(semaphore)

这里记录一下我学习 Linux 信号量时我所理解的信号量 API 基本用法。很多内容来自手册,
如果有不清楚不详细的地方,可以参照手册理解。

struct semid_ds

1
2
3
4
5
6
struct semid_ds {
struct ipc_perm sem_perm; /* Ownership and permissions */
time_t sem_otime; /* Last semop time */
time_t sem_ctime; /* Last change time */
unsigned long sem_nsems; /* No. of semaphores in set */
};

每个信号量集在内核里都有一个这样的数据结构。可以通过semctl()函数获取,最后的例
子中有展示。

semget()函数

1
2
3
4
5
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int semget(key_t key, int nsems, int semflg);

函数semget()用来获取一个信号量集的标识符,参数key可以是IPC_PRIVATE或者是由
函数ftok()得到的一个 key。参数nsems在新建信号量集时用来指定信号量集中的信号
量的数量,可以根据需要自己指定。参数semflg可以指定IPC_CREAT用来表示在key
绑定的信号量集不存在时创建一个新的,semflg的最低 9 个比特用来表示新建信号量集
的权限,可以与其他参数直接相与来设置。

semget()并不创建单个的信号量,而是创建一个集合,这个集合中包含多个信号量,所以
称作信号量集。

semop()函数

1
2
3
4
5
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int semop(int semid, struct sembuf *sops, size_t nsops);

参数semid用来指定信号量集,参数sops是一个指向struct sembuf的指针,而实际上
这个一个数组的首地址,参数nsops指针指向的一共有多少个项,struct sembuf的定义
如下:

1
2
3
4
5
struct sembuf {
unsigned short sem_num; /* semaphore number */
short sem_op; /* semaphore operation */
short sem_flg; /* operation flags */
};

这个数组中的每个结构体定义了要对信号量进行的一个操作。sem_num用来指定对信号量
集中的第几个信号量进行操作,sem_op指定要进行的操作(即增加或减少信号量的值,给
定相应的正数或负数即可),sem_flg可以设置的值有IPC_NOWAITSEM_UNDO,如果
指定了SEM_UNDO则在进程结束后操作会被 undo。内核中每一个信号量集都保存的有一个
undo 表。

semctl()函数

1
2
3
4
5
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int semctl(int semid, int semnum, int cmd, ...);

函数semctl()会根据参数cmd来对semid所指定的信号量集进行操作,semnum指定了
信号量集中的第几个信号量。函数根据参数cmd的不同而可能有3个或4个参数,当有第4
个参数时,它的类型为union semun,调用者必须把它定义为如下的形式:

1
2
3
4
5
6
7
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) */
};

cmd可能取的值为IPC_STAT IPC_SET IPC_RMID GETVAL SETVAL等。下面是一个
小的例子程序:

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

#define OP_SIZE 2

void init_sops(struct sembuf *sops, int size);

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(int argc, char *argv[])
{
key_t key;
int semid;
struct sembuf sops[OP_SIZE];
struct semid_ds semds;

key = ftok("/bin", 1);
semid = semget(key, 3, IPC_CREAT);
if (semid == -1) {
perror("semget error");
exit(1);
}

semctl(semid, 1, IPC_STAT, semds);
printf("values in semid_ds:\n");
printf("\tsemid_ds.sem_otime: [%ld]\n", semds.sem_otime);
printf("\tsemid_ds.sem_ctime: [%ld]\n", semds.sem_ctime);
printf("\tsemid_ds.sem_nsems: [%lu]\n", semds.sem_nsems);

printf("value of semaphore: %d\n", semctl(semid, 1, GETVAL));

init_sops(sops, OP_SIZE);
if (semop(semid, sops, OP_SIZE) == -1) {
perror("semop error");
exit(2);
}

printf("value of semaphore: %d\n", semctl(semid, 1, GETVAL));

semctl(semid, 1, IPC_STAT, semds);
printf("values in semid_ds:\n");
printf("\tsemid_ds.sem_otime: [%ld]\n", semds.sem_otime);
printf("\tsemid_ds.sem_ctime: [%ld]\n", semds.sem_ctime);
printf("\tsemid_ds.sem_nsems: [%lu]\n", semds.sem_nsems);

semctl(semid, 1, IPC_RMID);

return 0;
}

void init_sops(struct sembuf *sops, int size)
{
int i;
for (i=0; i < size; i++) {
sops[i].sem_num = 1;
sops[i].sem_op = i+1;
sops[i].sem_flg = SEM_UNDO;
}
}

注意

信号量集在程序退出后不会自动消失,需要使用函数semctl()手动删除,或者使用命令
ipcs -s查看,使用命令ipcrm -s <semid>删除。

0%