操作系统第6次实验报告:使用信号量解决进程互斥访问
- 姓名:程开
- 学号:201821121060
- 班级:计算1812
1. 选择哪一个问题
- 哲学家进餐问题
哲学家进餐问题是由荷兰学者Dijkstra提出的经典的同步问题之一。
有五个哲学家,他们的生活方式是交替地进行思考和进餐,哲学家们共用一张圆桌,分别坐在周围的五张椅子上,在圆桌上有五个碗和五支筷子,平时哲学家进行思考,饥饿时便试图取其左、右最靠近他的筷子,只有在他拿到两支筷子时才能进餐,该哲学家进餐完毕后,放下左右两只筷子又继续思考。
约束条件
(1)只有拿到两只筷子时,哲学家才能吃饭。
(2)如果筷子已被别人拿走,则必须等别人吃完之后才能拿到筷子。
(3)任一哲学家在自己未拿到两只筷子吃完饭前,不会放下手中已经拿到的筷子。
2. 给出伪代码
1 semaphore chopstick chopstick[5] = {1,1,1,1,1}; 2 3 do 4 { 5 //think 6 wait(chopstick[i]); 7 wait(chopstick[(i+1)%5]); 8 //eat 9 signal(chopstick[i]); 10 signal(chopstick[(i+1)%5]); 11 }while(true)
先拿左手边筷子,再拿右手边筷子,进餐完毕先放下左手筷子,再放下右手筷子
此时可能出现死锁,当五个哲学家同时去取他左边的筷子,每人拿到一只筷子且不释放,即五个哲学家只得无限等待下去,引起死锁。
后面给出解决方法
3. 给出完整代码
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 #include <stdint.h> 5 #include <stdbool.h> 6 #include <errno.h> 7 8 #include <unistd.h> 9 #include <sys/types.h> 10 #include <sys/stat.h> 11 #include <sys/ipc.h> 12 #include <sys/sem.h> 13 #include <sys/wait.h> 14 15 16 union semun 17 { 18 int val; 19 struct semid_ds *buf; 20 unsigned short *array; 21 struct seminfo *__buf; 22 }; 23 /* 24 struct semid_ds { 25 struct ipc_perm sem_perm; /Ownership and permissions/ 26 time_t sem_otime; /Last semop time/ 27 time_t sem_ctime; /Last change time/ 28 unsigned long sem_nsems; /No. of semaphores in set/ 29 }; 30 */ 31 32 //遇到错误,退出,"\"表示续行符 33 #define ERR_EXIT(m) \ 34 do { \ 35 perror(m); \ 36 exit(EXIT_FAILURE); \ 37 } while(0) 38 39 40 //申请一个资源 41 int wait_1fork(int no,int semid) 42 { 43 //int left = no; 44 //int right = (no + 1) % 5; 45 /*struct sembuf{ 46 short sem_num;信号量的编号 47 short sem_op;信号量一次PV操作时加减的数值 48 short sem_flg;跟踪该信号量的修改情况 49 }; */ 50 struct sembuf sb = {no,-1,0}; 51 int ret; 52 ret = semop(semid,&sb,1); 53 if(ret < 0) { 54 ERR_EXIT("资源不足"); 55 } 56 return ret; 57 } 58 59 // 释放一个资源 60 int free_1fork(int no,int semid) 61 { 62 struct sembuf sb = {no,1,0}; 63 int ret; 64 ret = semop(semid,&sb,1); 65 if(ret < 0) { 66 ERR_EXIT("资源释放错误"); 67 } 68 return ret; 69 } 70 71 72 73 //相当于P操作 74 void wait_for_2fork(int no,int semid) 75 { 76 //哲学家左边的刀叉编号和哲学家是一样的 77 int left = no; 78 //右边的刀叉 79 int right = (no + 1) % 5; 80 81 //刀叉值是两个 82 //注意第一个参数是编号 83 //操作的是两个信号量,即两种资源都满足,才进行操作 84 struct sembuf buf[2] = { 85 {left,-1,0}, 86 {right,-1,0} 87 }; 88 //信号集中有5个信号量,只是对其中的资源sembuf进行操作 89 semop(semid,buf,2); 90 } 91 92 //相当于V操作 ,释放刀叉 93 void free_2fork(int no,int semid) 94 { 95 int left = no; 96 int right = (no + 1) % 5; 97 struct sembuf buf[2] = { 98 {left,1,0}, 99 {right,1,0} 100 }; 101 semop(semid,buf,2); 102 } 103 104 105 //哲学家要做的事 106 void philosophere(int no,int semid) 107 { 108 srand(getpid()); 109 //srand(time(NULL)); 110 for(;;) 111 { 112 /*这里采取的措施是当两把刀叉都可用的时候(即两种资源都满足的时候) 113 //哲学家才能吃饭,这样不相邻的哲学家就可吃上饭 114 printf("哲学家%d正在思考\n",no); // 思考中 115 sleep(3); 116 printf("哲学家%d饿了\n",no); // 感觉到饥饿 117 wait_for_2fork(no,semid);//拿到两把叉子才能吃饭 118 printf("哲学家%d正在吃饭\n",no); // 吃饭 119 sleep(3); 120 free_2fork(no,semid);//释放两把叉子*/ 121 122 //这段代码可能会造成死锁 123 int left = no; 124 int right = (no + 1) % 5; 125 printf("哲学家%d正在思考\n",no); // 思考中 126 sleep(3); 127 printf("哲学家%d饿了\n",no); // 感觉到饥饿 128 wait_1fork(left,semid); // 拿起左叉子,现在是只要有一个资源,就申请 129 sleep(3); 130 wait_1fork(right,semid); // 拿到右叉子 131 printf("哲学家%d正在吃饭\n",no); // 吃饭 132 sleep(3); 133 free_1fork(left,semid); // 释放左叉子 134 free_1fork(right,semid); // 释放右叉子 135 136 } 137 } 138 139 140 int main(int argc,char *argv[]) 141 { 142 int semid; 143 //创建信号量 144 //信号量集中5个信号量 145 semid = semget(IPC_PRIVATE,5,IPC_CREAT | 0666); 146 if(semid < 0) { 147 ERR_EXIT("semid"); 148 } 149 union semun su; 150 su.val = 1; 151 int i; 152 for(i = 0;i < 5;++i) { 153 //注意第二个参数也是索引 154 semctl(semid,i,SETVAL,su); 155 } 156 //创建4个子进程 157 int num = 0; 158 pid_t pid; 159 for(i = 1;i < 5;++i) 160 { 161 pid = fork(); 162 if(pid < 0) 163 { 164 ERR_EXIT("fork"); 165 } 166 if(0 == pid) // 子进程 167 { 168 num = i; 169 break; 170 } 171 } 172 //哲学家开始 173 philosophere(num,semid); 174 return 0; 175 }
4. 运行结果并解释
5名哲学家同时拿起左筷子,出现死锁 没有出现死锁时的正常状态
5. 加分项
要解决死锁问题,有如下几种方案
①增加一名服务员来管理就餐
②最多只允许4名哲学家同时拿起同一边的筷子,这样保证至少有一名哲学家完成进餐并释放资源
③使用AND信号量机制,如果某个哲学家想拿筷子,那他需要同时拿起左右筷子,然后进餐,否则就一个都不拿
④比较麻烦一点,规定奇数号哲学家先拿起他左边的筷子,然后再去拿他右边的筷子,而偶数号的哲学家则相反,这样的话总能保证一个哲学家能获得两根筷子完成进餐,从而释放其所占用的资源
这里采用方法③
//相当于P操作 void wait_for_2fork(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_2fork(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 philosopher (void* arg) { while (1) { think(); hungry(); pthread_mutex_lock(mutex); pthread_mutex_lock(&chopsticks[left]); pthread_mutex_lock(&chopsticks[right]); pthread_mutex_unlock(mutex); eat(); pthread_mutex_unlock(&chopsticks[left]); pthread_mutex_unlock(&chopsticks[right]); } }