代码改变世界

操作系统第6次实验报告:使用信号量解决进程互斥访问

2020-05-29 17:08  Wangpj  阅读(269)  评论(0编辑  收藏  举报

姓名:王丕杰

班级:计算1812

学号:201821121052

1. 选择哪一个问题

  • 哲学家进餐问题

      有五个哲学家,他们的生活方式是交替地进行思考和进餐。他们共用一张圆桌,分别坐在五张椅子上。在圆桌上有五个碗和五支筷子,平时一个哲学家进行思考,饥饿时便试图取用其左、右最靠近他的筷子,只有在他拿到两支筷子时才能进餐。进餐完毕,放下筷子又继续思考。

2. 给出伪代码

放在圆桌上的筷子是临界资源,在一段时间内只允许一位哲学家的使用。为了实现对筷子的互斥使用,使用一个信号量表示一只筷子,由这五个信号量构成信号量数组,每个哲学家的行动可表现为:

semaphore chopstick[5]={1,1,1,1,1};
while(true)
{
    //当哲学家饥饿时,总是先拿左边的筷子,再拿右边的筷
    wait(chopstick[i]);
    wait(chopstick[(i+1)%5]);
    //就餐
    //当哲学家进餐完成后,总是先放下左边的筷子,再放下右边的筷子
    signal(chopstick[i]);
    signal(chopstick[(i+1)%5]);
}

但是,当五位哲学家同时饥饿而各自拿起左边的筷子时,就会使五个信号量chopstick均为0;而当他们再试图去拿右边的筷子时,都将因无筷子可拿造成无限的等待,产生死锁。

为解决死锁的问题,就要规定:仅当一个哲学家左右两边的筷子都可用时才允许他进餐。

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. 给出完整代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/wait.h>
union semun{
    int val;
    struct semid_ds *buf;
    unsigned short *array;
    struct seminfo *__buf;
};
#define ERR_EXIT(m) 
do { 
    perror(m); 
    exit(EXIT_FAILURE); 
} while(0)
//相当于P操作,申请一个资源
int wait_1chopstick(int no,int semid)
{
    struct sembuf sb = {no,-1,0};
    int ret= semop(semid,&sb,1);
    //semop()系统调用在semid标识的信号量集中的信号量上执行一个或多个up或down操作,可用于进程间的同步和互斥。
    if(ret < 0) {
        ERR_EXIT("semop");
    }
    return ret;
}
//相当于V操作,释放一个资源
int free_1chopstick(int no,int semid){
    struct sembuf sb = {no,1,0};
    int ret;
    ret = semop(semid,&sb,1);
    if(ret < 0) {
        ERR_EXIT("semop");
    }
    return ret;
}
#define DELAY (rand() % 5 + 1)  //筷子是一个临界资源
//相当于P操作
void wait_for_2chopstick(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_2chopstick(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 philosophere(int no,int semid){
    srand(getpid());
    for(;;) {
        #if 1
        //当两筷子都可用时哲学家才能吃饭,这样不相邻的哲学家就可吃上饭
        printf("%d 正在思考\n",no);
        sleep(DELAY);
        printf("%d 饿了\n",no);
        wait_for_2chopstick(no,semid);//拿到筷子才能吃饭
        printf("%d 正在进餐\n",no);
        sleep(DELAY);
        free_2chopstick(no,semid);//释放筷子
        #else
        //可能会造成死锁
        int left = no;
        int right = (no + 1) % 5;
        printf("%d 正在思考\n",no);
        sleep(DELAY);
        printf("%d 饿了\n",no);
        wait_1chopstick(left,semid);    //拿起左筷子
        sleep(DELAY);
        wait_1chopstick(right,semid);   //拿起右筷子
        printf("%d 正在进餐\n",no);
        sleep(DELAY);
        free_1chopstick(left,semid);      //释放左筷子
        free_1chopstick(right,semid);    //释放右筷子
        #endif
    }
}
int main(int argc,char *argv[]){
    int semid;
    //创建信号量
    semid = semget(IPC_PRIVATE,5,IPC_CREAT | 0666);
    if(semid < 0) {
        ERR_EXIT("semid");
    }
    union semun su;
    su.val = 1;
    int i;
    for(i = 0;i < 5;++i) {
        semctl(semid,i,SETVAL,su);
   //semctl()系统调用在一个信号量集或集合中的单个信号量上执行各种控制操作
    }
    //创建4个子进程
    int num = 0;
    pid_t pid;
    for(i = 1;i < 5;++i) {
        pid = fork();
        if(pid < 0) {
            ERR_EXIT("fork");
        }
        if(0 == pid) {
            num = i;
            break;
        }
    }
    philosophere(num,semid);  //num 代表进程号
    return 0;
}

4. 运行结果并解释

程序运行后,不再出现死锁,一直处于运行状态,这里只截取一部分进行分析:

分析:0~4代表5位哲学家。

(1)在一开始的时候,所有的哲学家都在思考中,没有人拿起筷子,随后第1位哲学家(编号为0)饥饿并且等待筷子中,由于之前大家都在思考,因此第1位哲学家左右筷子均可用,开始进餐;

(2)第4位哲学家(编号为3)左右筷子均可用,开始进餐;

(3)第3位哲学家(编号为2)饥饿,但由于第4位哲学家拿走其左手边的筷子并且没有释放导致其无法进餐,保持饥饿;

(4)第2位哲学家(编号为1)饥饿,但由于第1位哲学家拿走其右手边的筷子并且没有释放导致其无法进餐,保持饥饿;

(5)第5位哲学家(编号为4)饥饿,但由于第4位哲学家拿走其右手边的筷子,第1位哲学家拿走其左手边的筷子,并且都没有释放导致其无法进餐,保持饥饿;

(6)这时第1位哲学家进入思考中释放筷子,第2位哲学家右手边的筷子被第1位哲学家释放,此时第2位哲学家左右筷子均可用,开始进餐。