操作系统实验 生产者消费者问题详解

操作系统课程设计 生产者消费者实验报告

一、实验目的

加深对进程概念的理解,明确进程与程序的区别。
认识并发执行的本质。
理解和掌握Linux和Windows进程通信系统调用的功能,通过实验和学习,提高对进程痛惜系统调用的编程能力。
掌握使用信号量机制完成进程间同步和互斥的方法。

二、实验内容

设置一个大小为的缓冲区,且初始为空。
创建两个生产者和三个消费者。
生产者和消费者都必须用进程模拟。
对于生产者而言,随机等待一段时间后,往缓冲区中存数据,若缓冲区满,则等待消费者取走数据后再存放。
对于消费者而言,随机等待一段时间后,从缓冲区中取数据,若缓冲区空,则等待生产者存放数据后再取走。
每个生产者进程需要执行6次存数据的操作,每个消费者进程需要执行4次取数据的操作。
显示每次向缓冲区添加或取走的数据以及时间,每次操作后都要显示缓冲区中的全部数据,生产者、消费者进程号。

三、实验环境

虚拟机: Ubuntu 18.04
Linux操作系统计:Linux-5.9.10
操作系统:Windows10
Win10下的IDE:Visual Studio2019

四、程序设计与实现

4.1 设计思路

定义大小为3的共享内存区:
struct BUF {
char array[BufLength];
int head;
int tail;
int IsEmpty;};
其中array表示生产者和消费者存取数据的环形缓冲区,设置其大小BufLength为3。头指针head用于指向消费者下一次待取的数据,尾指针tail指向生产者下一次需要存放数据的缓冲,IsEmpty用来表示环形缓冲区是否为空,取0为不空,取1为空。
在实验中,设置三个信号量如下:
互斥信号量MUTEX:用于生产者进程与生产者进程、消费者进程与生产者进程、消费者进程与消费者进程间互斥使用缓冲区,初始值为1。
同步信号量EMPTY:用于指示当前空缓冲区的可用数量,用于制约生产者进程向缓冲区存数据,初始值为3。
同步信号量FULL:用于指示当前有数据的缓冲区的数量,用于制约消费者进程取数据,初始值为0。
假设P(MUTEX)操作是申请MUTEX资源,V(MUTEX)操作是释放MUTEX资源,给出生产者消费者问题的流程如下。

生产者存数据的流程:

(1) 生产数据
(2) P(EMPTY) //消费者进程申请一个空缓冲单元,EMPTY-1
(3) P(MUTEX) //若申请到空缓冲单元,则申请这个缓冲单元的使用权,保证
//这段时间内没有其他进程在使用环形缓冲区,MUTEX-1
(4) 生产者进程向缓冲区中存放数据
(5) 修改尾指针,指向下一个缓冲单元(此缓冲单元不一定为空缓冲),修改缓冲区状态IsEmpty。
(6) V(MUTEX) //生产者进程释放对环形缓冲区的使用权,MUTEX+1
(7) V(FULL) //FULL+1,表示环形缓冲区中可取数据的总量,唤醒消费者进程
//取数据

消费者取数据的流程:

(1) P(FULL) //申请一个放有数据的缓冲单元(即申请一个产品),FULL-1
(2) P(MUTEX) //若申请到产品,保证这段时间没有其他进程在使用环形缓冲
//区,MUTEX-1
(3)取得当前头指针指向的数据,清除存储这一数据的缓冲单元的内容,修改缓冲区状态,并修改尾指针指向下一缓冲单元。
(4) V(MUTEX) //消费者进程释放对环形缓冲区的使用权
(5) V(EMPTY) //Empty+1,表示环形缓冲区中空缓冲单元的数量,用于唤醒生
//产者进程向缓冲区中存数据。
由于在生产与消费的过程中,有同步信号量FULL和EMPTY控制各进程(消费者或生产者)是否能够申请缓冲单元,所以不会出现缓冲区满而生产者仍然往里放数据,缓冲区空而消费者仍然在取数据的情况。此外,由于MUTEX的存在,保证各进程能够互斥使用环形缓冲区,MUTEX、FULL与EMPTY的合理搭配保证不会有死锁的产生。

4.2 Windows下的消费者生产者问题(详细见代码)

函数功能:

(1) 创建信号量:

HANDLE CreateSemaphore(  //创建信号量
	  lpSemaphoreAttributes, //NULL表示默认属性
	  lInitialCount,         //信号量的初值
	  lMaximumCount,  //信号量的最大值
	  lpName);         //信号量的名称

(2)释放信号量:

BOOL ReleaseSemaphore(
	  hSemaphore,   //信号量的句柄
	  lReleaseCount,     //信号量计数增加值
	  lpPreviousCount);  //返回信号量原来值

(3)打开信号量:

HANDLE  OpenSemaphore(dwDesiredAccess,  bInheritHandle,  lpName)

(4)关闭信号量(hSemphore为信号量的句柄):

CloseHandle(hSemphore)

(5)创建共享内存区:

HANDLE CreateFileMapping(
  HANDLE hFile,// 文件句柄,填写 INVALID_HANDLE_VALUE
  LPSECURITY_ATTRIBUTES lpFileMappingAttributes,//安全描述符 
  DWORD flProtect,    // 映射对象保护属性
  DWORD dwMaximumSizeHigh,		// 文件映射的最大长度的高32位
  DWORD dwMaximumSizeLow,		// 文件映射的最大长度的低32位
  LPCTSTR lpName);         // 文件映射对象名称

(6)将共享内存区映射到进程虚拟地址:

LPVOID MapViewOfFile(
  HANDLE hFileMappingObject,  //文件映像对象句柄
  DWORD dwDesiredAccess,      // 数据的访问方式
  DWORD dwFileOffsetHigh,     // 文件映射起始偏移的高32位
  DWORD dwFileOffsetLow,      // 文件映射起始偏移的低32位
  DWORD dwNumberOfBytesToMap);  // 文件中要映射的字节数,0表示映射整个文件映射对象

(7)打开进程中对应的内存映射对象(共享内存区):

HANDLE OpenFileMapping(
  DWORD dwDesiredAccess,  // 数据的访问方式
  BOOL bInheritHandle,    // 是否继承句柄
  LPCTSTR lpName );         // 要打开的文件映射对象名称

(8)取消进程地址空间的映射,关闭共享内存区:

UnmapViewOfFile(pLocalMem);  
pLocalMem=NULL;   
//关闭内核映射文件,hFileMapping 为内存映射文件句柄  
CloseHandle(hFileMapping);

父进程的流程:

(1) 创建信号量,创建共享内存区。使用CreateSemaphore()接口创建信号量MUTEX、FULL、EMPTY,并赋予初值分别为1、0、3,使用CreateFileMapping()接口创建共享内存区,内存区大小即为BUF的大小,并将共享内存区映射到父进程地址空间,得到共享内存区的入口地址pfile。
(2) 通过pfile对共享内存区进行初始化,在之后的各进程中,几乎都采用如下的方式对共享内存区进行读写操作。

if (pfile != NULL) {
	ZeroMemory(pfile, sizeof(struct BUF));
}//得到共享内存区的数据结构
struct BUF* pbuf = reinterpret_cast<struct BUF*>(pfile);
//初始化
pbuf->head = 0;
pbuf->tail = 0;
pbuf->IsEmpty = 0;
memset(pbuf->array, 0, sizeof(pbuf->array));

(3)调用UnmapViewOfFile(pfile)接口解除共享内存在父进程地址空间的映射,使用CloseHandle(pfile)关闭共享内存区
(4)创建三个生产者进程,两个消费者进程。在创建进程时使用了自己写的StartClone(int i)函数,详见代码。该函数在每个进程的argv[]参数中依次存放文件名、给子进程的编号(编号0、1、2表示该进程为生产者进程,编号3、4表示该进程为消费者进程)。
(5)等待所有子进程运行完毕,此处使用到WaitForMultipleObjects()接口
(6)调用CLoseHandle()接口关闭信号量

生产者进程流程:

(1) 调用OpenSemaphore()接口打开MUTEX、FULL、EMPTY信号量。
(2) 调用OpenFileMapping()接口打开共享内存区,调用MapViewOfFile()接口把该内存区映射到生产者进程的虚拟地址空间。
(3) 语句struct BUF* pbuf = reinterpret_cast(pfile)将pbuf指向共享内存区,且其结构为BUF,这使得之后在读取共享内存区中的数据时,可以照BUF的结构进行读取。
(4) 每个生产者进程进行6次存数据操作,存数据对应于4.1中的生产者存数据流程,只是增加了少许步骤。
生产者进程先通过GetRandomChar()得到数据,在申请空缓冲单元后,调用Sleep(GetRandomSleep())函数使其随机等待一段时间,在得到环形缓冲区的使用权后,存放数据。这两个函数为自己编写的函数,具体见代码。
在做完存数据的一系列操作之后,调用localtime()函数获取系统时间,并以时分秒的形式打印,除此,在每次存数据后,应打印出该该进程标识、存放的数据、环形缓冲区的所有数据以供观察,并在打印完后清空输出缓冲区。
(5) 调用UnmapViewOfFile()解除共享内存区在该进程地址空间的映射,并调用CloseHandle()关闭共享内存区句柄,关闭MUTEX、EMPTY、FULL信号量句柄。

消费者进程流程:

(1) 调用OpenSemaphore()接口打开MUTEX、FULL、EMPTY信号量。
(2) 调用OpenFileMapping()接口打开共享内存区,调用MapViewOfFile()接口把该内存区映射到生产者进程的虚拟地址空间,并得到pfile,可将其看做共享内存区的入口。
(3) 语句struct BUF* pbuf = reinterpret_cast(pfile)将pbuf指向共享内存区,且其结构为BUF,这使得之后在读取共享内存区中的数据时,可以照BUF的结构进行读取。
(4) 每个消费者进程进行4次取数据操作,取数据操作对应于4.1中消费者取数据流程,但步骤有所增加:
消费者进程在申请产品后调用Sleep(GetRaondomSleep())函数等待随机时间,在得到环形缓冲区的使用权后,后开始取数据,在完成取数据的一系列操作后,调用localtime()函数得到当前系统时间,并以时分秒的形式打印,除此,还需打印出消费者进程标识、取到的数据、当前环形缓冲区的全部数据等,并在打印完后清除输出缓冲区。
(5) 调用UnmapViewOfFile()解除共享内存区在该进程地址空间的映射,并调用CloseHandle()关闭共享内存句柄,关闭MUTEX、EMPTY、FULL信号量句柄。
在main()函数中,根据传过来的argv[1]参数(即我们给子进程的编号,令此编号为id)进行不同的操作,具体如下:

if argc>1:
	int id = atoi(argv[1]);
	if id < 0:
		exit(-1);//编号小于0说明出现了问题
	else if id < 2:
		生产者进程流程
	else if id <5:
		消费者进程流程
else:
	父进程流程

4.3 Linux下的生产者消费者问题(详见代码):

在Linux下,定义存取数据所需要的P、V操作如下:

void P(int sem_id, int sem_num){
	struct sembuf xx;
	xx.sem_num = sem_num;
	xx.sem_op = -1;
	xx.sem_flg = 0;
	semop(sem_id, &xx, 1);}
void V(int sem_id, int sem_num){
	struct sembuf xx;
	xx.sem_num = sem_num;
	xx.sem_op = 1;
	xx.sem_flg = 0;
	semop(sem_id, &xx, 1);}

在Linux下,共享内存区的定义与之前一致,只是将名字BUF改为了ShrBuf。

使用到的函数:

(1) 创建信号量或取得信号量

int semget(key_t key, int num_sems, int sem_flags);

参数key是整数值(唯一非零),不相关的进程可以通过它访问一个信号量,它代表程序可能要使用的某个资源,程序对所有信号量的访问都是间接的,程序先通过调用semget()函数并提供一个键,再由系统生成一个相应的信号标识符(semget()函数的返回值),只有semget()函数才直接使用信号量键,所有其他的信号量函数使用由semget()函数返回的信号量标识符。如果多个程序使用相同的key值,key将负责协调工作。
参数num_sems指定需要的信号量数目。
参数sem_flags是一组标志,当想要当信号量不存在时创建一个新的信号量,可以和值IPC_CREAT做按位或操作。设置了IPC_CREAT标志后,即使给出的键是一个已有信号量的键,也不会产生错误。而IPC_CREAT | IPC_EXCL则可以创建一个新的,唯一的信号量,如果信号量已存在,返回一个错误。
semget()函数成功返回一个相应信号标识符(非零),失败返回-1。
(2)修改信号量的值

int semop(int sem_id, struct sembuf *sem_opa, size_t num_sem_ops);

(3) 对信号量执行控制操作。如读取或修改信号量集合的有关状态信息,撤销信号量集合等等。

int semctl(int sem_id,int sem_num,int cmd,union semun arg);

参数分别为信号量集合标识、信号量索引、要执行的命令、设置信号量信息的参数。
(4) 申请(创建)一个共享内存区

shmget(key,size,shmflg)

key为共享内存区的关键字,shmflg为创建或打开标志,size是共享内存区字节长度。
(5) 将共享内存区加到申请通信的进程地址空间

shmat(shmid,shmadd,shmflg)

shmid是共享内存区的标识,shmadd是给出的附加到进程虚空间的地址,通常 shmflg和shmadd为0。
(6) 解除共享内存区与进程之间的连接

shmdt(shamaddr)

shmaddr是共享内存区在进程地址空间的虚地址
(7) 对共享内存区执行控制操作

int shmctl(int shmid,int cmd,struct shmid_ds *buf)

cmd参数可以指定以下值:
IPC_STAT:把shmid_ds结构中的数据设置为共享内存的当前关联值,即用共享内存的当前关联值覆盖shmid_ds的值。
IPC_SET:如果进程有足够的权限,就把共享内存的当前关联值设置为shmid_ds结构中给出的值
IPC_RMID:删除共享内存段
SHM_LOCK:将共享段锁在内存
SHM_UNLOCK:解锁共享段

父进程流程:

(1) 调用semget()接口创建大小为3的信号量集,并返回信号量集的标识,若创建失败,应返回错误信息。
(2) 调用semctl()接口初始化信号量集中各信号的信息。在信号集中,索引0表示MUTEX,1表示EMPTY,2表示FULL。
(3) 调用shmget()接口申请共享内存区,且大小为ShrBuf的大小,若申请失败,应返回错误信息,若申请成功,调用shmat()接口将该共享内存区附加到父进程地址空间,结构体指针MyBuf指向这一地址空间,大致语句如下:

shm_id = shmget(IPC_PRIVATE,sizeof(ShrBuf),0600);
MyBuf = shmat(shm_id, 0, 0);

如此,父进程中便可借助MyBuf存取共享内存区,MyBuf是ShrBuf结构体类型的。子进程附加共享内存区的方式以及让MyBuf指向这一内存区的方式与父进程相同。
(4) 借助MyBuf指针初始化共享内存区
(5) 创建两个生产者进程,三个消费者进程,并让其做相关操作。
(6) 调用wait()接口等待子进程运行结束,因为创建了5个子进程,所有用while(wait(0)!=-1)语句来保证所有子进程都运行结束。
(7) 调用shmdt()接口解除父进程与共享内存区之间的连接,调用shmctl()接口释放共享内存区(指定cmd为IPC_RMID),调用shmctl()接口删除信号量集合(指定cmd为IPC_RMID)。

生产者进程流程:

(1) 调用shmat()接口将共享内存区映射到该进程的地址空间,若映射出错,返会错误信息,若成功映射则执行(2)
(2) 生产者进程要进行6次数据存储,每次存数据的流程与4.1中生产者存数据的流程大体一致,但是增加了几个步骤,如下:生产者进程在申请空缓冲单元和环形缓冲区的使用权后,调用sleep(GetRandomWait())函数使其随机等待一段时间,后将GetRandomInput()得到的随机数据存入缓冲单元。这两个函数为自己编写的函数,具体见代码。
在做完存数据的一系列操作之后,调用localtime()函数获取系统时间,并以时分秒的形式打印,除此,在每次存数据后,应打印出该该进程标识、存放的数据、环形缓冲区的所有数据以供观察,并在打印完后清空输出缓冲区。
(3) 在6次存储数据后,调用shmdt(MyBuf)接口取消共享内存区与生产者进程的连接。
(4) 调用exit(0)接口结束进程。

消费者进程流程:

(1) 调用shmat()接口将共享内存区映射到该进程的地址空间,若映射出错,返会错误信息,若成功映射则执行(2)
(2) 消费者进程要进行4次取数据操作,每次取数据的流程与4.1中消费者取数据的流程大体一致,但是增加了几个步骤,如下:
消费者进程在申请空缓冲单元和环形缓冲区的使用权后,调用sleep(GetRandomWait())函数使其随机等待一段时间。
在做完取数据的一系列操作之后,调用localtime()函数获取系统时间,并以时分秒的形式打印,除此,在每次存数据后,应打印出该该进程标识、存放的数据、环形缓冲区的所有数据以供观察,并在打印完后清空输出缓冲区。
(3) 在4次取数据后,调用shmdt(MyBuf)接口取消共享内存区与生产者进程的连接。
(4) 调用exit(0)接口结束进程。
在程序中,为了更好地区分父进程、消费者进程、生产者进程的执行关系,给出以下控制流:

main(){
	信号量的创建、初始化;
	共享内存的创建,共享内存区附加到父进程地址空间及初始化;
	for(i=0;i<2;i++):
		创建生产者进程:
			如果创建失败,返回错误信息;
		如果进程标识号为0:
			生产者进程流程;
	for(i=0;i<3;i++):
		创建消费者进程:
			如果创建失败,返回错误信息;
		如果进程标识号为0:
			消费者进程流程;
		等待子进程结束;
		解除映射、删除共享内存区、删除信号量集;
}

4.4 实验结果

4.4.1 Linux下的实验结果

操作系统实验 生产者消费者问题详解_第1张图片

查看共享内存、信号量是否被释放:
操作系统实验 生产者消费者问题详解_第2张图片

前后执行多次发现,结果都如上图,可见,在程序执行完后,信号量数组为空,这足以说明共享内存的和信号量在使用完后被释放掉。
4.4.2 Windows下的实验结果
操作系统实验 生产者消费者问题详解_第3张图片

五、实验收获与体会

本次实验主要是针对操作系统中的进程间同步和互斥等知识的运用。
在实验中,掌握了Linux和Windows下共享内存的创建、使用和销毁,掌握了信号量的申请、使用记忆销毁,对Linux和Windows下关于共享内存、信号量创建、使用、销毁以及进程创建、销毁的系统调用有了更深刻的认识和理解。
在实验中,掌握了如何通过信号量实现进程间的同步和互斥。
在实验中,要注意释放已申请的共享内存和信号量。在Linux下,需要调用shmctl来销毁共享内存区,调用semctl来销毁信号量;在Windows下,应记得调用CloseHandle()关闭句柄。
Windows下用WaitForSingleObject()来实现信号量的P操作。

六、实验代码

Windows下的实验代码

#include
#include
#include
#include
#define BufLength 3
#define P(S) WaitForSingleObject(S,INFINITE);
#define V(S) ReleaseSemaphore(S,1,NULL);
static LPCTSTR filemapping_name = "FileMapping";
HANDLE ProcessHandle[5];
using namespace std;

struct BUF {
	char array[BufLength];
	int head;
	int tail;
	int IsEmpty;
};

int GetRandomSleep() {
	return (rand() + GetCurrentProcessId())%100 + 1000;
}

char GetRandomChar() {
	return ((rand() + GetCurrentProcessId()) % 26 + 'A');
}

///创建子进程 
void StartClone(const int id) {
	TCHAR szFilename[MAX_PATH];
	GetModuleFileName(NULL, szFilename, MAX_PATH);

	TCHAR szcmdLine[MAX_PATH];
	sprintf(szcmdLine, "\"%s\" %d", szFilename, id);

	STARTUPINFO si;
	ZeroMemory(reinterpret_cast<void*>(&si), sizeof(si));
	si.cb = sizeof(si);
	PROCESS_INFORMATION pi;

	BOOL bCreateOK = CreateProcess(
		szFilename,
		szcmdLine,
		NULL,
		NULL,
		FALSE,
		CREATE_DEFAULT_ERROR_MODE,
		NULL,
		NULL,
		&si,
		&pi);
	//通过返回的hProcess来关闭进程
	if (bCreateOK) {
		CloseHandle(pi.hProcess);
		CloseHandle(pi.hThread);
		ProcessHandle[id] = pi.hProcess;
	}
	else {
		printf("child process error!\n");
		exit(0);
	}
}

///父进程程序 
void ParentProc() {
	//创建信号量 
	HANDLE MUTEX = CreateSemaphore(NULL, 1, 1, "mymutex");
	HANDLE EMPTY = CreateSemaphore(NULL, 3, 3, "myempty");
	HANDLE FULL = CreateSemaphore(NULL, 0, 3, "myfull");
	
	//创建内存映射
	HANDLE hMapping = CreateFileMapping(
		NULL, 
		NULL, 
		PAGE_READWRITE, 
		0, 
		sizeof(struct BUF), 
		filemapping_name);
	if (hMapping != INVALID_HANDLE_VALUE) {
		LPVOID pfile = MapViewOfFile(
			hMapping, 
			FILE_MAP_ALL_ACCESS, 
			0, 
			0, 
			0);
		if (pfile != NULL) {
			ZeroMemory(pfile, sizeof(struct BUF));
		}
		//初始化
		struct BUF* pbuf = reinterpret_cast<struct BUF*>(pfile);
		pbuf->head = 0;
		pbuf->tail = 0;
		pbuf->IsEmpty = 0;
		memset(pbuf->array, 0, sizeof(pbuf->array));
		//解除映射
		UnmapViewOfFile(pfile);
		pfile = NULL;
		CloseHandle(pfile);
	}
	//生产者
	for (int i = 0; i < 2; i++) {
		StartClone(i);
	}
	//消费者
	for (int i = 2; i < 5; i++) {
		StartClone(i);
	}
	WaitForMultipleObjects(5, ProcessHandle, TRUE, INFINITE);
	//回收信号量 
	CloseHandle(EMPTY);
	CloseHandle(FULL);
	CloseHandle(MUTEX);
}
///生产者 
void Producer() {
	//打开信号量
	//HANDLE MUTEX = CreateMutex(NULL,FALSE,"mymutex");
	HANDLE MUTEX = OpenSemaphore(SEMAPHORE_ALL_ACCESS, FALSE, "mymutex");
	HANDLE EMPTY = OpenSemaphore(SEMAPHORE_ALL_ACCESS, FALSE, "myempty");
	HANDLE FULL = OpenSemaphore(SEMAPHORE_ALL_ACCESS, FALSE, "myfull");

	//打开共享内存区,并加载到当前进程地址空间
	HANDLE hmap = OpenFileMapping(
		FILE_MAP_ALL_ACCESS, 
		FALSE, 
		filemapping_name);
	LPVOID pfile = MapViewOfFile(
		hmap,
		FILE_MAP_ALL_ACCESS,
		0,
		0,
		0);
	struct BUF* pbuf = reinterpret_cast<struct BUF*>(pfile);
	
	for (int i = 0; i < 6; i++) {
		char ch = GetRandomChar();
		P(EMPTY);
		//sleep
		Sleep(GetRandomSleep());
		P(MUTEX);
		pbuf->array[pbuf->tail] = ch;//存数据 
		pbuf->tail = (pbuf->tail + 1) % BufLength;//修改指针 
		pbuf->IsEmpty = 1;//修改状态 
		time_t t=time(NULL);
		struct tm* ptm = localtime(&t);
		//GetSystemTime(&tm);
		printf("\nProducerID:%6d, puts data:%c\tbuffer:\t%c\t%c\t%c\t%d时%d分%d秒",
			(int)GetCurrentProcessId(),
			ch,
			pbuf->array[0], 
			pbuf->array[1], 
			pbuf->array[2],
			ptm->tm_hour,
			ptm->tm_min,
			ptm->tm_sec);
		fflush(stdout);
		V(MUTEX);//释放缓冲区使用权 
		V(FULL);
	}
	//解除映射
	UnmapViewOfFile(pfile);
	pfile = NULL;
	CloseHandle(pfile);
	//关闭信号量
	CloseHandle(MUTEX);
	CloseHandle(EMPTY);
	CloseHandle(FULL);
}
///消费者 
void Customer() {
	//打开信号量
	HANDLE MUTEX = OpenSemaphore(SEMAPHORE_ALL_ACCESS, FALSE, "mymutex");
	//HANDLE MUTEX = CreateMutex(NULL,FALSE,"mymutex");
	HANDLE EMPTY = OpenSemaphore(SEMAPHORE_ALL_ACCESS, FALSE, "myempty");
	HANDLE FULL = OpenSemaphore(SEMAPHORE_ALL_ACCESS, FALSE, "myfull");

	//打开共享内存区,并加载到当前进程地址空间
	HANDLE hmap = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, filemapping_name);
	LPVOID pfile = MapViewOfFile(
		hmap,
		FILE_MAP_ALL_ACCESS,
		0,
		0,
		0);
	struct BUF* pbuf = reinterpret_cast<struct BUF*>(pfile);
    //读数据(取产品) 
	for (int i = 0; i < 6; i++) {
		P(FULL);
		//sleep
		Sleep(GetRandomSleep());
		P(MUTEX);
		char ch = pbuf->array[pbuf->head];
		pbuf->array[pbuf->head] = ' ';//将缓冲置空 
		pbuf->head = (pbuf->head + 1) % BufLength;//修改指针 
		pbuf->IsEmpty = (pbuf->head == pbuf->tail);//修改状态 
		time_t t=time(NULL);
		struct tm* ptm = localtime(&t);
		printf("\nCustomerID:%6d, gets data:%c\tbuffer:\t%c\t%c\t%c\t%d时%d分%d秒",
			(int)GetCurrentProcessId(),
			ch,
			pbuf->array[0], 
			pbuf->array[1], 
			pbuf->array[2],
			ptm->tm_hour,
			ptm->tm_min,
			ptm->tm_sec);
		fflush(stdout);
		V(EMPTY);
		//ReleaseMutex(MUTEX);
		V(MUTEX);//释放缓冲区使用权 
	}
	//解除映射
	UnmapViewOfFile(pfile);
	pfile = NULL;
	CloseHandle(pfile);
	//关闭信号量
	CloseHandle(MUTEX);
	CloseHandle(EMPTY);
	CloseHandle(FULL);
	
}

int main(int argc, char* argv[]) {
	if (argc > 1) {
		int id = atoi(argv[1]);
		if (id < 0) {
			printf("maybe child process error!\n");
			exit(-1);
		}
		else if (id < 2) {
			Producer();
		}
		else if (id < 5) {
			Customer();
		}
	}

	else {
		ParentProc();
	}
	return 0;
}

Linux下的实验代码

#include
#include
#include
#include
#include
#include
#include
#include
#include
#define MUTEX 0
#define EMPTY 1
#define FULL 2
#define Length 3
#define WriteProc 2
#define ReadProc 3
#define W_P_Redo 6
#define R_P_Redo 4
#define SEM_KEY 300

void P(int sem_id, int sem_num) {
	struct sembuf xx;
	xx.sem_num = sem_num;
	xx.sem_op = -1;
	xx.sem_flg = 0;
	semop(sem_id, &xx, 1);
}

void V(int sem_id, int sem_num) {
	struct sembuf xx;
	xx.sem_num = sem_num;
	xx.sem_op = 1;
	xx.sem_flg = 0;
	semop(sem_id, &xx, 1);
}

struct ShrBuf {
	int array[3];
	int head;
	int tail;
	int IsEmpty;
};

//随机停留一定时间
int GetRandomWait() {
	//srand((unsigned)(getpid() + time(NULL)));
	return rand() % 5;
}

//随机取10以内的整数存入
int GetRandomInput() {
	srand((unsigned)(getpid() + time(NULL)));
	return (rand() % 100+1);
}

int main() {
	int sem_id, shm_id, pid, pid2;//信号量id 
	int i, rc;
	struct ShrBuf *MyBuf;
	time_t cur;
	struct tm *p;
   //init semget
	if ((sem_id = semget(SEM_KEY, 3, IPC_CREAT | 0600)) < 0) {
		printf("main's semget is error\n");
		exit(-1);
	}
	semctl(sem_id, MUTEX, SETVAL, 1);
	semctl(sem_id, EMPTY, SETVAL, Length);
	semctl(sem_id, FULL, SETVAL, 0);

	//创建共享内存
	if ((shm_id = shmget(IPC_PRIVATE, 24, 0600)) < 0) {
			printf("error on shget\n");
			exit(-1);
	}
	if ((MyBuf = shmat(shm_id, 0, 0)) == (void*)-1) {
		printf("error on shmat\n");
		exit(-1);
	}
	MyBuf->head = 0;
	MyBuf->tail = 0;
	MyBuf->IsEmpty = 1;
	memset(MyBuf->array,0,sizeof(MyBuf->array));

	for (int k = 0; k < WriteProc; k++) {
		if ((pid = fork()) < 0) {
			printf("error on fork\n");
			exit(-1);
		}
		if (pid == 0) {
			//把共享内存加载到进程地址空间
			if ((MyBuf = shmat(shm_id, 0, 0)) == (void*)-1) {
				printf("error on shmat\n");
				exit(-1);
			}
			//6次存操作
			for (i = 0; i < W_P_Redo; i++) {
				P(sem_id, EMPTY);
				P(sem_id, MUTEX);
				sleep(GetRandomWait());
				MyBuf->array[MyBuf->tail] = GetRandomInput();
				int num = MyBuf->array[MyBuf->tail];
				MyBuf->tail = (MyBuf->tail + 1) % Length;
				MyBuf->IsEmpty = 0;
				cur = time(NULL);
				p = localtime(&cur);
				printf("%d时%d分%d秒\t", p->tm_hour, p->tm_min, p->tm_sec);
				printf("producerID:%d    puts data:%d \tbuffer:\t%d\t%d\t%d\n", getpid(),num,MyBuf->array[0],MyBuf->array[1],MyBuf->array[2]);
				V(sem_id, MUTEX);
				V(sem_id, FULL);
			}
			shmdt(MyBuf);
			exit(0);
		}
	}

	for (int k = 0; k < ReadProc; k++) {
		if ((pid = fork()) < 0) {
			printf("error on fork\n");
			exit(-1);
		}
		if (pid == 0) {
			//把共享内存加载到进程地址空间
			if ((MyBuf = shmat(shm_id, 0, 0)) == (void*)-1) {
				printf("error on shmat\n");
				exit(-1);
			}
			//4次读操做
			for (i = 0; i < R_P_Redo; i++) {
				P(sem_id, FULL);
				P(sem_id, MUTEX);
				sleep(GetRandomWait());				
				int num = MyBuf->array[MyBuf->head];
				MyBuf->array[MyBuf->head] = 0;
				MyBuf->head = (MyBuf->head + 1) % Length;
				MyBuf->IsEmpty = (MyBuf->head == MyBuf->tail);
				cur = time(NULL);
				p = localtime(&cur);
				printf("%d时%d分%d秒\t", p->tm_hour, p->tm_min, p->tm_sec);
				printf("customerID:%d    gets data:%d \tbuffer:\t%d\t%d\t%d\n", getpid(), num, MyBuf->array[0], MyBuf->array[1], MyBuf->array[2]);
				V(sem_id, MUTEX);
				V(sem_id, EMPTY);
			}
			shmdt(MyBuf);
			exit(0);
		}
	}

	while (wait(0) != -1);
	shmdt(MyBuf);
	semctl(sem_id, IPC_RMID, 0);
	//semctl(sem_id, IPC_RMID, 0);
	//semctl(sem_id, IPC_RMID, 0);
	shmctl(shm_id, IPC_RMID, 0);
	exit(0);
}

你可能感兴趣的:(操作系统实验,操作系统)