信号量的概念

信号量,或称信号灯,其原理是一种数据操作锁的概念,本身不具备数据交换的功能,它负责协调各个进程,保证保证两个或多个关键代码段不被并发调用,确保公共资源的合理使用。信号量分为单值和多值两种。

在信号的实际应用中,是不能只定义一个信号量的,而只能定义一个信号量集,其中包含一组信号量,同一信号量集中的信号量使用同一个引用ID,这样的设置是为了多个资源或同步操作的需要。每个信号量集都有一个与之对应的结构,其中记录了信号量集的各种信息,该结构定义为:

1
2
3
4
5
6
7
8
struct semid_ds
{
struct ipc_perm sem_perm; // operation permission struct
struct sem *sem_base; // ptr to first semaphore in set
unsigned short sem_nsems; // numbers of semaphore in set
time_t sem_otime; // last semop time
time_t sem_ctime; // last change time
};

ipc_perm结构体包含了信号量集的键值、所有者信息及操作权限等,定义如下:

1
2
3
4
5
6
7
8
9
10
11
struct ipc_perm
{
__key_t __key; // key value
__uid_t uid; // owner uid
__gid_t gid; // owner gid
__uid_t cuid; // creater uid
__gid_t cgid; // creater gid
unsigned short int mode; // read/write permission
unsigned short int __seq; // seq number
......
};

sem结构体记录了一个信号量的信息,定义如下:

1
2
3
4
5
6
7
struct sem
{
unsigned short semval; // semaphore value, always >= 0
pid_t sempid; // pid for last operation
unsigned short semncent; // numbers of processes awaiting semval > currval
unsigned short semzcnt; // numbers of processes awaiting semval = 0
};

信号量集的相关操作

创建/打开信号量集

使用semget()函数创建或打开一个信号量集,其函数原型为:

1
2
3
4
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int semget(key_t key, int nsems, int flag);

运行成功返回信号量集的ID号,失败返回-1。

参数key为一个键值,可通过ftok()函数生成,参数nsems为创建的信号量集中包含的信号量个数,参数flag为操作参数,具体使用方法与创建共享内存的**shmget()**使用类似。

对信号量集的操作

使用semop()函数操作一个信号量集,其函数原型为:

1
2
3
4
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int semop(int semid, struct sembuf semoparray[], size_t nops);

运行成功返回0,失败返回-1。

参数semid为semget()函数返回的信号量集ID号,参数semoparray是一个struct sembuf结构类型的数组,参数nops为前一数组参数的元素个数,sembuf的定义如下:

1
2
3
4
5
6
struct sembuf
{
unsigned short sem_num;
short sem_op;
short sem_flg;
}

其中sem_num是信号集中的某一资源(即指定操作的信号量),其取值为0到资源总数(ipc_perm.sem_nsems)之间的整数;sem_op为一个整数,指明要执行的操作;sem_flg说明函数semop()的行为。

  • sem_op > 0:表示进程对资源使用完毕,释放相应的资源数,并将sem_op的值加到信号量的值上。
  • sem_op = 0:进程阻塞直到信号量的相应值为0,当信号量已经为0,函数立即返;信号量值不为0,则依据sem_flgIPC_NOWAIT位决定函数动作:
    • sem_flg指定IPC_NOWAIT,则semop()函数出错返回EAGAIN
    • sem_flg没有指定IPC_NOWAIT,则将信号量的semncnt的值减1,然后进程挂起直到下述情况发生:
      • 信号量为0,则将信号量的semncnt的值加1,函数semop()成功返回;
      • 信号量被删除,函数semop()出错返回EIDRM
      • 进程捕捉到信号,并从信号处理函数返回,信号量的semncnt的值减1,函数semop()出错返回EINTR
  • sem_op < 0:请求sem_op的绝对值的资源,若相应的资源数可以满足请求,则将该信号量的值减去sem_op的绝对值,函数成功返回;若资源数不足,这个操作与sem_flg有关:
    • sem_flg指定IPC_NOWAIT,则semop()函数出错返回EAGAIN
    • sem_flg没有指定IPC_NOWAIT,则将信号量的semncnt的值加1,然后进程挂起直到下述情况发生:
      • 资源数满足请求,信号值减去sem_op的绝对值,成功返回;
      • 信号量被删除,函数semop()出错返回EIDRM
      • 进程捕捉到信号,并从信号处理函数返回,信号量的semncnt的值减1,函数semop()出错返回EINTR

信号量集的控制

与共享内存的控制相似,信号量集也有自己的控制函数**semctl()**,函数原型如下:

1
2
3
4
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int semctl(int semid, int semnum, int cmd, union semun arg);

运行成功返回大于等于0的值,失败返回-1,并设置错误变量errno。

参数semid为semget()函数返回的信号量集ID号,参数semnum指定信号量集中的某一信号量,类似于下标索引,参数cmd定义函数的操作,具体含义与后面的参数arg有关,arg是一个结构体,定义如下:

1
2
3
4
5
6
7
union semun
{
int val;
struct semid_ds *buf;
unsigned short array;
struct seminfo *__buf;
};

cmd的取值含义如下:

cmd取值 含义
GETALL 获得信号量集中信号量的个数,并将其赋值给无符号短整数arg.array
GETVAL 获得信号量集中semnum所指定信号量的semval
GETPID 获得信号量集中最后一个使用semop函数的进程ID,即semid_ds中sem.sempid的值
GETNCNT 获得信号量集中等待给定信号锁的进程数目,即semid_ds中sem.semncnt的值
GETZCNT 获得信号量集中等待信号量成为0的进程数目,即semid_ds中sem.semzcnt的值
IPC_RMID 删除信号量集。由超级用户或信号量集拥有者进程执行
IPC_SET 按照arp.buf参数指向的结构体中的值设置sem_pern.uid、sem_pern.gid、sem_pern.mode的值。由超级用户或信号量集拥有者进程执行
IPC_STAT 获得该信号的semid_ds结构,保存在arg.buf指向的缓冲区
SETALL 以arg.array的值设置信号量集semid中信号量的个数
SETVAL 以arg.val的值设置信号量集semid中semnum所指定信号量的值semval
SEM_INFO或IPC_INFO 返回内核关于所有信号量集记录数组的最大索引值,该信息可用于重复执行SEM_STAT来获取系统内所有信号集的信息

编程示例

示例1:信号量创建(内含ftok生成key原理)

创建信号量示例,sem_create.c:

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
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <unistd.h>
#include <sys/stat.h>
#include <stdlib.h>
#define PATHNAME "/tmp"

int main(int argc, char *argv[])
{
key_t keyid;
int sigid;
int nsems, semflg;
nsems = 1;
semflg = IPC_CREAT|0666;

// below 4 lines code not necessary, just show the PATHNANE -> dev & inode,
// then combine with proj_id to calculate the keyid (ftok work principle)
struct stat stat_info;
stat(PATHNAME, &stat_info);
printf("dev:%lx\n", stat_info.st_dev);
printf("inode:%lx\n", stat_info.st_ino);

// generate key
if((keyid = ftok(PATHNAME, 1)) == -1)
{
perror("ftok() failed\n");
exit(1);
}
else
printf("key is %x\n", keyid);

// create a semaphore
if((sigid = semget(keyid, nsems, semflg)) == -1)
{
perror("semget() failed\n");
exit(1);
}
printf("sem create ok!\n");

return 0;
}

运行结果:

1
2
3
4
5
6
$ ./sem_create
dev:804
inode:280001
key is 1040001
sem create ok!

这里说明一下ftok()函数生成key的原理ftok()定义如下:

1
2
key_t ftok(const char *pathname, int proj_id);

首先根据一个任意存在的pathname路径提取其所属文件系统的特有信息,包括设备号(stat.st_dev)inode号(stat.st_ino),其获取方法参见上述程序中的sata结构,然后再结合ftok()函数的proj_id参数,按照如下规则计算key:

1
2
3
4
5
6
7
key1 = stat.st_ino & 0xffff;   // 保留低16位
key2 = stat.st_dev & 0xff; // 保留低8位
key2 << = 16; // 左移16位
key3 = proj_id & 0xff; // 保留低8位
key3 << = 24; // 左移24位
key = key1|key2|key3; // 三者进行或运算

本例中,pathname=’/tmp‘,ftok()函数提取的设备号为0x804,inode号为0x280001,proj_id为0x01,根据以上运算过程,key1=0x01,key2=0x40000,key3=0x1000000,三者求或得到key=0x1040001,与程序输出的结果一致。

示例2:服务器-客户端模式

服务器端,信号量的创建与初始化,监视信号量资源使用情况,sem_server.c:

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
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#define PATHNAME "/tmp"
#define RESOURCE 3

union semun
{
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *__buf;
};

int main(int argc, char *argvp[])
{
key_t keyid;
int sigid;
int nsems, semflg;
union semun semopt;
struct sembuf sbuf = {0, 0, SEM_UNDO};
nsems = 1;
semflg = IPC_CREAT|0666;

// generate key
if((keyid = ftok(PATHNAME, 3)) == -1)
{
perror("ftok() failed\n");
exit(1);
}
else
printf("key is %x\n", keyid);

// create a semaphore
if((sigid = semget(keyid, nsems, semflg)) == -1)
{
perror("semget() failed\n");
exit(1);
}
else
printf("create semaphore ok\n");

// initial the semaphore
semopt.val = RESOURCE;
if(semctl(sigid, 0, SETVAL, semopt) == -1)
{
perror("semctl() failed\n");
exit(1);
}
else
printf("initial the semphore ok\n");

// monitor the semphore usage status
while(1)
{
if(semop(sigid, &sbuf, 1) == 0)// use up!!!
printf("resources have been exhausted\n");
sleep(3);
}
return 0;
}

客户端程序,使用按键pv模拟P操作和V操作,sem_client.c:

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
69
70
71
72
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#define PATHNAME "/tmp"
#define RESOURCE 3

int main(int argc, char *argvp[])
{
key_t keyid;
int sigid;
int nsems, semflg;
char opt;
struct sembuf pbuf = {0, -1, IPC_NOWAIT}; // P operate
struct sembuf vbuf = {0, 1, IPC_NOWAIT}; // V operate
nsems = 1;
semflg = IPC_CREAT|0666;

// generate key
if((keyid = ftok(PATHNAME, 3)) == -1)
{
perror("ftok() failed\n");
exit(1);
}
else
printf("key is %x\n", keyid);

// get the semaphore
if((sigid = semget(keyid, nsems, semflg)) == -1)
{
perror("semget() failed\n");
exit(1);
}
else
printf("get the semaphore ok\n");

// use p/v to simulate the PV operation
while(1)
{
opt = getchar();
switch(opt)
{
case 'p':// --
if(semop(sigid, &pbuf, 1) == -1)
printf("-->resources have been exhausted\n");// exhausted
else
printf("-->Total unused resources:%d\n", semctl(sigid, 0, GETVAL, 0));
break;
case 'v':// ++
if(semop(sigid, &vbuf, 1) == -1)
printf("-->V opreation failed\n");
else if(semctl(sigid, 0, GETVAL, 0) > RESOURCE)
{
printf("-->resources to achieve maximum\n");// achieve maximum
semop(sigid, &pbuf, 1);
}
else
printf("-->Total unused resources:%d\n", semctl(sigid, 0, GETVAL, 0));
break;
case 's':// show
printf("-->Total unused resources:%d\n", semctl(sigid, 0, GETVAL, 0));
break;
case 'q':// quit
exit(0);
}
sleep(1);
}
return 0;
}

编译上面两个程序并执行,先运行服务器端程序:

1
2
3
4
5
6
$ ./sem_server
key is 3040001
create semaphore ok
initial the semphore ok


可以看到信号量创建并初始化成功,进入监视状态,在另一个shell中运行客户端程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ ./sem_client
key is 3040001
get the semaphore ok
p
-->Total unused resources:2
p
-->Total unused resources:1
p
-->Total unused resources:0
v
-->Total unused resources:1
s
-->Total unused resources:1
q

运行后,使用键盘的pv键入shell,继续观察服务器端的结果,当客户端多次使用p键使资源耗尽后,服务器端会每隔3秒进行相应的输出提示报警,客户端使用v键增加资源,服务器端停止报警。

1
2
3
4
5
6
7
8
9
$ ./sem_server
key is 3040001
create semaphore ok
initial the semphore ok
resources have been exhausted
resources have been exhausted
resources have been exhausted


最后可以使用Ctrl+c终止服务器端程序。

示例3

获取各种信号量的信息,利用信号量实现共享资源的申请和释放,sem_app.c:

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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <errno.h>
#include <sys/sem.h>
#include <stdlib.h>
#include <unistd.h>
#define SEM_PATH "."
#define max_tries 3

int semid;

union semun
{
int val;
struct semid_ds *buf;
unsigned short array;
struct seminfo *__buf;
};

int main(void)
{
int flag1, flag2, key, i, init_ok, tmperrno;
struct semid_ds sem_info;
struct seminfo sem_info2;
union semun arg;
struct sembuf askfor_res, free_res;
flag1 = IPC_CREAT|IPC_EXCL|00666;
flag2 = IPC_CREAT|00666;

// generate key
key = ftok(SEM_PATH, 'a');
if(key == -1)
{
perror("ftok error");
exit(1);
}

// create a semaphore
init_ok = 0;
semid = semget(key, 1, flag1);
if(semid < 0)
{
tmperrno = errno;
perror("semget");
if(tmperrno == EEXIST)
{
//
semid = semget(key, 1, flag2);
arg.buf = &sem_info;
for(i=0; i<max_tries; i++)
{
if(semctl(semid, 0, IPC_STAT, arg) == -1)
{
perror("semctl error");
i = max_tries;
}
else
{
if(arg.buf->sem_otime!=0)
{
i = max_tries;
init_ok = 1;
}
else
sleep(1);
}
}
if(!init_ok)
{
arg.val = 1;
if(semctl(semid, 0, SETVAL, arg) == -1)
perror("semctl setval error");
}
}
else
{
perror("semget error, process exit");
exit(1);
}
}
else // semid >= 0, do some initializing
{
arg.val = 1;
if(semctl(semid, 0, SETVAL, arg) == -1)
perror("semctl setval error");
}

// get some information about the semaphore and the limit of semaphore in Linux
arg.buf = &sem_info;
if(semctl(semid, 0, IPC_STAT, arg) == -1)
perror("semctl IPC_STAT");
printf("owner's uid is %d\n", arg.buf->sem_perm.uid);
printf("owner's gid is %d\n", arg.buf->sem_perm.gid);
printf("creater's uid is %d\n", arg.buf->sem_perm.cuid);
printf("creater's gid is %d\n", arg.buf->sem_perm.cgid);

arg.__buf = &sem_info2;
if(semctl(semid, 0, SEM_INFO, arg) == -1)
perror("semctl IPC_INFO");
printf("the number of entries in semaphore map is %d\n", arg.__buf->semmap);
printf("max number of semaphore identifiers is %d\n", arg.__buf->semmni);//o);
printf("mas number of semaphore in system is %d\n", arg.__buf->semmns);
printf("the number of undo structures system wide is %d\n", arg.__buf->semmnu);
printf("max number of semaphore per semid is %d\n", arg.__buf->semmsl);
printf("max number of ops per semop call is %d\n", arg.__buf->semopm);
printf("max number of undo entries per process is %d\n", arg.__buf->semume);
printf("the size of struct sem_undo is %d\n", arg.__buf->semusz);
printf("the maximum semaphore value is %d\n", arg.__buf->semvmx);

// now ask for available resource
askfor_res.sem_num = 0;
askfor_res.sem_op = -1;
askfor_res.sem_flg = SEM_UNDO;
if(semop(semid, &askfor_res, 1) == -1)
perror("semop error");

sleep(3);
printf("now free the resource\n");

// now free resource
free_res.sem_num = 0; // resources num
free_res.sem_op = 1;
free_res.sem_flg = SEM_UNDO;
if(semop(semid, &free_res, 1) == -1)
if(errno == EIDRM)
printf("the semaphore set was removed\n");

if(semctl(semid, 0, IPC_RMID) == -1)
perror("semctl IPC_RMID");
else
printf("remove sem ok\n");

return 0;
}

编译运行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ ./sem_app
owner's uid is 1000
owner's gid is 1000
creater's uid is 1000
creater's gid is 1000
the number of entries in semaphore map is 1024000000
max number of semaphore identifiers is 32000
mas number of semaphore in system is 1024000000
the number of undo structures system wide is 1024000000
max number of semaphore per semid is 32000
max number of ops per semop call is 500
max number of undo entries per process is 500
the size of struct sem_undo is 1
the maximum semaphore value is 32767
now free the resource
remove sem okt

参考:

  1. 《精通Linux C编程》- 程国钢
  2. 《Linux C编程完全解密》- 闫敬 吴淑坤