【注意】代码在文末,以下为详细实验报告
【实验目的】
以哲学家进餐问题为例,学习并熟悉Linux下进程通信、同步机制的具体实现方法,主要是了解并掌握信号量机制和避免死锁的使用方法,使得不会出现哲学家饥饿的情况,并进一步熟悉Linux系统的相关指令的调用。
【实验内容】
5个位哲学家共用一张圆桌,分别坐在周围的五张椅子上,在圆桌上有5个盘子和5双筷子,他们的生活方式是交替的进行思考和进餐。平时哲学家进行思考,饥饿时便试图取用其左右最靠近他的筷子,只有在他拿到两只筷子时才能进餐。进餐完成之后,放下筷子继续思考。
利用进程实现哲学家进餐问题,使哲学家不会出现饿死现象,亦即避免进程之间的死锁问题。筷子为临界资源,一段时间内只允许一位哲学家使用,为实现对筷子的互斥使用, 可用一个信号量表示一只筷子;五个信号量构成信号量数组Chopstick[i],Chopstick[(i+1)%5]。
【实验环境】(含主要设计设备、器材、软件等)
【实验步骤、过程】(含原理图、流程图、关键代码,或实验过程中的记录、数据等)
一、解决方法
哲学家进餐问题避免哲学家被饿死的解决方法有:
(1)至多四个人同时拿左边的筷子,保证至少有一个人可以进餐,最终释放筷子使更多的人进餐。
(2)仅当哲学家的左右两支筷子均可用时,才允许他拿起筷子进餐。
(3)规定奇数号哲学家先拿起其左边的筷子,再拿右边的,偶数号哲学家则相反。总会有某一人进餐。
利用进程实现以上三种解决方法。
二、数据结构
对于第一个方法,定义了一个变量room,使得最多只允许四个哲学家同时进餐,避免死锁,而对于第二个方法,定义了一个记录型信号量mutex,对左右的筷子进行互斥访问,进而避免死锁。
三、算法描述
1.方法一
内容:至多四个人同时拿左边的筷子,保证至少有一个人可以进餐,最终释放筷子使更多的人进餐。
步骤:
(1)初始化筷子的信号量,同时初始化room为4,保证最多四个哲学家进餐。
(2)对每一个哲学家在进餐之前进行如下原子操作
Wait(room),Wait(chopstick[i]),Wait(chopstick[(i+1)%5]),
Signal(chopstick[i]),Signal(chopstick[(i+1)%5]),Signal(room)
(3)若出现四个哲学家同时拿起了左手边的筷子,则第五个人必须等待,直到他两边的筷子空闲才可以进行进餐,这样就避免了哲学家在进餐过程中出现饥饿现象,也就是死锁。
(4)哲学家开始进餐。
(5)删除信号量和共享内存,释放空间。
2.方法二
内容:仅当哲学家的左右两支筷子均可用时,才允许他拿起筷子进餐。
步骤:
(1)初始化筷子的信号量,同时初始化mutex为5,对筷子进行互斥访问。
(2)对每一个哲学家在进餐之前进行如下原子操作
Wait(mutex),Wait(chopstick[i]),Wait(chopstick[(i+1)%5]),
Signal(mutex),Signal(chopstick[i]),Signal(chopstick[(i+1)%5])
在这里是先执行mutex的V操作,原因在于如果哲学家已经拿起了一双筷子开始进餐之后,为了不让别的哲学家一直等待,需要立即释放该信号量,以便于与他不相邻的哲学家可以进餐。
(3)哲学家开始进餐
(4)删除信号量和共享内存,释放空间
3.方法三
内容:规定奇数号哲学家先拿起其左边的筷子,再拿右边的,偶数号哲学家则相反。总会有某一人进餐。
步骤:
(1)初始化筷子的信号量。
(2)对偶数号哲学家在进餐之前进行如下原子操作
Wait(chopstick[(i+1)%5]),Wait(chopstick[i]),
Signal(chopstick[(i+1)%5]),Signal(chopstick[i])
在这里是规定偶数号哲学家先拿起右边的筷子,再拿起左边的筷子。
(3)对奇数号哲学家在进餐之前进行如下原子操作
Wait(chopstick[i]),Wait(chopstick[(i+1)%5]),
Signal(chopstick[i]),Signal(chopstick[(i+1)%5])
在这里是规定奇数号哲学家先拿起左边的筷子,再拿起右边的筷子。
(4)哲学家开始进餐
(5)删除信号量和共享内存,释放空间
4.进行错误判断
这里进行的错误判断主要是通过调用函数来判断,信号量是否成功初始化、进程是否成功创建以及共享内存是否设置好,详见如下
图1 error 判断
5.原子操作——P操作和V操作
P操作、V操作对封装的信号量进行“减1”“加1”操作。
在LINUX下,通过使用 semop 函数改变信号量的值。以下P、V操作分别使用两种代码书写方式
图3 V操作(signal)
四、程序流程图
1.方法一流程图
内容:至多四个人同时拿左边的筷子,保证至少有一个人可以进餐,最终释放筷子使更多的人进餐。
图4 方法一流程图
2.方法二流程图
内容:仅当哲学家的左右两支筷子均可用时,才允许他拿起筷子进餐。
图5 方法二流程图
3.方法三流程图
内容:规定奇数号哲学家先拿起其左边的筷子,再拿右边的,偶数号哲学家则相反。总会有某一人进餐。
图6 方法三流程图
五、伪代码
1.方法一伪代码
内容:至多四个人同时拿左边的筷子,保证至少有一个人可以进餐,最终释放筷子使更多的人进餐。
semaphore chopstick[5] = {
1, 1, 1, 1, 1};
semphore room=4;
Pi ()
{
while(1)
{
P(room);
P(chopstick [i] );
P(chopstick[(i+1)%5]);
进餐;
V(chopstick [(i+1)%5];
V(chopstick [i]);
V(room);
思考
}
}
2.方法二伪代码
内容:仅当哲学家的左右两支筷子均可用时,才允许他拿起筷子进餐。
semaphore chopstick[5] = {
1, 1, 1, 1, 1};
semaphore mutex=1;
Pi ()
{
while(1)
{
P(mutex);
P(chopstick [i] );
P(chopstick[(i+1)%5]);
V(mutex);
进餐;
V(chopstick[(i+1)%5]);
V(chopstick [i]);
思考
}
}
3.方法三伪代码
内容:规定奇数号哲学家先拿起其左边的筷子,再拿右边的,偶数号哲学家则相反。总会有某一人进餐。
semaphore chopstick[5] = {
1, 1, 1, 1, 1};
Pi ()
{
while(1)
{
if(i mod 2==0) //偶数号哲学家,先右后左
{
P(chopstick[(i+1)%5]);
P(chopstick [i] );
进餐;
V(chopstick [i]);
V(chopstick[(i+1)%5]);
}
else //奇数号哲学家,先左后右
{
P(chopstick [i] );
P(chopstick[(i+1)%5]);
吃饭;
V(chopstick[(i+1)%5]);
V(chopstick [i]);
}
}
}
六、编译指令
//方法一
$ gcc -o philosophers_1.out philosophers_1.c
$ ./philosophers_1.out
//方法二
$ gcc -o philosophers_2.out philosophers_2.c
$ ./philosophers_2.out
//方法三
$ gcc -o philosophers_3.out philosophers_3.c
$ ./philosophers_2.out
图7 所有编译指令
七、运行结果
1.方法一运行结果
内容:至多四个人同时拿左边的筷子,保证至少有一个人可以进餐,最终释放筷子使更多的人进餐。
图8 方法一运行结果
2.方法二运行结果
内容:仅当哲学家的左右两支筷子均可用时,才允许他拿起筷子进餐。
图9 方法二运行结果
3.方法三运行结果
内容:规定奇数号哲学家先拿起其左边的筷子,再拿右边的,偶数号哲学家则相反。总会有某一人进餐
图10 方法三运行结果
【实验结果或总结】(对实验结果进行相应分析,或总结实验的心得体会,并提出实验的改进意见)
本次实验是关于哲学家进餐互斥和同步的问题。问题的实质是如何避免饥饿,也就是死锁,实验一共给出了三种解决方案,分别是设置room,至多四人进餐;设置mutex,当左右筷子都被哲学家拿到方可进餐;规定奇数号和偶数号哲学家的进餐规则。
通过本次实验,我对操作系统的P,V操作和死锁有了进一步的认识,深入地了解P,V操作的实质和避免死锁的重要性,也通过课本的理论知识进一步阐述了现实的实际问题。
代码
#include
#include
#include
#include
#include
#include
#include
#include
#define MAX_BUFFER_SIZE 5
#define SHM_MODE 0600
#define SEM_MODE 0600
#define mutex 5
#define true 1
#define room 6
int chopstick[5] = {
0,1,2,3,4};
int sem_id = -1;
pid_t philosopher;
//P操作
void Wait(int sem_id,int sem_num)
{
struct sembuf buf;
buf.sem_num = sem_num;
buf.sem_op = -1;
buf.sem_flg = SEM_UNDO;
if(semop(sem_id,&buf,1) < 0)
{
perror("wait failed");
exit(1);
}
}
//V操作
void Signal(int sem_id,int sem_num)
{
struct sembuf buf;
buf.sem_num = sem_num;
buf.sem_op = 1;
buf.sem_flg = SEM_UNDO;
if(semop(sem_id,&buf,1) < 0)
{
perror("signal failed");
exit(1);
}
}
void think(int i)
{
printf("the philosopher of %d is thinking(pid is %d)\n",i,getpid());
}
void eat(int i)
{
printf("the philosopher of %d is eating(pid is %d)\n",i,getpid());
}
void Philosophers1(int sem_id,int i)
{
int j;
for(j=0;j<2;j++){
think(i);
Wait(sem_id,room);
Wait(sem_id,chopstick[i]);
Wait(sem_id,chopstick[(i+1)%5]);
eat(i);
Signal(sem_id,chopstick[i]);
Signal(sem_id,chopstick[(i+1)%5]);
printf("the process of %d(pid is %d,ppid is %d)has finished eating\n",i,getpid(),getppid());
Signal(sem_id,room);
fflush(stdout);
}
exit(0);
}
int main()
{
int i = 0;
if((sem_id = semget(IPC_PRIVATE,7,SEM_MODE)) < 0)
{
perror("create semaphore failed! \n");
exit(1);
}
if(semctl(sem_id,mutex,SETVAL,1) == -1)
{
perror("sem set value error! \n");
exit(1);
}
for(i=0;i<5;i++){
if(semctl(sem_id,chopstick[i],SETVAL,1) == -1)
{
perror("sem set value error! \n");
exit(1);
}
}
if(semctl(sem_id,room,SETVAL,4) == -1)
{
perror("sem set value error! \n");
exit(1);
}
for(i=0;i<5;i++){
philosopher = fork();
if(philosopher < 0){
perror("the fork failed");
exit(1);
}
else if(philosopher == 0){
Philosophers1(sem_id,i);
}
}
while (wait(0) != -1);
shmctl(sem_id,IPC_RMID,0);
printf("finish!!!\n");
fflush(stdout);
exit(0);
return 0;
}