一、信号量
以一个停车场的运作为例。假设停车场只有三个车位,一开始三个车位都是空的。这时如果同时来了五辆车,看门人允许其中三辆直接进入,然后放下车拦,剩下的车则必须在入口等待,此后来的车也都不得不在入口处等待。这时,有一辆车离开停车场,看门人得知后,打开车拦,放入外面的一辆进去,如果又离开两辆,则又可以放入两辆,如此往复。
在这个停车场系统中,车位是公共资源,每辆车好比一个线程,看门人起的就是信号量的作用。
同样的,为了防止因多个程序同时访问一个共享资源而引发的一系列问题,我们需要一种方法,它可以通过生成并使用令牌来授权,在任意一时刻只能有一个执行线程访问代码的临界区域。
临界区域是指数据更新的代码需要独占式地执行,而信号量就可以提供这样的一种访问机制,让一个临界区同一时间只有一个线程在访问它,也就是说信号量是用来调协进程对共享资源的访问的。其中共享内存的使用就要用到信号量。
二、P,V原语
信号量本质上是⼀一个计数器,只能进行两种操作等待和发送信号,即P(sv)和V(sv),他们的行为:
P(s)
{ s.value = s.value--;
if (s.value < 0)
{
该进程状态置为等待状状态
将该进程的PCB插⼊入相应的等待队列s.queue末尾
}
}
V(s)
{ s.value = s.value++;
if (s.value < =0)
{
唤醒相应等待队列s.queue中等待的⼀一个进程
改变其状态为就绪态
并将其插⼊入就绪队列
}
}
三、信号量集函数
1、semget 函数--用来创建和访问⼀一个信号量集
原型 : int semget(key_t key, int nsems, int semflg);
参数:
key: 信号集的名字
nsems:信号集中信号量的个数
semflg: 由九个权限标志构成,它们的用法和创建文件时使⽤的mode模式标志是一样的
返回值:成功返回⼀一个⾮非负整数,即该信号集的标识码;失败返回-1
2、shmctl 函数--用于控制信号量集
原型 : int semctl(int semid, int semnum, int cmd, ...);
参数 :
semid:由semget返回的信号集标识码
semnum:信号集中信号量的序号
cmd:将要采取的动作(有三个可取值) 最后一个参数根据命令不同而不同 ,如下:
返回值:成功返回0;失败返回-1
3、semop函数--用来创建和访问⼀个信号量集
原型 : int semop(int semid, struct sembuf *sops, unsigned nsops);
参数 :
semid:是该信号量的标识码,也就是semget函数的返回值
sops:是个指向⼀一个结构数值的指针
nsops:信号量的个数
返回值:成功返回0;失败返回-1
四、代码
1、Makefile
1 test_sem:comm.c test_sem.c
2 gcc -o $@ $^
3
4 .PHONY:clean
5 clean:
6 rm -f test_sem
2、comm.h
1 #ifndef _COMM_H_
2 #define _COMM_H_
3
4 #include<stdio.h>
5 #include<sys/types.h>
6 #include<sys/ipc.h>
7 #include<sys/sem.h>
8
9 #define PATHNAME "."
10 #define PROJ_ID 0x6666
11
12 union semun{
13 int val;
14 struct semid_ds *buf;
15 unsigned short *array;
16 struct seminfo *_buf;
17 };
18
19 int createSemSet(int nums);
20 int initSem(int semid,int nums,int initVal);
21 int getSemSet(int nums);
22 int P(int semid,int who);
23 int V(int semid,int who);
24 int destroySemSet(int semid);
25
26 #endif
3、comm.c
1 #include"comm.h"
2
3 static int commSemSet(int nums,int flags){
4 key_t key=ftok(PATHNAME,PROJ_ID);
5 if(key<0){
6 perror("ftok");
7 return -1;
8 }
9 int semid=semget(key,nums,flags);
10 if(semid<0){
11 perror("semget");
12 return -2;
13 }
14 return semid;
15 }
16
17 int createSemSet(int nums){
18 return commSemSet(nums,IPC_CREAT|IPC_EXCL|0666);
19 }
20
21 int getSemSet(int nums){
22 return commSemSet(nums,IPC_CREAT);
23 }
25 int initSem(int semid,int nums,int initVal){
26 union semun _un;
27 _un.val=initVal;
28 if(semctl(semid,nums,SETVAL,_un)<0){
29 perror("semctl");
30 return -1;
31 }
32 return 0;
33 }
34
35 static int commPV(int semid,int who,int op){
36 struct sembuf _sf;
37 _sf.sem_num=who;
38 _sf.sem_op=op;
39 _sf.sem_flg=0;
40 if(semop(semid,&_sf,1)<0){
41 perror("semop");
42 return -1;
43 }
44 return 0;
45 }
46
47 int P(int semid,int who){
48 return commPV(semid,who,-1);
49 }
50
51 int V(int semid,int who){
52 return commPV(semid,who,1);
53 }
54
55 int destorySemSet(int semid){
56 if(semctl(semid,0,IPC_RMID)<0){
57 perror("senctl");
58 return -1;
59 }
60 }
4、test_sem.c
1 #include"comm.h"
2
3 int main(){
4 int semid=createSemSet(1);
5 initSem(semid,0,1);
6 pid_t id=fork();
7 if(id==0){
8 int _semid=getSemSet(0);
9 while(1){
10 P(_semid,0);
11 //P(_semid,0);
12 printf("A");
13 fflush(stdout);
14 usleep(123456);
15 printf("A ");
16 fflush(stdout);
17 usleep(321456);
18 V(_semid,0);
19 //V(_semid,0);
20 }
21 }
22 else{
23 while(1){
24 P(semid,0);
25 //P(semid,0);
26 printf("B");
27 fflush(stdout);
28 usleep(123456);
29 printf("B ");
30 fflush(stdout);
31 usleep(321456);
32 V(semid,0);
33 //V(semid,0);
34 }
35 wait(NULL);
36 }
37 destorySemSet(semid);
38 return 0;
39 }
结果:
所有的AB成对出现,不会出现交叉的情况,如果去掉PV现象是什么呢?如上代码中主函数将PV注释: