操作系统第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号哲学家吃完了才能够拿起左右两个筷子。这样就不会构成死锁。
浙公网安备 33010602011771号