操作系统第6次实验报告:使用信号量解决进程互斥访问
- 姓名:江磊
- 学号:201821121059
- 班级:计算1812
1. 选择哪一个问题
- 哲学家进餐问题
2. 给出伪代码
首先筷子是大家公用的资源,当一位哲学家想要进餐时需要同时拿起左右两边的两根筷子,这时就需要对左右两边的筷子进行加锁,这样其他的哲学家就不能使用。当正在进餐的哲学家放下筷子的时候,就相当于给筷子解锁,相邻的两位哲学家才能进餐。
semaphore chopstick[5]={1,1,1,1,1};
do{ /*当哲学家饥饿时,总是先拿起左边的筷子,再拿起右边的筷子*/ wait(chopstick[i]); //拿起左筷子 wait(chopstick[(i+1)%5]); //拿起右筷子 eat(); //进餐 signal(chopstick[i]); signal(chopstick[i+1]%5); think(); }while(true);
但是在上述情况中,假如五位哲学家同时饥饿而各自拿起左边的筷子时,那当五名哲学家都再同时拿起右边的筷子时,就会造成死锁,这样程序就运行不下去。
因此,需要对上述算法进行改进,限制仅当哲学家左右的两只筷子都可用时,才允许他拿起筷子进餐。可以利用AND 型信号量机制实现。
记录型信号量代码:
semaphore mutex = 1; // 这个过程需要判断两根筷子是否可用,并保护起来 semaphore chopstick[5]={1,1,1,1,1}; void philosopher(int i) { while(true) { /* 这个过程中可能只能由一个人在吃饭,效率低下,有五只筷子,其实是可以达到两个人同时吃饭 */ think(); wait(mutex); wait(chopstick[(i+1)%5]); wait(chopstick[i]); signal(mutex); eat(); signal(chopstick[(i+1)%5]); signal(chopstick[i]); } }
AND信号量代码:
semaphore chopstick[5]={1,1,1,1,1}; do{ think(); //思考 Swait(chopstick[(i+1)%5],chopstick[i]); //请求筷子 eat(); //进餐 Ssignal(chopstick[(i+1)%5],chopstick[i]); //释放筷子 }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 #include <unistd.h> 8 #include <sys/types.h> 9 #include <sys/stat.h> 10 #include <sys/ipc.h> 11 #include <sys/sem.h> 12 #include <sys/wait.h> 13 14 15 union semun 16 { 17 int val; 18 struct semid_ds *buf; 19 unsigned short *array; 20 struct seminfo *__buf; 21 }; 22 23 #define ERR_EXIT(m) \ 24 do { \ 25 perror(m); \ 26 exit(EXIT_FAILURE); \ 27 } while(0) 28 29 //申请一个资源 30 int wait_1chop(int no,int semid) 31 { 32 //资源减1 33 struct sembuf sb = {no,-1,0}; 34 int ret; 35 ret = semop(semid,&sb,1); 36 if(ret < 0) { 37 ERR_EXIT("semop"); 38 } 39 return ret; 40 } 41 42 // 释放一个资源 43 int free_1chop(int no,int semid) 44 { 45 //资源加1 46 struct sembuf sb = {no,1,0}; 47 int ret; 48 ret = semop(semid,&sb,1); 49 if(ret < 0) { 50 ERR_EXIT("semop"); 51 } 52 return ret; 53 } 54 55 //筷子是一个临界资源 56 #define DELAY (rand() % 5 + 1) 57 58 //相当于P操作 59 //第一个参数是筷子编号 60 //第二个参数是信号量编号 61 void wait_for_2chop(int no,int semid) 62 { 63 //哲学家左边的筷子编号和哲学家编号是一样的 64 int left = no; 65 //右边的筷子 66 int right = (no + 1) % 5; 67 68 //筷子值是两个 69 //操作的是两个信号量,即两种资源都满足,才进行操作 70 struct sembuf buf[2] = { 71 {left,-1,0}, 72 {right,-1,0} 73 }; 74 //信号集中有5个信号量,只是对其中的资源sembuf进行操作 75 semop(semid,buf,2); 76 } 77 78 //相当于V操作 ,释放筷子 79 void free_2chop(int no,int semid) 80 { 81 int left = no; 82 int right = (no + 1) % 5; 83 struct sembuf buf[2] = { 84 {left,1,0}, 85 {right,1,0} 86 }; 87 semop(semid,buf,2); 88 } 89 90 91 //哲学家要做的事 92 void philosophere(int no,int semid) 93 { 94 srand(getpid()); 95 for(;;) 96 { 97 #if 1 98 //当两只筷子都可用的时候,哲学家才能进餐 99 printf("%d号哲学家 思考中\n",no); //思考中 100 sleep(DELAY); 101 printf("%d号哲学家 饿了\n",no); //饥饿 102 wait_for_2chop(no,semid); //拿到两只筷子才能吃饭 103 printf("%d号哲学家 正在进餐\n",no); //进餐 104 sleep(DELAY); 105 free_2chop(no,semid); //释放两只筷子 106 #else 107 //可能会造成死锁 108 int left = no; 109 int right = (no + 1) % 5; 110 printf("%d is thinking\n",no); //思考中 111 sleep(DELAY); 112 printf("%d is hungry\n",no); //饥饿 113 wait_1chop(left,semid); //拿起左筷子(只要有一个资源就申请) 114 sleep(DELAY); 115 wait_1chop(right,semid); //拿起右筷子 116 printf("%d is eating\n",no); //进餐 117 sleep(DELAY); 118 free_1chop(left,semid); //释放左筷子 119 free_1chop(right,semid); //释放右筷子 120 #endif 121 } 122 } 123 124 125 int main(int argc,char *argv[]) 126 { 127 int semid; 128 //创建信号量集,其中有5个信号量 129 semid = semget(IPC_PRIVATE,5,IPC_CREAT | 0666); 130 if(semid < 0) { 131 ERR_EXIT("semid"); 132 } 133 union semun su; 134 su.val = 1; 135 int i; 136 for(i = 0;i < 5; ++i) { 137 //第二个参数也是索引 138 semctl(semid,i,SETVAL,su); 139 } 140 //创建4个子进程 141 int num = 0; 142 pid_t pid; 143 for(i = 1;i < 5;++i) 144 { 145 pid = fork(); 146 if(pid < 0) 147 { 148 ERR_EXIT("fork"); 149 } 150 if(0 == pid) //子进程 151 { 152 num = i; 153 break; 154 } 155 } 156 //哲学家要做的事情 157 philosophere(num,semid); 158 return 0; 159 }
4. 运行结果并解释
结果分析:
以4号哲学家为例子,由于3号哲学家已经在进餐了,所以当4号哲学家饿了的时候并能够马上进行进餐,而是需要等待3号哲学家吃完了才能够拿起左右两个筷子。这样就不会构成死锁。