进程同步实验张咪软件四班一、实验目的总结和分析示例实验和独立实验中观察到的调试和运行信息,说明您对与解决非对称性互斥操作的算法有哪些新的理解和认识?为什么会出现进程饥饿现象?本实验的饥饿现象是怎样表现的?怎样解决并发进程间发生的饥饿现象?您对于并发进程间使用消息传递解决进程通信问题有哪些新的理解和认识?根据实验程序、调试过程和结果分析写出实验报告。
二、实验要求理发店问题:假设理发店的理发室中有3个理发椅子和3个理发师,有一个可容纳4个顾客坐等理发的沙发。
此外还有一间等候室,可容纳13位顾客等候进入理发室。
顾客如果发现理发店中顾客已满(超过20人),就不进入理发店。
在理发店内,理发师一旦有空就为坐在沙发上等待时间最长的顾客理发,同时空出的沙发让在等候室中等待时间最长的的顾客就坐。
顾客理完发后,可向任何一位理发师付款。
但理发店只有一本现金登记册,在任一时刻只能记录一个顾客的付款。
理发师在没有顾客的时候就坐在理发椅子上睡眠。
理发师的时间就用在理发、收款、睡眠上。
请利用linux系统提供的IPC进程通信机制实验并实现理发店问题的一个解法。
三、实验环境实验环境均为Linux操作系统,开发工具为gcc和g++。
四、实验思路约束:1.设置一个count变量来对顾客进行计数,该变量将被多个顾客进程互斥地访问并修改,通过一个互斥信号量mutext来实现。
count>20时,就不进入理发店。
7<count<20时,count++,顾客申请等候室,进入等候室等待,用一个room信号量控制。
然后等待申请沙发,用一个sofa信号量控制。
然后申请椅子。
3<count<7时,count++,顾客坐在沙发上等待,等待申请椅子。
count<3时,count++,顾客坐在椅子上等待理发。
2.只有在理发椅空闲时,顾客才能做到理发椅上等待理发师理发,否则顾客便必须等待;只有当理发椅上有顾客时,理发师才可以开始理发,否则他也必须等待。
可通过信号量empty 和full来控制。
3.理发师为顾客理发时,顾客必须等待理发的完成,并在理发完成后理发师唤醒他,使用一个信号量finish来控制;4.顾客理完发后必须向理发师付费,并等理发师收费后顾客才能离开;而理发师则需等待顾客付费,并在收费后唤醒顾客以允许他离开,这可分别通过两个信号量payment和receipt 来控制。
初值:计数int count=0信号量empty=3;full=0;room=13;sofa=4;finish=0;pay=0;receipt=0;mutex=1;理发师进程while(1){wait(full); //等待理发椅上有顾客剪头发signal(finish); //通知顾客理发完成wait(pay); //等待顾客付费wait(mutex);//在任一时刻只能记录一个顾客的付款收费signal(mutex);signal(receipt); //通知顾客收费完毕}顾客进程wait(mutex); //count既用于判断,也要修改,所以为临界资源,用mutex管理互斥if(count>20) {//顾客大于20人signal(mutex);离开理发店}else{//顾客小于20人count=count+1;//进入理发店if(count>7) { //count>7,说明理发椅和沙发上都有人,需要到等待室等待signal(mutex);wait(room);//申请进入等待室在等待室等wait(sofa); //申请沙发signal(room);//释放等待室坐在沙发上等wait(empty);//等待理发椅为空申请到理发椅signal(sofa); //释放沙发}else if(count>3){//说明理发椅上都有人,需要坐到沙发上等待signal(mutex);wait(sofa);//申请沙发坐在沙发上等wait(empty);//等待理发椅为空申请到理发椅signal(sofa);//释放沙发}else{//count<3,可以坐到理发椅上等待signal(mutex);wait(empty); //申请理发椅}坐在理发椅上等待理发signal(full); //通知理发师开始理发理发wait(finish); //等待理发完毕付款signal(payment); //通知理发师已付款wait(receipt); //等待理发师收款理发师收费完成,顾客离开理发椅signal(empty); //释放理发椅wait(mutex); //对count 临界资源操作,用mutex完成互斥count=count-1; //离开理发店signal(mutex);}七、调试及实验结果1、创建makefile文件hdrs = ipc.hopts = -g -cc_src = cons.c ipc.cc_obj = cons.o ipc.op_src = bar.c ipc.cp_obj = bar.o ipc.oall: producer consumerconsumer: $(c_obj)gcc $(c_obj) -o consumercons.o: $(c_src) $(hdrs)gcc $(opts) $(c_src)producer: $(p_obj)gcc $(p_obj) -o producerbar.o: $(p_src) $(hdrs)gcc $(opts) $(p_src)clean:rm cons bar *.o2.执行make命令,结果出现了许多由于粗心造成的编译错误3、修改程序后编译成功,打开两个终端,先运行producer.c,再运行consumer.c4、若按ctrl+c停止producer进程,则出现如下图结果。
沙发坐满后顾客将进入等候室等待5、若再次执行producer进程,将陆续唤醒在等待的顾客,结果如下图6、若再停止producer,让等待室的人也满,则顾客会离开理发店,结果如下图七、心得与收获1、本次试验,使我基本掌握了怎样用消息队列控制和堵塞进程,实现对共享内存的有序访问。
2、msgrcv/msgsnd为linux系统中异步或进程间通信的一种机制,msgrcv()可以从消息队列中读取消息,msgsnd()将一个新的消息写入队列。
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg); msgflg:这个参数依然是是控制函数行为的标志,取值可以是:0,表示忽略;IPC_NOWAIT,如果消息队列为空,则返回一个ENOMSG,并将控制权交回调用函数的进程。
3、不仅加深了对进程互斥的理解,还使我加深了对理发师算法的理解,找到了它与读者写者问题的共同之处:(1).进程间的互斥(2).理发师类似读者进程,顾客类似写者进程。
4、编写程序时要细心,对于编译过程中出现的错误,要有耐心去解决。
八、源代码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>#define BUFSZ 256#define MAXVAL 100#define STRSIZ 8#define WRITERQUEST 1#define READERQUEST 2#define FINISHED 3//写请求标识//读请求标识//读写完成标识typedef union semuns { int val;} Sem_uns;typedef struct msgbuf { long mtype;int mid;} Msg_buf;//信号量key_t costomer_key; int costomer_sem;key_t account_key;int account_sem;int sem_val;int sem_flg;//消息队列int wait_quest_flg;key_t wait_quest_key; int wait_quest_id;int wait_respond_flg;key_t wait_respond_key;int wait_respond_id;int sofa_quest_flg;key_t sofa_quest_key;int sofa_quest_id;int sofa_respond_flg;key_t sofa_respond_key;int sofa_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);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_idif((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_idif((shm_id = shmget(shm_key,shm_num,shm_flg)) <0)perror("shareMemory set error");exit(EXIT_FAILURE);}//shmat 将由shm_id 标识的共享内存附加给指针shm_bufif((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_bufif((shm_buf = (char *)shmat(shm_id,0,0)) < (char *)0){perror("get shareMemory error");exit(EXIT_FAILURE);}return shm_buf;}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_idif((msq_id = msgget(msq_key,msq_flg)) < 0){perror("messageQueue set error");exit(EXIT_FAILURE);}}return msq_id;}Bar.c:#include "ipc.h"int main(int argc,char *argv[]){// int i;int rate;Msg_buf msg_arg;//可在在命令行第一参数指定一个进程睡眠秒数,以调解进程执行速度if(argv[1] != NULL) rate = atoi(argv[1]);else rate = 3;//一个请求消息队列wait_quest_flg = IPC_CREAT| 0644;wait_quest_key = 101;wait_quest_id = set_msq(wait_quest_key,wait_quest_flg);//一个响应消息队列wait_respond_flg = IPC_CREAT| 0644;wait_respond_key = 102;wait_respond_id = set_msq(wait_respond_key,wait_respond_flg);//一个请求消息队列sofa_quest_flg = IPC_CREAT| 0644;sofa_quest_key = 201;sofa_quest_id = set_msq(sofa_quest_key,sofa_quest_flg);//一个响应消息队列sofa_respond_flg = IPC_CREAT| 0644;sofa_respond_key = 202;sofa_respond_id = set_msq(sofa_respond_key,sofa_respond_flg);//信号量使用的变量costomer_key = 301;//顾客同步信号灯键值account_key = 302;//账簿互斥信号灯键值sem_flg = IPC_CREAT | 0644;//顾客同步信号灯初值设为0sem_val = 0;//获取顾客同步信号灯,引用标识存costomer_semcostomer_sem = set_sem(costomer_key,sem_val,sem_flg);//账簿互斥信号灯初值设为1sem_val = 1;//获取消费者同步信号灯,引用标识存cons_semaccount_sem = set_sem(account_key,sem_val,sem_flg);int pid1, pid2;pid1=fork();if(pid1==0) {while(1) {// wait_quest_flg=IPC_NOWAIT;printf("%d号理发师睡眠\n", getpid());wait_quest_flg=0;/*msgrcv()可以从消息队列中读取消息,msgsnd()将一个新的消息写入队列msgtyp等于0 则返回队列的最早的一个消息。