【操作系统实验】Linux环境下用进程实现哲学家进餐问题——C语言完整代码+详细实验报告

实验通过三种方法解决哲学家进餐问题,避免进程死锁。方法一是限制同时进餐人数,方法二是确保左右筷子同时可用,方法三是设定奇偶号哲学家的拿筷顺序。实验中使用信号量机制,如P、V操作,实现进程间的同步和互斥,以确保哲学家不会饿死。通过Linux系统调用实现进程创建和信号量操作,最后展示了三种方法的运行结果。
摘要由CSDN通过智能技术生成

【注意】代码在文末,以下为详细实验报告

【实验目的】

  以哲学家进餐问题为例,学习并熟悉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操作分别使用两种代码书写方式
在这里插入图片描述

            图2 P操作(wait)
在这里插入图片描述

            图3 V操作(signal)

四、程序流程图
1.方法一流程图
内容:至多四个人同时拿左边的筷子,保证至少有一个人可以进餐,最终释放筷子使更多的人进餐。
在这里插入图片描述

            图4 方法一流程图
2.方法二流程图
内容:仅当哲学家的左右两支筷子均可用时,才允许他拿起筷子进餐。
在这里插入图片描述

            图5 方法二流程图
3.方法三流程图
内容:规定奇数号哲学家先拿起其左边的筷子,再拿右边的,偶数号哲学家则相反。总会有某一人进餐。

在这里插入图片描述

            图6 方法三流程图

五、伪代码
1.方法一伪代码
内容:至多四个人同时拿左边的筷子,保证至少有一个人可以进餐,最终释放筷子使更多的人进餐。

semaphore chopstick[5] = {1, 1, 1, 1, 1}; 
semphore room=4;
Pi ()
{ 
	while1{ 
		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 ()
{ 
	while1{ 
		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 ()
{ 
while1{ 
     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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <sys/types.h>

#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;
}

评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小天才才

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值