System V IPC(2)-信号量

时间:2021-09-22 20:42:08

一.概述                                                   

 System V信号量与System V消息队列不同。它不是用来在进程间传递数据。它主要是来同步进程的动作

1.一个信号量是一个由内核维护的整数。其值被限制为大于或等于0

2.可以在信号量上加上或减去一个数量。

3.当一个减操作把信号量减到小于0时,内核会阻塞调用进程。直到另一操作把信号恢复,阻塞才会解除。

4.常用的信号量是二进制信号量。即操作0和1来控制临界区

二.函数接口                                           

1.创建或打开一个信号量

1 #include <sys/sem.h>
2 
3 int semget(key_t key, int nsems, int semflg);

key和semflg跟消息队列一样,这里不再介绍,上面已给出消息队列的文章连接。

nsems:指定信号量数目,一般都是1。

2.控制信号量

1 #include <sys/sem.h>
2 
3 int semctl(int semid, int semnum, int cmd, ...);

semid:semget()返回的信号量标识符。

semnum:信号量编号,如果是成组的信号量,就要用到它,否则是0。

cmd:控制信号量的命令。SETVAL初始化一个值,IPC_RMID删除信号量。

第四个参数是一个union semun结构。如果有的Linux版本头文件没有这个结构,需要自己定义:

1 union semun {
2                int              val;    /* Value for SETVAL */
3                struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
4                unsigned short  *array;  /* Array for GETALL, SETALL */
5                struct seminfo  *__buf;  /* Buffer for IPC_INFO
6                                            (Linux-specific) */
7  };

3.改变信号量的值

1 #include <sys/sem.h>
2 
3 int semop(int semid, struct sembuf *sops, size_t nsops);

sops:指向一个sembuf结构,该结构成员如下:

unsigned short sem_num:信号量编号,如果不是一组信号,一般都为0。
short sem_op:对信号量加减操作,如:-1,+1,1。
short sem_flg:通常设置为SEM_UNDO。如果进程终止时没有释放该信号量,内核会释放它。

三.简单例子                                            

我们封装一个简单的二进制信号量,写2个小程序,一个只打印基数,一个只打印偶数,通过二进制信号量来控制它们按顺序打印1-10。

1.封装二进制信号量

 1 /**
 2  * @file binary_sem.h
 3  */
 4 
 5 #ifndef _BINARY_SEM_H_
 6 #define _BINARY_SEM_H_
 7 
 8 union semun {
 9     int              val;    /* Value for SETVAL */
10     struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
11     unsigned short  *array;  /* Array for GETALL, SETALL */
12     struct seminfo  *__buf;  /* Buffer for IPC_INFO
13                                 (Linux-specific) */
14 };
15 
16 /* 设置信号量 */
17 int sem_set(int sem_id);
18 /* 增大信号量 */
19 int sem_up(int sem_id);
20 /* 减小信号量 */
21 int sem_down(int sem_id);
22 /* 删除信号量 */
23 int sem_delete(int sem_id);
24 
25 #endif
 1 /**
 2  * @file binary_sem.c
 3  */
 4 
 5 #include <stdio.h>
 6 #include <stdlib.h>
 7 #include <string.h>
 8 #include <sys/sem.h>
 9 
10 #include "binary_sem.h"
11 
12 static union semun sem_union;
13 static struct sembuf sem_buf;
14 
15 
16 /* 设置信号量 */
17 int sem_set(int sem_id)
18 {
19     sem_union.val = 1;
20     if (semctl(sem_id, 0, SETVAL, sem_union) == -1)
21         return -1;
22     return 0;
23 }
24 
25 /* 改变信号量为1 */
26 int sem_up(int sem_id)
27 {
28     sem_buf.sem_num = 0;
29     sem_buf.sem_flg = SEM_UNDO;
30     sem_buf.sem_op = 1;
31     if (semop(sem_id, &sem_buf, 1) == -1)
32         return -1;
33     return 0;
34 }
35 
36 /* 信号量减1 */
37 int sem_down(int sem_id)
38 {
39     sem_buf.sem_num = 0;
40     sem_buf.sem_flg = SEM_UNDO;
41     sem_buf.sem_op = -1;
42     if (semop(sem_id, &sem_buf, 1) == -1)
43         return -1;
44     return 0;
45 }
46 
47 /* 删除信号量 */
48 int sem_delete(int sem_id)
49 {
50     if (semctl(sem_id, 0, IPC_RMID, sem_union) == -1)
51         return -1;
52     return 0;
53 }

2.打印基数的程序

 1 /**
 2  * @file sem1.c
 3  */
 4 
 5 #include <stdio.h>
 6 #include <string.h>
 7 #include <stdlib.h>
 8 #include <sys/sem.h>
 9 #include <unistd.h>
10 
11 #include "binary_sem.h"
12 
13 #define SEM_KEY 7788
14 
15 void err_exit(const char *err_msg)
16 {
17     printf("error: %s\n", err_msg);
18     exit(1);
19 }
20 
21 int main(int argc, const char *argv[])
22 {
23     int sem_id, i;
24 
25     /* 创建信号 */
26     if ((sem_id = semget(SEM_KEY, 1, 0666 | IPC_CREAT)) == -1)
27         err_exit("semget()");
28 
29     /* 初始化信号为1 */
30     if (sem_set(sem_id) == -1)
31         err_exit("sem_set()");
32 
33     for (i = 1; i < 10; i += 2)
34     {
35         /* 信号减一 */
36         if (sem_down(sem_id) == -1)
37             err_exit("sem_down()");
38 
39         sleep(3);
40         printf("%s:%d\n", argv[0], i);
41 
42         /* 信号加一 */
43         if (sem_up(sem_id) == -1)
44             err_exit("sem_up()");
45 
46         sleep(1);
47     }
48     return 0;
49 }

对信号量加减操作之间的代码就是临界区。程序第39行的sleep()是为了有足够的时间来允许第二个程序,46行的sleep()是尽量让其他程序来获取信号量。

3.只打印偶数的程序

 1 /**
 2  * @file sem2.c
 3  */
 4 
 5 #include <stdio.h>
 6 #include <string.h>
 7 #include <stdlib.h>
 8 #include <unistd.h>
 9 #include <sys/sem.h>
10 
11 #include "binary_sem.h"
12 
13 #define SEM_KEY 7788
14 
15 void err_exit(const char *err_msg)
16 {
17     printf("error: %s\n", err_msg);
18     exit(1);
19 }
20 
21 int main(int argc, const char *argv[])
22 {
23     int sem_id, i;
24 
25     /* 创建信号 */
26     if ((sem_id = semget(SEM_KEY, 1, 0666 | IPC_CREAT)) == -1)
27         err_exit("semget()");
28 
29     for (i = 2; i <= 10; i += 2)
30     {
31         /* 信号减一 */
32         if (sem_down(sem_id) == -1)
33             err_exit("sem_down()");
34 
35         printf("%s:%d\n", argv[0], i);
36 
37         /* 信号加一 */
38         if (sem_up(sem_id) == -1)
39             err_exit("sem_up()");
40 
41         sleep(1);
42     }
43     return 0;
44 }

四.实验                                                  

1.为了测试方便,上面2个程序的key统一用一个值为7788的宏来指定。

2.两个程序执行的流程:sem1初始化一个为1的信号量,进入循环后对信号执行减一,并进入临界区。休眠3秒的时间我执行第二个程序sem2,sem2进入循环后也执行减一操作,但此时信号已被sem1减为0,此时内核会阻塞sem1的操作,因为再减就小于0了。回到sem1,3秒过后,打印基数,并把信号重置为1,离开临界区,休眠1秒。此时内核发现信号是1,可以执行减操作,内核会对sem2放行,sem2进入临界区打印偶数。刚刚休眠1秒的sem1再次进入临界区,发现信号量被sem2减到了0,此时被内核阻塞,直到sem2重置信号量。如此反复!!!

System V IPC(2)-信号量

最后:其实System V信号量的语义和API都比较复杂,有机会再探讨比它简单的POSIX信号量。