1、 什么是信号量?
在对于临界区资源管理过程中,为了防止多个程序同时访问一个共享资源而引发的一系列问题。比如:死锁。
为了解决这种问题,巨人们就发明了信号量。
信号量就是为了解决在一个临界区只有一个进程访问它,也就是说信号量相当于交警,来协调进程对共享资源有序的访问而不造成死锁等行为。
信号量是一个特殊的变量,程序对它访问都是原子操作,并且她只能进行两种操作,等待(P信号量)和发送(V信号量) 。
P(sv):如果sv的值大于0,说明资源可用,就给它减1,如果它等于0,就挂起该进程说明资源不可用,直到资源可用。
V(sv):如果sv值大于0,并且有其他进程因为等待sv而被挂起,则该进程恢复运行,如果没有进程等待,则sv加1
2、信号量的函数操作
- 2.1、semget函数
它的作用是创建一个新信号量集或获取一个已有的信号量集
int semget(key_t key, int nsems, int semflg);
- key:每一个信号量集都有一个唯一的信号量集标识符id,这个id是通过一个键值key获得,key值可以通过ftok函数获得。而semget必须有一个key值,并由系统生成一个相应的进程标识符id作为semget的返回值。如果多个进程共用同一个key值,那么key将负责协调工作。
- nsems:指定信号量集的数目
- semflg:当信号量集不存在时创建一个信号量用IPC_CREAT,当信号量已经存在时用IPC_CREAT|IPC_EXCL,返回一个错误。
- semget成功返回一个相应信号量集标识符,失败返回-1.
- 2.2、 semop函数
作用:改变信号量的值,只要是为PV操作做准备的
int semop(int semid, struct sembuf *sops, unsigned nsops);
- semid:信号量集标识符id,semget的返回值
struct sembuf *sops:sops指向一个结构体数组的指针,这个结构体对一个特定的信号进行操作,因此你要熟悉该结构体里的内容才能进行更好的操作。
struct sembuf
{
unsigned short sem_num; //信号量在信号集中的下标,0代表第一个信号,1代表第二个信号
short val(sem_op); //操作类型
short sem_flg; //操作标志
}; 。若val>0进行V操作信号量值加val,表示进程释放控制的资源
若val<0进行P操作信号量值减val,若(semval-val)<0(semval为该信号量值),则调用进程挂起,直到资源可用;
若设置IPC_NOWAIT不会睡眠,进程直接返回EAGAIN错误
若val==0时阻塞等待信号量为0,调用进程进入睡眠状态,直到信号值为0;若设置IPC_NOWAIT,进程不会睡眠,直接返回EAGAIN错误sem_flg:
1、若为0 设置信号量的默认操作
2、IPC_NOWAIT设置信号量操作不等待
3、SEM_UNDO 选项会让内核记录一个与调用进程相关的UNDO记录,如果该进程崩溃,则根据这个进程的UNDO记录自动恢复相应信号量的计数值
- nsops:数组元素个数
- 2.3、semctl函数
作用:控制信号量集
int semctl(int semid,int senum,int cmd,...)
返回值:信号量的当前值
- senum:信号集中信号量的编号(下标),第一个信号量从0开始
- cmd:红色字体是我们一般常用的
命令 | 解释 |
---|---|
IPC_STAT | 从信号量集上检索semid_ds结构,并存到semun联合体参数的成员buf的地址中 |
IPC_SET | 设置一个信号量集合的semid_ds结构中ipc_perm域的值,并从semun的buf中取出值 |
IPC_RMID | 从内核中删除信号量集合 |
GETALL | 从信号量集合中获得所有信号量的值,并把其整数值存到semun联合体成员的一个指针数组中 |
GETNCNT | 返回当前等待资源的进程个数 |
GETPID | 返回最后一个执行系统调用semop()进程的PID |
GETVAL | 返回信号量集合内单个信号量的值 |
GETZCNT | 返回当前等待100%资源利用的进程个数 |
SETALL | 与GETALL正好相反 |
SETVAL | 用联合体中val成员的值设置信号量集合中单个信号量的值 |
对于该函数,只有当cmd取某些特定的值的时候,才会使用到第4个参数,第4个参数它通常是一个union semum结构,定义如下
union semun
{
int val;//当执行SETVAl命令时,用到该值,用于指定把信号设计为什么值
struct semid_ds *buf; // 当执行IPC_STAT/IPC_SET时,它代表内核中所使用内部信号量数据结构的一个复制 ,用到成员:buf
unsigned short *array; //当执行GETALL/SETALL命令时,他代表指向一个数组的指针,在设置或获取集合中所有信号量的值的过程中,将会用到该数组,用到成员:array
struct seminfo *__buf; //设计IPC_INFO时,会使用,但是一般很少用到
}
3、哲学家就餐问题:
- 3.1:问题描述
假设有五位哲学家围坐在一张圆形餐桌旁,做以下两件事情之一:吃饭,或者思考。吃东西的时候,他们就停止思考,思考的时候也停止吃东西。餐桌中间有一大碗米饭,每两个哲学家之间有一只筷子。因为用一只筷子很难吃到米饭,所以假设哲学家必须用两只筷子吃东西。他们只能使用自己左右手边的那两只筷子。所以现在就出现了问题。
- 3.2:问题分析
当五个哲学家在某个时刻同时申请到拿筷子操作时,并且申请成功了,这时他们都拿到了左手边的一只筷子,但是当他们申请右手边筷子时,发现右手边筷子没有资源只能继续等,这时五个哲学家都在等,这样就产生了死锁。
- 3.3 : 解决思路
3.3.1:给每个哲学家和筷子进行编号,从0开始到4,因为senum中第一个信号量的下标是从0开始的。
3.3.2:每个筷子代表一个信号量
3.3.3:每个哲学家在取筷子时每次拿左右两只筷子,并且执行P操作使其它哲学家处于等待状态,直到该哲学家吃饱,放下筷子(释放资源)时,其它哲学家才可以拿筷子,从而继续循环。
3.3.4:通过信号量以及PV操作保证每次只有一个哲学家拿一双筷子,其它哲学家都等待,直到该哲学家释放资源。从而避免死锁。
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/sem.h>
int id;
union semun
{
int val;//设置信号量值
};
void P(int num)
{
struct sembuf sops[2] = //两只筷子啊
{
{num,-1,0},
{(num+1)%5,-1,0}
};
semop(id,sops,2);
}
void V(int num)
{
struct sembuf sops[2] = //两只筷子啊
{
{num,1,0},
{(num+1)%5,1,0}
};
semop(id,sops,2);
}
void zxj(int num)
{
while(1)
{
printf("%d哲学家思考中...\n",num);
sleep(rand()%5);
printf("%d哲学家饿了,准备拿筷子\n",num);
P(num);
printf("%d哲学家开始吃东西...\n",num);
sleep(rand()%5);
printf("%d哲学家吃饱了\n",num);
V(num);
}
}
int main()
{
srand(getpid());//以当前进程返回的pid值作为产生随机值的源头
key_t key = ftok(".",'a');//"."代表当前路径
if(key < -1)
{
perror("ftok\n");
return -1;
}
id = semget(key,5,IPC_CREAT|0644);
if(id == -1)
{
perror("perror semget\n");
exit(-2);
}
union semun su = {1};//每个哲学家的筷子数为1
int i = 0;
for(i = 0;i < 5 ; i++)
{
semctl(id,i,SETVAL,su);
}
int num = 0;
for(i = 1;i < 5; i++ )
{
pid_t pid = fork();
if(pid == 0)
{
num = i;
break;
}
}
zxj(num);
return 0;
}