操作系统第6次实验报告:使用信号量解决进程互斥访问

  • 姓名:危文涛
  • 学号:201821121048
  • 班级:计算1812

1. 哲学家进餐问题

五个哲学家共用一张圆桌,分别坐在周围的五张椅子上,在桌子上有五只碗和五只筷子,他们的生活方式是交替地进行思考和进餐。平时,一个哲学家进行思考,饥饿时便试图取用其左右最靠近他的筷子,只有在他拿到两只筷子时才能进餐。进餐毕,放下筷子继续思考。

如图:

 

2. 给出伪代码

semaphore chopstick[5] = {1,1,1,1,1};
while(true)
{
/*规定当哲学家饥饿时,先拿左边的筷子,再拿右边的筷子*/
wait(chopstick[i]);
wait(chopstick[(i+1)%5]);

// 进餐
/*同样的,规定当哲学家进餐完成后,先放下左边的筷子,再放下右边的筷子*/
signal(chopstick[i]);
signal(chopstick[(i+1)%5]);
}

 这段代码可以保证不会有两个相邻的哲学家同时进餐,但是可能引起死锁的情况。假定五位哲学家同时饥饿拿起的左边的筷子,就会使五个chopstick都为0,所以他们试图去拿右手边的筷子时,都将拿不到筷子而陷入等待,无法解除,形成死锁。

 

 

为解决死锁问题,提出以下代码改进

semaphore chopstick[5]={1,1,1,1,1};
void philosopher(int i)
{
while(true)
{
think();
if(i%2 == 0) //偶数哲学家拿起筷子为先右后左。
{
wait (chopstick[(i + 1)%5]) ;
wait (chopstick[i]) ;
eat();
signal (chopstick[(i + 1)%5]) ;
signal (chopstick[i]) ;
}
else //奇数哲学家拿起筷子为先左后右。
{
wait (chopstick[i]) ;
wait (chopstick[(i + 1)%5]) ;
eat();
signal (chopstick[i]) ;
signal (chopstick[(i + 1)%5]) ;
}
}
}

规定哲学家的座位奇数和偶数号,奇数号哲学家先动左筷子后右筷子,而偶数哲学家相反先动右筷子再动左筷子。所以,即是1、2号哲学家竞争1号筷子,3、4号哲学家竞争3号筷子。即五个哲学家都竞争奇数号筷子,竞争完成后,再去竞争偶数号筷子,这样总会有一个哲学家能获得两支筷子而进餐。

3. 给出完整代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/wait.h>
union semun{
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *__buf;
};
#define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
//相当于P操作
int wait_1chopstick(int no,int semid)
{
struct sembuf sb = {no,-1,0};
int ret= semop(semid,&sb,1);//semop()系统调用在semid标识的信号量集中的信号量上执行一个或 多个up或down操作,可用于进程间的同步和互斥。
if(ret < 0) {
ERR_EXIT("semop");
}
return ret;
}
//相当于V操作
int free_1chopstick(int no,int semid){
struct sembuf sb = {no,1,0};
int ret;
ret = semop(semid,&sb,1);
if(ret < 0) {
ERR_EXIT("semop");
}
return ret;
}
#define DELAY (rand() % 5 + 1)
//相当于P操作
void wait_for_2chopstick(int no,int semid){
//哲学家左边的筷子号数
int left = no;
//右边的筷子
int right = (no + 1) % 5;
//筷子值是两个
//第一个参数是编号
struct sembuf buf[2] = {
{left,-1,0}, //左右两只筷子都能用时才进餐
{right,-1,0}
};
//信号集中有5个信号量,只是对其中的//资源sembuf进行操作
semop(semid,buf,2);
}
//相当于V操作
void free_2chopstick(int no,int semid){
int left = no;
int right = (no + 1) % 5;
struct sembuf buf[2] = {
{left,1,0},
{right,1,0}
};
semop(semid,buf,2);
}
void philosophere(int no,int semid){
srand(getpid());
for(;;) {
#if 1
//当两筷子都可用时哲学家才能吃饭,这样不相邻的哲学家就可吃上饭
printf("%d 正在思考\n",no);
sleep(DELAY);
printf("%d 饿了\n",no);
wait_for_2chopstick(no,semid);//拿到筷子才能吃饭
printf("%d 正在进餐\n",no);
sleep(DELAY);
free_2chopstick(no,semid);//释放筷子
#else
//可能会造成死锁
int left = no;
int right = (no + 1) % 5;
printf("%d 正在思考\n",no);
sleep(DELAY);
printf("%d 饿了\n",no);
wait_1chopstick(left,semid);
sleep(DELAY);
wait_1chopstick(right,semid);
printf("%d 正在进餐\n",no);
sleep(DELAY);
free_1chopstick(left,semid);
free_1chopstick(right,semid);
#endif
}
}
int main(int argc,char *argv[]){
int semid;
//创建信号量
semid = semget(IPC_PRIVATE,5,IPC_CREAT | 0666);
if(semid < 0) {
ERR_EXIT("semid");
}
union semun su;
su.val = 1;
int i;
for(i = 0;i < 5;++i) {
semctl(semid,i,SETVAL,su);//semctl()系统调用在一个信号量集或集合中的单个信号量上执行各种控制操作
}
//创建4个子进程
int num = 0;
pid_t pid;
for(i = 1;i < 5;++i) {
pid = fork();
if(pid < 0) {
ERR_EXIT("fork");
}
if(0 == pid) {
num = i;
break;
}
}
philosophere(num,semid);//num 代表进程号
return 0;
}

4. 运行结果并解释

运行结果:

 

 刚开始5个哲学家都在思考,之后是0号和3号开始就餐,错开了左右筷子的问题,之后4号2号1号都饿了且0号就餐完毕,但三号还没有就餐完毕,所以是1号开始就餐,之后3号就餐完毕进入思考,4号就可以开始就餐。之后的思路与上述类似。这样就解决了死锁问题。

 

posted @ 2020-05-30 14:15  kaxment  阅读(261)  评论(0编辑  收藏  举报