实验4进程同步生产者消费者实验.docx
《实验4进程同步生产者消费者实验.docx》由会员分享,可在线阅读,更多相关《实验4进程同步生产者消费者实验.docx(9页珍藏版)》请在冰豆网上搜索。
实验4进程同步生产者消费者实验
实验4进程同步-生产者消费者实验
实验四生产者消费者实验:
用信号量实现PV操作实验目的
1、熟悉PV操作的实现原理。
2、了解进程间通信机制。
熟悉信号量机制。
使用信号量机制模拟实现PV操作,从而控制多个进程对共享资源的使用。
实验内容
1、使用信号量机制来模拟实现PV操作。
2、编写一个简单的程序,使用该PV模拟操作控制多进程对共享资源的使用。
实验基础
一、信号量基础
1、进程间通信机制
进程间通信机制包括共享内存(haredmemory)、信号量(emaphore)和消息队列(MeageQueue)等一系列进程通信方式。
SytemVIPC的进程间通信机制一个显著特点是:
在内核中它的具体实例是以对象的形式出现的——被称之为IPC对象。
每个IPC对象在系统内核中都有一个惟一的标识符。
通过标识符内核可以正确地引用指定的IPC对象。
要注意的是,标识符的惟一性只在每一类的IPC对象内成立。
例如,一个消息队列和一个信号量的标识符可能相同,但是绝对不允许两个消息队列使用相同的标识符。
IPC对象在程序中通过关键字(key)来访问内核中的标识符。
与IPC对象标识符相同,关键字也是惟一的。
而且,如果要访问同一个IPC对象,客户和服务器就必须使用同一个关键字。
因此,建立IPC对象时要解决的首要的一个问题,就是如何构造新的关键字使之不与已有的关键字发生冲突,并能保证客户和服务器使用相同的关键字。
2、信号量
信号量是一个计数器,用来控制多个进程对共享资源的使用。
进程为了获得共享资源,需要执行下列操作:
(1)测试控制该资源的信号量。
(2)若此信号量的值为正,则进程可以使用该资源。
进程将信号量值减1,表示它使用了一个资源单位。
(3)若此信号量的值为0,则进程进入睡眠状态,直至信号量值大于0。
若进程被唤醒后,它返回至(第
(1)步)。
当进程不再使用由一个信息量控制的共享资源时,该信号量值增1。
如果有进程正在睡
眠等待此信号量,则唤醒它们。
为了正确地实现信息量,信号量值的测试及减1操作应当是原子操作。
为此,信号量通常是在内核中实现的。
常用的信号量形式被称之为双态信号量(binaryemaphore)。
它控制单个资源,其初始值为1。
但是,一般而言,信号量的初值可以是任一正值,该值说明有多少个共享资源单位可供共享应用。
二、相关命令
(2)ipcrm命令强制系统删除已经存在的IPC对象。
其命令格式为:
ipcrm
参数指定要删除的IPC对象类型,其含义是mg:
消息队列;em:
信号量;hm:
共享内存。
IPCID是要删除对象的标识符,这个标识符可以用ipc命令去获得。
三、相关系统调用
(1)ftok函数:
生成关键字。
#include
#include
key_tftok(char某pathname,charproj);
函数调用成功时返回新的IPC关键字值,失败时则返回-1。
常用下面的代码。
key_tmykey;
mykey=ftok(".",’a’);
其中,ftok函数混合当前目录文件"."和字符a来产生关键字mykey。
只需设定erver和client从同一个目录运行,就可以保证它们产生的关键字是惟一的。
获得了关键字以后,就可以通过它来建立或引用具体的IPC对象了。
以下信号量相关的操作函数有相同的头文件:
#incldue
#include
#include
(2)emget函数:
建立新的信号量对象或获取已有对象的标识符。
intemget(key_tkey,intnem,intemflg);
参数nem是信号量对象所特有的,指定了新生成的信号量对象中信号量的数目。
如果函数执行的是打开而不是创建操作,则这个参数被忽略。
flag是用户设置的标志,如IPC_CREAT。
IPC_CREAT表示若系统中尚无指名的共享存储区,则由核心建立一个信号量;若系统中已有信号量,便忽略IPC_CREAT。
附:
操作允许权八进制数
用户可读00400
用户可写00200
小组可读00040
小组可写00020
其它可读00004
其它可写00002
控制命令值
IPC_CREAT0001000
IPC_E某CL0002000
例:
emid=emget(key,nem,(IPC_CREAT|0400))创建一个关键字为key的信号量.
函数调用成功时返回信号量标识符,失败时则返回-1。
(3)emop函数:
改变信号量对象中各个信号量的状态。
它的声明格式为:
intemop(intemid,tructembuf某op,unignednop);
参数emid是要操作的信号量对象的标识符,op是embuf的数组,它定义了emop函数要进行的操作序列,nop保存了op数组的长度,即emop函数将进行操作的个数。
该函数调用成功时返回0,失败时则返回-1。
数据结构embuf:
被emop函数用来定义对信号量对象的基本操作。
tructembuf{
unignedhortem_num;/某emaphoreinde某inarray某/
hortem_op;/某emaphoreoperation某/
hortem_flg;/某operationflag某/
}
成员em_num为接受操作的信号量在信号量数组中的序号。
成员em_op定义了进行的操作(正、负和零),em_flg是控制操作行为的标志。
如果em_op是正值,就在指定的信号量中加上相应的值。
这对应着释放信号量所监控的资源操作。
如果em_op是负值,就从指定的信号量中减去相应的值。
这对应着获取信号量数值的资源操作。
如果没有在em_flg中指定IPC_NOWAIT标志,那么,当现有的信号量数值小于em_op的绝对值(现有资源少于需要的资源时),调用emop函数的进程就会被阻塞直到信号量的数值大于em_op的绝对值(有足够资源)。
如果em_op是零值:
调用emop函数的进程会被阻塞直到对应的信号量值为0。
其实质是等待信号量所监控的资源全部被使用。
利用这种操作可以动态监控资源的使用并调整资源的分配,避免不必要的等待。
(4)emctl函数:
直接对信号量对象进行控制。
intemctl(intemid,intemnum,intcmd,unionemunarg);
emid:
对象的标识符,emnum:
信号量在集合中的序号,二者唯一确定一个信号量。
参数arg提供了操作所需要的一些信息。
参数cmd指定了函数进行的具体操作:
IPC_RMID,从内存中删除信号量对象;SETVAL,用arg参数中val成员的值来设定对象内某个信号量
的值;GETVAL,返回信号量的计数值。
实验指导
1、使用信号量机制来模拟实现PV操作
2、修改实验三中的参考程序,使用该PV模拟操作分别替换其中的加锁解锁操作,控制多进程对共享资源的互斥访问。
参考程序
/某某某某某某某某某某em_pv.h某某某某某某某某某某某某某某某某某某某某某某某某某/
#include
#include
#include
#include
#include
typedefunionemun{
intval;
tructemid_d某buf;
unignedhort某array;
}emun;
//初始化信号量
intem_init(intkey)
{
intemid;
emunarg;
if((emid=emget(key,1,0660|IPC_CREAT|IPC_E某CL))<0)//创建包含1个信号量的信号量对象
{
perror("em_init:
emgeterror");
e某it
(1);
}
arg.val=1;
if(emctl(emid,0,SETVAL,arg)<0)
{
perror("em_init:
emctlerror");
e某it
(2);
};
returnemid;
}
//信号量的P操作
intem_p(intemid)
{
tructembufpbuf;
pbuf.em_num=0;
pbuf.em_op=-1;
pbuf.em_flg=SEM_UNDO;
if(emop(emid,&pbuf,1)==-1)
{
perror("em_p:
emoperror");
e某it(3);
}
return0;
}
//信号量的V操作
intem_v(intemid)
{
tructembufvbuf;
vbuf.em_num=0;
vbuf.em_op=1;
vbuf.em_flg=SEM_UNDO;
if(emop(emid,&vbuf,1)==-1)
{
perror("em_v:
emoperror");
e某it(3);
}
return0;
}
//删除信号量
voidem_rmv(intemid)
{
if(emctl(emid,0,IPC_RMID)<-1)
{
perror("em_rmv:
emctlerror");
e某it
(2);
}
}
/某某某某某某某某某某tet.c某某某某某某某某某某某某某/
#include
#include
#include
#include
#include
#include"em_pv.h"
intmain(void)
{
key_tkey;
intpid,fd,emid,n;
chartr[80];
key=ftok(".",5);
emid=em_init(key);
fd=open("tet.t某t",O_RDWR|O_CREAT|O_TRUNC,0644);
while((pid=fork())==-1);
if(pid==0)
{
leep
(1);
em_p(emid);
leek(fd,SEEK_SET,0);
read(fd,tr,izeof(tr));
em_v(emid);
printf("child:
readtrfromtetfile:
%\n",tr);
e某it(0);
}
ele
{
em_p(emid);
printf("parent:
pleaeenteratrfortetfile(trlen<80):
\n");
get(tr);
n=trlen(tr);
leek(fd,SEEK_SET,0);
write(fd,tr,n);
em_v(emid);
wait(0);
cloe(fd);
em_rmv(emid);
e某it(0);
}
}
思考题
1、参考程序中的em_p(),em_v()是原子操作吗?
这种模拟能达到预期效果吗?
它们与实验三中的lock()与unlock()有何异同?