当前位置:文档之家› 利用信号量机制解决哲学家进餐问题

利用信号量机制解决哲学家进餐问题

利用信号量机制解决哲学家进餐问题
利用信号量机制解决哲学家进餐问题

利用信号量机制解决哲学家进餐问题

————————————————————————————————作者:————————————————————————————————日期:

成绩:课程设计报告

课程名称:课程设计(UNIX程序设计)

设计题目:利用信号量机制解决哲学家进餐问题

姓名:

专业:网络工程

班级:

学号:

计算机科学与技术学院

网络系

2013年12月28日

设计项目:利用信号量机制解决哲学家进餐问题

一、选题背景

1965年,数学家迪杰斯特拉提出,并成为经典的IPC问题—哲学家进餐问题。该问题的简单描述如下:五个哲学家围坐在一张圆桌周围,每个哲学家面前都一盘通心粉。由于通心粉很滑,需要两把叉子才能夹住。哲学家的生活中有两种交替活动时段,吃饭(EATING)和思考(THINKING)。当一个哲学家觉得饿了时,他就试图分两次去取左手边和右手边的叉子,每次拿一把,但不分次序。如果成功拿到两把叉子,就进入吃饭状态,吃完后放下叉子继续思考。

二、设计思路

1.每个哲学家的行为活动状态为两种:进餐(EATING)和思考(THINKING)。因此创建一个有5个元素的状态数组,每个数组元素的状态值为EATING或者THINKING。

2.五个哲学家围坐成一圈,每两个人中间有一个叉子,即每个哲学家的边和右边有一个叉子,但这个叉子需要和旁边的邻居竞争使用。对于每一个哲学家来说,其只有成功获得两个叉子,才能进入进餐状态。在进完餐后,需要成功放下手中的两个叉子,才能进入思考的状态。换个角度的描述就是,每个哲学家查询左右边的邻居当前状态,如果左右的邻居当前状态都为THINKING,则该哲学家可以进餐;如果左右邻居当前状态不都是THINKING,则哲学家不能进餐。因此可以为每一个哲学家设置一个信号量,来描述哲学家的活动状态。

3.因为五只叉子做多只能允许两个哲学家进餐,所以可以将桌子作为一个临界资源。通过设置一个互斥信号量来限制对临界资源的访问数。

4.创建两个动作函数,对应于每个哲学家的获取两把叉子和放下两把叉子的动作。而每个动作都需要对互斥信号量和哲学家信号量进行访问操作,因此创建原子操作P和原子操作V,来执行对信号量的消耗和释放操作。

5.利用父进程创建五个子进程来模拟五个哲学家,在每个子进程中执行PHILOSOPHER (phi_num)函数来模拟每个哲学家进入哲学家进餐问题活动。

三、主要问题的解决方法和关键技术

1.共享状态数组问题。

问题描述:因为状态数组是共享的,而每个模拟哲学家的子进程是相互独立的,有自己的地址空间,在进程之间共享使用状态数组出现问题。

解决方法:父进程通过利用UNIX系统进程通信机制中共享内存机制的shmget()和shmat系统调用创建一个共享内存区,并将状态数组地址链接到进程地址空间,成功的解决了该问题。

2.信号量创建及初始化问题

问题描述:整个程序使用两个不同的信号量,一个是记录型信号量数组,一个是互斥

信号量,并且在信号量创建初就需要对信号量进行初始化,这样才能保证接下来的子进程运行时,五个子进程面对的是相同值的信号量数组。

解决方法:父进程通过利用UNIX系统进程通信机制中信号量机制的semget()系统调用和semctl()系统调用,成功创建一个五元素的信号量数组和一个元素的信号量数组,并将其在初始为设计的初始值,保证了程序后续操作的正确。

3.P和V原子操作问题

问题描述:在子进程中的对信号量的操作必须是原子操作P和V,而且由于在动作函数中需要调用P和V原子操作,所以必须保证P和V操作的原子性,否则函数之间参数的传递将出现不一致。

解决方法:利用UNIX系统进程通信机制中信号量机制的semop()系统调用,封装P 和V操作函数,保证了函数的原子性。

4.进程创建的准确时刻问题

问题描述:根据设计的描述,程序是通过五个子进程来模拟五个哲学家,但对进程创建的先后顺序没有要求,又由于进程分配到时间片是有限的,并且在创建的过程中要保证五个子进程都是统一父进程创建的,所以对于进程的创建时刻不太容易确定。

解决方法:利用的UNIX系统的进程创建fork()系统调用,创建一个有五个子进程的进程扇,成功的解决创建准确时刻问题

5.哲学家动作函数编译问题

问题描述:在哲学家的三个动作函数中需要对两个类型的信号量进行操作,所以不可避免的需要调用P和V原子操作。尽管在动作函数定义之前,已经声明了P和V函数。但是由于其原子性,在动作函数中直接使用P,V函数导致了严重的编译错误问题。

解决方法:通过在各个函数参数列表中添加需要指向将使用的P和V函数指针,并在使用时采用(*P)和(*V)形式来解除引用操作,成功的解决了编译错误问题。

四、程序流程图

约定:

Take_forks 函数动作 TKFK

Put_forks 函数动作 PTFK

Test 函数动作 TEST

THINKING 思考状态 THINK

EATING 进餐状态 EAT

N

Y

N

Y

Y N

五、 原程序清单

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#define N 5

#define LEFT(i) (i+N-1)%N

#define RIGHT(i) (i+1)%N

TKFK TEST PTFK

#define THINKING 0

#define EATING 1

//#include"behavior_philosophy.h"

void philosopher(int , char*, int , int , void (*)(int, int), void (*)(int, int)); void take_forks(int , char* , int , int ,void (*)(int, int), void (*)(int, int)); void put_forks(int , char* , int , int ,void (*)(int, int), void (*)(int, int)); void test(int , char* , int ,void (*)(int, int));

void think(int );

void eat(int );

void P(int , int );

void V(int , int );

/*------------------------哲学家动作-----------------*/

void philosopher(int i, char* state, int sem_phiid, int sem_mutexid, void (*P)(int, int), void (*V)(int, int)){

sleep(1);

think(i);

while(1){

take_forks(i, state, sem_phiid, sem_mutexid, P, V);

eat(i);

put_forks(i, state, sem_phiid, sem_mutexid, P, V);

think(i);

}

}

void take_forks(int i, char* state, int sem_phiid, int sem_mutexid, void (*P)(int, int) , void (*V)(int, int)){

(*P)(sem_mutexid, 0);

state[i] = THINKING;

test(i, state, sem_phiid, V);

(*V)(sem_mutexid, 0);

(*P)(sem_phiid, i);

}

void put_forks(int i, char* state, int sem_phiid, int sem_mutexid,void (*P)(int, int), void (*V)(int, int)){

(*P)(sem_mutexid, 0);

state[i] = THINKING;

test(LEFT(i), state, sem_phiid, V);

test(RIGHT(i), state, sem_phiid, V);

(*V)(sem_mutexid, 0);

}

void test(int i, char* state, int sem_phiid,void (*V)(int, int)){

if(state[i] == THINKING && state[LEFT(i)] != EATING && state[RIGHT(i)]!=EATING){

state[i] = EATING;

(*V)(sem_phiid, i);

}

}

void think(int i){

printf("philosopher:%d>>>>>is THINKING.\n", i);

}

void eat(int i){

printf("I'm philosopher:%d>>>>>is EATING.and will eating %d seconds!\n", i, sleep(2));

}

/*---------------------P,V原子操作------------*/

void P(int semid, int index){

struct sembuf sema_buffer;

sema_buffer.sem_num = index;

sema_buffer.sem_op = -1;

sema_buffer.sem_flg = SEM_UNDO;

semop(semid, &sema_buffer, 1);

}

void V(int semid, int index){

struct sembuf sema_buf;

sema_buf.sem_num = index;

sema_buf.sem_op = 1;

sema_buf.sem_flg = SEM_UNDO;

semop(semid, &sema_buf, 1);

}

/*-----------------------------------------------------------*/

int main( )

{

/*-------------------------创建信号量操作----------------*/

int sem_phiid;

sem_phiid = semget(1008, 5, 0666|IPC_CREAT);

assert(sem_phiid>=0);

unsigned short array[5] = {0, 0, 0, 0, 0};

union semun{

int val;

unsigned short *array;

}semopts;

semopts.array=array;

int ret1 = semctl(sem_phiid, 0, SETALL, semopts);

assert(ret1 == 0);

int sem_mutexid;

sem_mutexid = semget(0x225, 1, 0666|IPC_CREAT);

assert(sem_mutexid >= 0);

semopts.val = 1;

int ret2 = semctl(sem_mutexid, 0, SETVAL, semopts); assert(ret2 == 0);

/*------------------初始化共享内存-----------------------*/ int shmid;

char* state;

if((shmid = shmget(IPC_PRIVATE, N, 0600|IPC_CREAT)) == -1){ semctl(sem_phiid, 0, IPC_RMID, 0);

semctl(sem_mutexid, 0, IPC_RMID, 0);

perror("shmget faild!");

exit(1);

}

if((state = shmat(shmid, 0, 0)) == (char *)-1){

semctl(sem_phiid, 0, IPC_RMID, 0);

semctl(sem_mutexid, 0, IPC_RMID, 0);

perror("shmat faild!");

exit(1);

}

/*-----------------------------------------------*/

int i, phinum;

pid_t pid;

for(i=0; i<5; i++){

while((pid = fork()) == -1);

if(!pid){

phinum = i;

signal(SIGINT, SIG_DFL);

break;

}

}

if(pid>0){

while(wait((int *)0) == -1);

semctl(sem_phiid, 0, IPC_RMID, 0);

semctl(sem_mutexid, 0, IPC_RMID, 0);

shmdt(state);

printf("Hi, GAME OVER!");

}

else

philosopher(phinum, state, sem_phiid, sem_mutexid, P, V); }

六、程序运行结果

截图一

截图二

截图三七、设计总结

这次课程设计通过利用UNIX系统的信号量机制和共享内存机制,编写一个解决哲学家进餐问题的程序,感觉有很多收获,主要体现在以下几点:

(1)很好的复习了UNIX系统编程课上所学的知识,尤其是对unix进程通信机制中信号量机制和共享内存机制有了实践和更深的认识。

(2)练习了C编程,尤其是对函数指针有了更深的认识。

(3)复习了操作系统进程通信的原理。

相关主题
文本预览
相关文档 最新文档