操作系统实验五之进程互斥实验

题目要求:

理发店问题:假设理发店的理发室中有 3 个理发椅子和 3 个理发师,有一个可容
纳 4 个顾客坐等理发的沙发。此外还有一间等候室,可容纳 13 位顾客等候进入理发
室。顾客如果发现理发店中顾客已满(超过 20 人)
,就不进入理发店。
在理发店内,理发师一旦有空就为坐在沙发上等待时间最长的顾客理发,同时空
出的沙发让在等候室中等待时间最长的的顾客就坐。顾客理完发后,可向任何一位
理发师付款。但理发店只有一本现金登记册,在任一时刻只能记录一个顾客的付款。
理发师在没有顾客的时候就坐在理发椅子上睡眠。理发师的时间就用在理发、收款、
睡眠上。
请利用 linux 系统提供的 IPC 进程通信机制实验并实现理发店问题的一个解法。
参考解法分析:
该解法利用消息队列的每条消息代表每个顾客,将进入等候室的顾客组织到一个
队列,将坐入沙发的顾客组织到另一个队列。理发师从沙发队列请出顾客,空出的
沙发位置再从等候室请入顾客进入沙发队列。三个理发师进程使用相同的程序段上
下文,所有顾客使用同一个程序段上下文。这样可避免产生太多进程,以便节省系
统资源。
参考解法伪代码:
理发师程序(Barber)
{
建立一个互斥帐本信号量: s_account,初值=1;
建立一个同步顾客信号量: s_customer,初值=0;
建立沙发消息队列: q_sofa;
建立等候室消息队列:q_wait;
建立 3 个理发师进程: b1_pid, b2_pid, b3_pid;
每个理发师进程作:
while(1)
{
以阻塞方式从沙发队列接收一条消息,
如果有消息,则消息出沙发队列(模拟一顾客理发);
唤醒顾客进程(让下一顾客坐入沙发)
。
用进程休眠一个随机时间模拟理发过程。
理完发,使用帐本信号量记账。
互斥的获取账本
记账
唤醒用账本理发师者
否则没有消息(沙发上无顾客)
则理发师进程在沙发队列上睡眠;
当沙发队列有消息时被唤醒(有顾客坐入沙发)。
}
}
顾客程序(customer)
{
while(1)
{
取沙发队列消息数(查沙发上顾客数) ;
如果消息数小于 4(沙发没座满)
以非阻塞方式从等候室队列接收一条消息(查等候室有顾客否),
如果有消息将接收到的消息发送到沙发队列(等候室顾客坐入沙发);
否则发送一条消息到沙发队列(新来的顾客直接坐入沙发);
否则(沙发坐满)
取等候室队列消息数(查等候室顾客数) ;
如果消息数小于 13
发送一条消息到等候室队列(等候室没满,新顾客进等候室);
否则
在顾客同步信号量上睡眠(等候室满暂不接待新顾客);
用进程休眠一个随机时间模拟顾客到达的时间间隔。
}
}
程序执行结果的测试和分析:
假设先运行理发师程序 :
$ ./barber
8377 号理发师睡眠
8379 号理发师睡眠
8378 号理发师睡眠
此时理发师进程由于无顾客而阻塞。
再在另一窗体运行顾客程序:
$ ./customer
1 号新顾客坐入沙发
2 号新顾客坐入沙发
3 号新顾客坐入沙发
4 号新顾客坐入沙发
5 号新顾客坐入沙发
6 号新顾客坐入沙发
7 号新顾客坐入沙发
8 号新顾客坐入沙发
9 号新顾客坐入沙发
10 号新顾客坐入沙发
11 号新顾客坐入沙发
12 号新顾客坐入沙发
沙发坐满 13 号顾客在等候室等候
13 号顾客从等候室坐入沙发
沙发坐满 14 号顾客在等候室等候
14 号顾客从等候室坐入沙发
沙发坐满 15 号顾客在等候室等候
15 号顾客从等候室坐入沙发
沙发坐满 16 号顾客在等候室等候
16 号顾客从等候室坐入沙发
17 号新顾客坐入沙发
沙发坐满 18 号顾客在等候室等候
18 号顾客从等候室坐入沙发
沙发坐满 19 号顾客在等候室等候
19 号顾客从等候室坐入沙发
沙发坐满 20 号顾客在等候室等候
20 号顾客从等候室坐入沙发
沙发坐满 21 号顾客在等候室等候
21 号顾客从等候室坐入沙发
...
在理发师窗体理发师进程被唤醒:
8603 号理发师为 1 号顾客理发
8603 号理发师收取 1 号顾客交费
8604 号理发师为 2 号顾客理发
8605 号理发师为 3 号顾客理发
8603 号理发师睡眠
8604 号理发师收取 2 号顾客交费
8603 号理发师为 4 号顾客理发
8604 号理发师睡眠
8604 号理发师为 5 号顾客理发
8605 号理发师收取 3 号顾客交费
8605 号理发师睡眠
8603 号理发师收取 4 号顾客交费
8605 号理发师为 6 号顾客理发
8603 号理发师睡眠
8603 号理发师为 7 号顾客理发
8604 号理发师收取 5 号顾客交费
8604 号理发师睡眠
8605 号理发师收取 6 号顾客交费
8604 号理发师为 8 号顾客理发
...
反之,如果先运行顾客程序:
$ ./customer
1 号新顾客坐入沙发
2 号新顾客坐入沙发
3 号新顾客坐入沙发
4 号新顾客坐入沙发
沙发坐满 5 号顾客在等候室等候
沙发坐满 6 号顾客在等候室等候
沙发坐满 7 号顾客在等候室等候
沙发坐满 8 号顾客在等候室等候
沙发坐满 9 号顾客在等候室等候
沙发坐满 10 号顾客在等候室等候
沙发坐满 11 号顾客在等候室等候
沙发坐满 12 号顾客在等候室等候
沙发坐满 13 号顾客在等候室等候
沙发坐满 14 号顾客在等候室等候
沙发坐满 15 号顾客在等候室等候
沙发坐满 16 号顾客在等候室等候
沙发坐满 17 号顾客在等候室等候
等候室满 18 号顾客没有进入理发店
当 18 号顾客到达时理发店 20 个位置已满,顾客进程阻塞(假设理发师进程没运行表
示三个理发师正坐在 3 个理发椅上睡觉) 。
再运行理发师程序:
$ ./barber
8557 号理发师睡眠
8557 号理发师为 1 号顾客理发
8558 号理发师睡眠
8558 号理发师为 2 号顾客理发
8559 号理发师睡眠
8559 号理发师为 3 号顾客理发
8557 号理发师收取 1 号顾客交费
8557 号理发师睡眠
8557 号理发师为 4 号顾客理发
8558 号理发师收取 2 号顾客交费
8558 号理发师睡眠
8559 号理发师收取 3 号顾客交费
...
在顾客窗体顾客进程被唤醒重新开始运行:
5 号顾客从等候室坐入沙发
6 号顾客从等候室坐入沙发
7 号顾客从等候室坐入沙发
8 号顾客从等候室坐入沙发
9 号顾客从等候室坐入沙发
沙发坐满 18 号顾客在等候室等候
10 号顾客从等候室坐入沙发
11 号顾客从等候室坐入沙发
沙发坐满 19 号顾客在等候室等候
12 号顾客从等候室坐入沙发
13 号顾客从等候室坐入沙发
14 号顾客从等候室坐入沙发
沙发坐满 20 号顾客在等候室等候
...
总结和分析示例实验和独立实验中观察到的调试和运行信息,
说明您对与解决非
对称性互斥操作的算法有哪些新的理解和认识? 为什么会出现进程饥饿现象?
本实验的饥饿现象是怎样表现的?怎样解决并发进程间发生的饥饿现象?您对于
并发进程间使用消息传递解决进程通信问题有哪些新的理解和认识?根据实验程
序、调试过程和结果分析写出实验报告。

ipc.h

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <sys/msg.h>
#include <unistd.h>
#include <time.h>
#define BUFSZ 256
#define MAXVAL 100
#define STRSIZ 8
#define WRITERQUEST 1 //写请求标识
#define READERQUEST 2 //读请求标识
#define FINISHED 3 //读写完成标识
#define SOFA 4//沙发
#define WAIT 5//等候室
/*信号灯控制用的共同体*/
typedef union semuns
{
    int val;
} Sem_uns;
/* 消息结构体*/
typedef struct msgbuf
{
    long mtype;
    int mid;
} Msg_buf;
key_t buff_key;
int buff_num;
char *buff_ptr;
int shm_flg;
int quest_flg;
key_t quest_key;
int quest_id;
int respond_flg;
key_t respond_key;
int respond_id;
int get_ipc_id (char *proc_file, key_t key);
char *set_shm(key_t shm_key, int shm_num, int shm_flag);
int set_msq(key_t msq_key, int msq_flag);
int set_sem(key_t sem_key, int sem_val, int sem_flag);
int down (int sem_id);
int up (int sem_id);
int sem_flg;
key_t s_account_key;
int s_account_val;
int s_account_sem;
key_t s_customer_key;
int s_customer_val;
int s_customer_sem;
int q_flg;
key_t q_sofa_key;
int q_sofa_id;
key_t q_wait_key;
int q_wait_id;

ipc.c

#include "ipc.h"
int get_ipc_id (char *proc_file, key_t key)
{
    FILE *pf;
    int i, j;
    char line[BUFSZ], colum[BUFSZ];
    if ((pf = fopen(proc_file, "r")) == NULL)
    {
        perror("Proc file not open");
        exit(EXIT_FAILURE);
    }
    fgets(line, BUFSZ, pf);
    while (!feof(pf))
    {
        i = j = 0;
        fgets(line, BUFSZ, pf);
        while (line[i] == ' ')
            i++;
        while (line[i] != ' ')
            colum[j++] = line[i++];
        colum[j] = '\0';
        if (atoi(colum) != key)
            continue;
        j = 0;
        while (line[i] == ' ')
            i++;
        while (line[i] != ' ')
            colum[j++] = line[i++];
        colum[j] = '\0';
        i = atoi(colum);
        fclose(pf);
        return i;
    }
    fclose(pf);
    return -1;
}

int down (int sem_id)
{
    struct sembuf buf;
    buf.sem_op = -1;
    buf.sem_num = 0;
    buf.sem_flg = SEM_UNDO;
    if ((semop(sem_id, &buf, 1)) < 0)
    {
        perror("down error ");
        exit(EXIT_FAILURE);
    }
    return EXIT_SUCCESS;
}
int up (int sem_id)
{
    struct sembuf buf;
    buf.sem_op = 1;
    buf.sem_num = 0;
    buf.sem_flg = SEM_UNDO;
    if ((semop(sem_id, &buf, 1)) < 0)
    {
        perror("up error ");
        exit(EXIT_FAILURE);
    }
    return EXIT_SUCCESS;
}

int set_sem(key_t sem_key, int sem_val, int sem_flg)
{
    int sem_id;
    Sem_uns sem_arg;
//测试由 sem_key 标识的信号灯数组是否已经建立
    if ((sem_id = get_ipc_id("/proc/sysvipc/sem", sem_key)) < 0)
    {
//semget 新建一个信号灯,其标号返回到 sem_id
        if ((sem_id = semget(sem_key, 1, sem_flg)) < 0)
        {
            perror("semaphore create error");
            exit(EXIT_FAILURE);
        }
//设置信号灯的初值
        sem_arg.val = sem_val;
        if (semctl(sem_id, 0, SETVAL, sem_arg) < 0)
        {
            perror("semaphore set error");
            exit(EXIT_FAILURE);
        }
    }
    return sem_id;
}

char * set_shm(key_t shm_key, int shm_num, int shm_flg)
{
    int i, shm_id;
    char * shm_buf;
//测试由 shm_key 标识的共享内存区是否已经建立
    if ((shm_id = get_ipc_id("/proc/sysvipc/shm", shm_key)) < 0)
    {
//shmget 新建 一个长度为 shm_num 字节的共享内存,其标号返回到 shm_id
        if ((shm_id =shmget(shm_key, shm_num, shm_flg)) < 0)
        {
            perror("shareMemory set error");
            exit(EXIT_FAILURE);
        }
//shmat 将由 shm_id 标识的共享内存附加给指针 shm_buf
        if ((shm_buf = (char *) shmat(shm_id, 0, 0)) < (char *) 0)
        {
            perror("get shareMemory error");
            exit(EXIT_FAILURE);
        }
        for (i = 0; i < shm_num; i++)
            shm_buf[i] = 0; //初始为 0
    }
//shm_key 标识的共享内存区已经建立,将由 shm_id 标识的共享内存附加给指针shm_buf
    if ((shm_buf = (char *) shmat(shm_id, 0, 0)) < (char *) 0)
    {
        perror("get shareMemory error");

        exit(EXIT_FAILURE);
    }
    return shm_buf;
}
/*
* set_msq 函数建立一个消息队列
* 如果建立成功,返回 一个消息队列的标识符 msq_id
* 输入参数:
* msq_key 消息队列的键值
* msq_flag 消息队列的存取权限
*/
int set_msq(key_t msq_key, int msq_flg)
{
    int msq_id;
//测试由 msq_key 标识的消息队列是否已经建立
    if ((msq_id = get_ipc_id("/proc/sysvipc/msg", msq_key)) < 0)
    {
//msgget 新建一个消息队列,其标号返回到 msq_id
        if ((msq_id = msgget(msq_key, msq_flg)) < 0)
        {
            perror("messageQueue set error");
            exit(EXIT_FAILURE);
        }
    }
    return msq_id;
}

customer.c

#include "ipc.h";
int main(int argc, char *argv[])
{
         srand ((unsigned) time(NULL));
          Msg_buf msg_arg;
          struct msqid_ds msg_sofa_info;
          struct msqid_ds msg_wait_info;
//建立沙发消息队列
          q_flg = IPC_CREAT | 0644;
          q_sofa_key = 300;
          q_sofa_id = set_msq(q_sofa_key, q_flg);
//建立等候室消息队列
          q_wait_key = 400;
          q_wait_id = set_msq(q_wait_key, q_flg);
          sem_flg = IPC_CREAT | 0644;
//建立一个互斥帐本信号量
          s_account_key = 100;
          s_account_val = 1;
          s_account_sem = set_sem(s_account_key, s_account_val, sem_flg);
//建立一个同步顾客信号量
          s_customer_key = 200;
          s_customer_val = 0;
          s_customer_sem = set_sem(s_customer_key, s_customer_val, sem_flg);
          int customerNumber = 1;
          while (1)
{
    msgctl(q_sofa_id, IPC_STAT, &msg_sofa_info);
//沙发没座满
        if (msg_sofa_info.msg_qnum < 4)
        {
            quest_flg = IPC_NOWAIT; //以非阻塞方式接收消息
            if (msgrcv(q_wait_id, &msg_arg,sizeof(msg_arg), WAIT, quest_flg)>= 0)
            {
                msg_arg.mtype = SOFA;
                printf("%d号新顾客坐入沙发\n", msg_arg.mid);
                msgsnd(q_sofa_id, &msg_arg, sizeof(msg_arg), IPC_NOWAIT);
            }
            else
            {
                msg_arg.mtype = SOFA;
                msg_arg.mid = customerNumber;
                customerNumber++;
                printf("%d号新顾客坐入沙发\n", msg_arg.mid);
                msgsnd(q_sofa_id, &msg_arg, sizeof(msg_arg),IPC_NOWAIT);
            }
        }
        else
        {
            msgctl(q_wait_id, IPC_STAT, &msg_wait_info);
            if (msg_wait_info.msg_qnum < 13)
            {
                msg_arg.mtype = WAIT;
                msg_arg.mid = customerNumber;
                printf("沙发座满,%d号新顾客进入等候室\n",customerNumber);
                customerNumber++;
                msgsnd(q_wait_id, &msg_arg, sizeof(msg_arg), IPC_NOWAIT);
            }
            else
            {
                printf("等候室满,%d号新顾客没有进入理发店\n", customerNumber);
                down(s_customer_sem);
            }
        }
//用进程休眠一个随机时间模拟顾客到达的时间间隔。
        sleep(rand()%2+1);
    }
    return EXIT_SUCCESS;
}

barber.c

#include "ipc.h"
int main (int argc, char *argv[])
{
         srand ((unsigned)time(NULL));
          struct msqid_ds msg_sofa_info;
          Msg_buf msg_arg;
          sem_flg = IPC_CREAT | 0644;
//建立一个互斥帐本信号量
          s_account_key=100;
          s_account_val=1;
          s_account_sem=set_sem(s_account_key,s_account_val,sem_flg);
//建立一个同步顾客信号量
          s_customer_key=200;
          s_customer_val=0;
          s_customer_sem=set_sem(s_customer_key,s_customer_val,sem_flg);
//建立沙发消息队列
          q_flg=IPC_CREAT | 0644;
          q_sofa_key=300;
          q_sofa_id=set_msq(q_sofa_key,q_flg);
//建立等候室消息队列
          q_wait_key=400;
          q_wait_id=set_msq(q_wait_key,q_flg);
//建立 3 个理发师进程;
          int pid[3];
          int i;
          for(i=0; i<3; i++)
    {
        pid[i]=fork();
            if(pid[i]==0)
                {
                    while(1)
                        {
                            msgctl(q_sofa_id, IPC_STAT, &msg_sofa_info);
                            if(msg_sofa_info.msg_qnum==0)
                                    printf("%d 号理发师睡眠\n",getpid());
//以阻塞方式从沙发队列接收一条消息
                            msgrcv(q_sofa_id,&msg_arg,sizeof(msg_arg),SOFA,0);
//唤醒顾客进程(让下一顾客坐入沙发)
                            up(s_customer_sem);
//用进程休眠一个随机时间模拟理发过程。
                            printf("%d 号理发师给 %d 号顾客理发\n",getpid(),msg_arg.mid);
                            sleep(rand()%4+4);
                            down(s_account_sem);
                            printf("%d 号理发师向 %d 号顾客收费\n",getpid(),msg_arg.mid);
                            up(s_account_sem);
                        }
                }
        }
    return EXIT_SUCCESS;
}

makefile

header =ipc.h
b_src = barber.c ipc.c
b_obj = barber.o ipc.o
c_src = customer.c ipc.c
c_obj = customer.o ipc.o
opts = -g -c
all: barber customer
barber: $(b_obj)
	gcc $(b_obj) -o barber
barber.o: $(b_src) $(header)
	gcc $(opts) $(b_src)
customer: $(c_obj)
	gcc $(c_obj) -o customer
customer.o: $(c_src) $(header)
	gcc $(opts) $(c_src) 
clean:
	rm barber customer *.o


你可能感兴趣的:(c,操作系统,OS,进程互斥)