Linux IPC 之信号量

时间:2021-08-04 05:28:04

信号量(也叫信号灯)是一种用于提供不同进程间或一个给定进程的不同线程间同步手段的原语。

信号量是进程/线程同步的一种方式,有时候我们需要保护一段代码,使它每次只能被一个执行进程/线程运行,这种工作就需要一个二进制开关;

有时候需要限制一段代码可以被多少个进程/线程执行,这就需要用到关于计数信号量。信号量开关是二进制信号量的一种逻辑扩展,两者实际调用的函数都是一样。

信号量分为以下三种。

1、System V信号量,在内核中维护,可用于进程或线程间的同步,常用于进程的同步。 

2、Posix有名信号量,一种来源于POSIX技术规范的实时扩展方案(POSIX Realtime Extension),可用于进程或线程间的同步,常用于线程。

3、Posix基于内存的信号量,存放在共享内存区中,可用于进程或线程间的同步。

为了获得共享资源进程需要执行下列操作:

1)测试控制该资源的信号量。

2)若信号量的值为正,则进程可以使用该资源。进程信号量值减1,表示它使用了一个资源单位。此进程使用完共享资源后对应的信号量会加1。以便其他进程使用。

3)若信号量的值为0,则进程进入休息状态,直至信号量值大于0。进程被唤醒,返回第(1)步。

    为了正确地实现信号量,信号量值的测试值的测试及减1操作应当是原子操作(原子操作是不可分割的,在执行完毕不会被任何其它任务或事件中断)。为此信号量通常是在内核中实现的。

·System V IPC机制:信号量。

函数原型:

#include <sys/sem.h>

#include <sys/ipc.h>

#include <sys/types.h>

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

函数semget创建一个信号量集或访问一个已存在的信号量集。返回值:成功时,返回一个称为信号量标识符的整数,semop和semctl会使用它;出错时,返回-1.

参数key是唯一标识一个信号量的关键字,如果为IPC_PRIVATE(值为0,创建一个只有创建者进程才可以访问的信号量,通常用于父子进程之间;非0值的key(可以通过ftok函数获得)表示创建一个可以被多个进程共享的信号量;

参数nsems指定需要使用的信号量数目。如果是创建新集合,则必须制定nsems。如果引用一个现存的集合,则将nsems指定为0.

参数flag是一组标志,其作用与open函数的各种标志很相似。它低端的九个位是该信号量的权限,其作用相当于文件的访问权限。此外,它们还可以与键值IPC_CREAT按位或操作,以创建一个新的信号量。即使在设置了IPC_CREAT标志后给出的是一个现有的信号量的键字,也并不是一个错误。我们也可以通过IPC_CREAT和IPC_EXCL标志的联合使用确保自己将创建出一个新的独一无二的信号量来,如果该信号量已经存在,就会返回一个错误。

 

 

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

函数semop用于改变信号量对象中各个信号量的状态。返回值:成功时,返回0;失败时,返回-1.

参数semid是由semget返回的信号量标识符。

参数sops是指向一个结构体数组的指针。每个数组元素至少包含以下几个成员:

 

 1 struct sembuf{
 2     short sem_num;  //操作信号在信号集中的编号,第一个信号的编号是0。
 3 
 4     /*通常只会用到两个值,一个是-1,也就是p操作,它等待信号量变为可用;
 5     一个是+1,也就是v操作,它发送信号通知信号量现在可用。
 6      sem_op成员的值是信号量在一次操作中需要改变的数值*/
 7     short sem_op;
 8 
 9     short sem_flg; //通常设为:SEM_UNDO,程序结束,信号量为semop调用前的值。
10 };

 

参数nops为sops指向的sembuf结构数组的大小。

 

 

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

函数semctl用来直接控制信号量信息。函数返回值:成功时,返回0;失败时,返回-1.

参数semid是由semget返回的信号量标识符。

参数semnum为集合中信号量的编号,当要用到成组的信号量时,从0开始。一般取值为0,表示这是第一个也是唯一的一个信号量。

参数cmd为执行的操作。通常为:IPC_RMID(立即删除信号集,唤醒所有被阻塞的进程)、GETVAL(根据semnun返回信号的值)、SETVAL(根据semun设定信号的值)、GETALL(获取所有信号量的值,第二个参数为0,将所有信号的值存入semun.array中)、SETALL(将所有semun.array的值设定到信号集中,第二个参数为0)等。

参数…是一个union semun(需要由程序员自己定义),它至少包含以下几个成员:

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

 

示例:

sem1.c

 1 #include<stdio.h>
 2 #include<stdlib.h>
 3 #include<string.h>
 4 #include<unistd.h>
 5 #include<sys/types.h>
 6 #include<sys/ipc.h>
 7 #include<sys/sem.h>
 8 
 9 //P操作,申请一个资源
10 void P(int semid)
11 {
12     struct sembuf my_buf;
13     memset(&my_buf,0,sizeof(my_buf));
14     my_buf.sem_num = 0;
15     my_buf.sem_op = -1;
16     my_buf.sem_flg = SEM_UNDO;
17     semop(semid,&my_buf,1);
18 }
19 
20 void V(int semid)
21 {
22     struct sembuf my_buf;
23     memset(&my_buf,0,sizeof(my_buf));
24     my_buf.sem_num = 0;
25     my_buf.sem_op = 1;
26     my_buf.sem_flg = SEM_UNDO;
27     semop(semid,&my_buf,1);
28 }
29 
30 int main(int *argc, char *argv[])
31 {
32     key_t my_key = 1234;
33     int my_sem;
34 
35     //创建一个信号量集或访问一个已存在的信号量集
36     my_sem = semget(my_key,1,0666 | IPC_CREAT);
37     if(my_sem == -1)
38     {
39         printf("semget error!\n");
40         exit(1);
41     }
42     printf("before setval: %d\n",semctl(my_sem,0,GETVAL));
43     printf("setting val...\n");
44 
45     semctl(my_sem,0,SETVAL,1);
46     printf("After setval:%d\n",semctl(my_sem,0,GETVAL));
47     P(my_sem);
48     while(getchar() != EOF);
49     V(my_sem);
50 
51     sleep(5);
52     semctl(my_sem,0,IPC_RMID);
53 
54     return 0;
55 }

sem2.c

 1 #include<stdio.h>
 2 #include<stdlib.h>
 3 #include<string.h>
 4 #include<unistd.h>
 5 #include<sys/types.h>
 6 #include<sys/ipc.h>
 7 #include<sys/sem.h>
 8 
 9 void P(int semid)
10 {
11     struct sembuf my_buf;
12     memset(&my_buf,0,sizeof(my_buf));
13     my_buf.sem_num = 0;
14     my_buf.sem_op = -1;
15     my_buf.sem_flg = SEM_UNDO;
16     semop(semid,&my_buf,1);
17 }
18 
19 void V(int semid)
20 {
21     struct sembuf my_buf;
22     memset(&my_buf,0,sizeof(my_buf));
23     my_buf.sem_num = 0;
24     my_buf.sem_op = 1;
25     my_buf.sem_flg = SEM_UNDO;
26     semop(semid,&my_buf,1);
27 }
28 
29 int main(int *argc, char *argv[])
30 {
31     key_t my_key = 1234;
32     int my_sem;
33 
34     my_sem = semget(my_key,1,0666);
35     if(my_sem == -1)
36     {
37         printf("semget error!\n");
38         exit(1);
39     }
40     printf("getval:%d\n",semctl(my_sem,0,GETVAL));
41 
42     printf("p....\n");
43     P(my_sem);
44     printf("sem1 finished!\n");
45     V(my_sem);
46 
47     return 0;
48 }