count<3时,count++,顾客坐在椅子上等待理发。
2.只有在理发椅空闲时,顾客才能做到理发椅上等待理发师理发,否则顾客便必须等待;只有当理发椅上有顾客时,理发师才可以开始理发,否则他也必须等待。
可通过信号量empty和full来控制。
3.理发师为顾客理发时,顾客必须等待理发的完成,并在理发完成后理发师唤醒他,使用一个信号量finish来控制;
4.顾客理完发后必须向理发师付费,并等理发师收费后顾客才能离开;而理发师则需等待顾客付费,并在收费后唤醒顾客以允许他离开,这可分别通过两个信号量payment和receipt来控制。
初值:
计数intcount=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);//释放沙发
}
elseif(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.h
opts=-g-c
c_src=cons.cipc.c
c_obj=cons.oipc.o
p_src=bar.cipc.c
p_obj=bar.oipc.o
all:
producerconsumer
consumer:
$(c_obj)
gcc$(c_obj)-oconsumer
cons.o:
$(c_src)$(hdrs)
gcc$(opts)$(c_src)
producer:
$(p_obj)
gcc$(p_obj)-oproducer
bar.o:
$(p_src)$(hdrs)
gcc$(opts)$(p_src)
clean:
rmconsbar*.o
2.执行make命令,结果出现了许多由于粗心造成的编译错误
3、修改程序后编译成功,打开两个终端,先运行producer.c,再运行consumer.c
4、若按ctrl+c停止producer进程,则出现如下图结果。
沙发坐满后顾客将进入等候室等待
5、若再次执行producer进程,将陆续唤醒在等待的顾客,结果如下图
6、若再停止producer,让等待室的人也满,则顾客会离开理发店,结果如下图
七、心得与收获
1、本次试验,使我基本掌握了怎样用消息队列控制和堵塞进程,实现对共享内存的有序访问。
2、msgrcv/msgsnd为linux系统中异步或进程间通信的一种机制,msgrcv()可以从消息队列中读取消息,msgsnd()将一个新的消息写入队列。
intmsgsnd(intmsqid,constvoid*msgp,size_tmsgsz,intmsgflg);
ssize_tmsgrcv(intmsqid,void*msgp,size_tmsgsz,longmsgtyp,intmsgflg);
msgflg:
这个参数依然是是控制函数行为的标志,取值可以是:
0,表示忽略;IPC_NOWAIT,如果消息队列为空,则返回一个ENOMSG,并将控制权交回调用函数的进程。
3、不仅加深了对进程互斥的理解,还使我加深了对理发师算法的理解,找到了它与读者写者问题的共同之处:
(1).进程间的互斥
(2).理发师类似读者进程,顾客类似写者进程。
4、编写程序时要细心,对于编译过程中出现的错误,要有耐心去解决。
八、源代码
Ipc.h:
#include
#include
#include
#include
#include
#include
#include
#defineBUFSZ256
#defineMAXVAL100
#defineSTRSIZ8
#defineWRITERQUEST1
#defineREADERQUEST2
#defineFINISHED3
//写请求标识
//读请求标识
//读写完成标识
typedefunionsemuns{
intval;
}Sem_uns;
typedefstructmsgbuf{
longmtype;
intmid;
}Msg_buf;
//信号量
key_tcostomer_key;
intcostomer_sem;
key_taccount_key;
intaccount_sem;
intsem_val;
intsem_flg;
//消息队列
intwait_quest_flg;
key_twait_quest_key;
intwait_quest_id;
intwait_respond_flg;
key_twait_respond_key;
intwait_respond_id;
intsofa_quest_flg;
key_tsofa_quest_key;
intsofa_quest_id;
intsofa_respond_flg;
key_tsofa_respond_key;
intsofa_respond_id;
intget_ipc_id(char*proc_file,key_tkey);
char*set_shm(key_tshm_key,intshm_num,intshm_flag);
intset_msq(key_tmsq_key,intmsq_flag);
intset_sem(key_tsem_key,intsem_val,intsem_flag);
intdown(intsem_id);
intup(intsem_id);
Ipc.c:
#include"ipc.h"
intget_ipc_id(char*proc_file,key_tkey)
{
FILE*pf;
inti,j;
charline[BUFSZ],colum[BUFSZ];
if((pf=fopen(proc_file,"r"))==NULL){
perror("Procfilenotopen");
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);
returni;
}
fclose(pf);
return-1;
}
intdown(intsem_id)
{
structsembufbuf;
buf.sem_op=-1;
buf.sem_num=0;
buf.sem_flg=SEM_UNDO;
if((semop(sem_id,&buf,1))<0){
perror("downerror");
exit(EXIT_FAILURE);
}
returnEXIT_SUCCESS;
}
intup(intsem_id)
{
structsembufbuf;
buf.sem_op=1;
buf.sem_num=0;
buf.sem_flg=SEM_UNDO;
if((semop(sem_id,&buf,1))<0){
perror("uperror");
exit(EXIT_FAILURE);
}
returnEXIT_SUCCESS;
}
intset_sem(key_tsem_key,intsem_val,intsem_flg)
{
intsem_id;
Sem_unssem_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("semaphorecreateerror");
exit(EXIT_FAILURE);
}
//设置信号灯的初值
sem_arg.val=sem_val;
if(semctl(sem_id,0,SETVAL,sem_arg)<0)
{
perror("semaphoreseterror");
exit(EXIT_FAILURE);
}
}
returnsem_id;
}
char*set_shm(key_tshm_key,intshm_num,intshm_flg)
{
inti,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("shareMemoryseterror");
exit(EXIT_FAILURE);
}
//shmat将由shm_id标识的共享内存附加给指针shm_buf
if((shm_buf=(char*)shmat(shm_id,0,0))<(char*)0)
{
perror("getshareMemoryerror");
exit(EXIT_FAILURE);
}
for(i=0;i}
//shm_key标识的共享内存区已经建立,将由shm_id标识的共享内存附加给指针shm_buf
if((shm_buf=(char*)shmat(shm_id,0,0))<(char*)0)
{
perror("getshareMemoryerror");
exit(EXIT_FAILURE);
}
returnshm_buf;
}
intset_msq(key_tmsq_key,intmsq_flg)
{
intmsq_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("messageQueueseterror");
exit(EXIT_FAILURE);
}
}
returnmsq_id;
}
Bar.c:
#include"ipc.h"
intmain(intargc,char*argv[])
{
//inti;
intrate;
Msg_bufmsg_arg;
//可在在命令行第一参数指定一个进程睡眠秒数,以调解进程执行速度
if(argv[1]!
=NULL)rate=atoi(argv[1]);
elserate=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;
//顾客同步信号灯初值设为0
sem_val=0;
//获取顾客同步信号灯,引用标识存costomer_sem
costomer_sem=set_sem(costomer_key,sem_val,sem_flg);
//账簿互斥信号灯初值设为1
sem_val=1;
//获取消费者同步信号灯,引用标识存cons_sem
account_sem=set_sem(account_key,sem_val,sem_flg);
intpid1,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则返回队列的最早的一个消息。
msgtyp大于0,则返回其类型为mtype的第一个消息。
msgtyp小于0,则返回其类型小于或等于mtype参数的绝对值的最小的一个消息。
*/
if(msgrcv(sofa_quest_id,&msg_arg,sizeof(msg_arg),0,wait_quest_flg)>=0){//读沙发请求队列
msgsnd(sofa_respond_id,&msg_arg,sizeof(msg_arg),0);//往沙发回应队列里写
printf("%d号理发师为%d号顾客理发……\n",getpid(),msg_arg.mid);
sleep(rate);
down(account_sem);
printf("%d号理发师收取%d号顾客交费\n",getpid(),msg_arg.mid);
up(account_sem);
}
}
}else{
pid2=fork();
if(pid2==0){
while
(1){
//wait_quest_flg=IPC_NOWAIT;
printf("%d号理发师睡眠\n",getpid());
wait_quest_flg=0;
if(msgrcv(sofa_quest_id,&msg_arg,sizeof(msg_arg),0,wait_quest_flg)>=0){
msgsnd(sofa_respond_id,&msg_arg,sizeof(msg_arg),0);
printf("%d号理发师为%d号顾客理发……\n",getpid(),msg_arg.mid);
sleep(rate);
down(account_sem);
printf("%d号理发师收取%d号顾客交费\n",getpid(),msg_arg.mid);
up(account_sem);
}else{
printf("%d号理发师睡眠\n",getpid());
}
}
}else{
while
(1){
//wait_quest_flg=IPC_NOWAIT;
printf("%d号理发师睡眠\n",getpid());
wait_quest_flg=0;
if(msgrcv(sofa_quest_id,&msg_arg,sizeof(msg_arg),0,wait_quest_flg)>=0){
msgsnd(sofa_respond_id,&msg_arg,sizeof(msg_arg),0);
printf("%d号理发师为%d号顾客理发……\n",getpid(),msg_arg.mid);
sleep(rate);
down(account_sem);
printf("%d号理发师收取%d号顾客交费\n",getpid(),msg_arg.mid);
up(account_sem);
}else{
printf("%d号理发师睡眠\n",getpid());
}
}
}
}
return0;
}
Cons.c:
#include"ipc.h"
intmain(intargc,char*argv[])
{
intrate;
Msg_bufmsg_arg;
//可在在命令行第一参数指定一个进程睡眠秒数,以调解进程执行速度
if(argv[1]!
=NULL)rate=atoi(argv[1]);
elserate=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