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

一、实验目的

加深对进程概念的理解,明确进程与程序的区别。
认识并发执行的本质。
理解和掌握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_VALUELPSECURITY_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<struct BUF*>(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<struct BUF*>(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下的实验结果

查看共享内存、信号量是否被释放:

前后执行多次发现,结果都如上图,可见,在程序执行完后,信号量数组为空,这足以说明共享内存的和信号量在使用完后被释放掉。
4.4.2 Windows下的实验结果

五、实验收获与体会

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

六、实验代码

Windows下的实验代码

#include<windows.h>
#include<iostream>
#include<string>
#include<time.h>
#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);//sleepSleep(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);//sleepSleep(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<sys/types.h>
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<time.h>
#include<sys/wait.h>
#include<sys/shm.h>
#include<sys/sem.h>
#include<string.h>
#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 300void 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 semgetif ((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);
}

操作系统实验 生产者消费者问题详解相关推荐

  1. 操作系统,生产者-消费者问题详解

    文章目录 信号量机制 1. 实现进程互斥 2. 实现进程同步 3. 实现进程的前驱关系 生产者-消费者问题 问题描述 解析 信号量机制 1. 实现进程互斥 分析问题,确定临界区 设置互斥信号量,初值为 ...

  2. Linux 实验:记录型信号量 生产者-消费者问题详解

    进程同步问题是一个非常重要且相当有趣的问题,因而吸引了很多学者对他进行研究.本文就选取其中较为代表性的生产者-消费者问题来进行学习,以帮助我们更好的理解进程同步的概念及实现方法. 一.问题描述 有一群 ...

  3. C++11 并发指南九(综合运用: C++11 多线程下生产者消费者模型详解)

    前面八章介绍了 C++11 并发编程的基础(抱歉哈,第五章-第八章还在草稿中),本文将综合运用 C++11 中的新的基础设施(主要是多线程.锁.条件变量)来阐述一个经典问题--生产者消费者模型,并给出 ...

  4. 生产者消费者模式详解(转载)

    ★简介 在实际的软件开发过程中,经常会碰到如下场景:某个模块负责产生数据,这些数据由另一个模块来负责处理(此处的模块是广义的,可以是类.函数.线程.进程等).产生数据的模块,就形象地称为生产者:而处理 ...

  5. Java多线程(含生产者消费者模式详解)

    多线程 导航 多线程 1 线程.进程.多线程概述 2 创建线程 (重点) 2.1 继承Thread类(Thread类也实现了Runnable接口) 2.2 实现Runnable接口(无消息返回) 2. ...

  6. 生产者/消费者模型详解(基于Java)

    title: 生产者消费者模型 tags: 多线程 synchronized 锁 wait() notify() 生产者/消费者模型原理以及代码实现 一.生产者/消费者模型原理 所谓的生产者消费者模型 ...

  7. 单线程下的生产者--消费者模式详解,wait和sleep的区别

    1. 单线程下的生产者--消费者模式 1.1 该模式下,一个线程生产数据,另一个线程处理数据.当数据还没被处理,那么生产数据的线程进入等待状态:如果数据还没生产,那么处理数据的线程进入等待状态,代码及 ...

  8. 线程同步之 生产者消费者模型详解

    前言 博主本来没打算讲这个比较前面的知识的(博主socket编程还有两个部分没讲,进程也才写完回收僵尸进程的三种方法,信号捕捉器也才完结),但是今天有朋友来问博主,什么是生产者消费者模型,所以博主就先 ...

  9. 生产者消费者模型详解

    生产者消费者模型 文章目录 生产者消费者模型 什么是生产者消费者模型 基于BlockingQueue的生产者消费者模型 单生产者单消费者模型 多生产者多消费者模型 什么是生产者消费者模型 生产者消费者 ...

最新文章

  1. 微信开发之自动回复图文消息
  2. tf.trainable_variables() and tf.all_variables()
  3. C++:友元(非成员友元函数、成员友元函数、友元类)
  4. MyEclipse10安装SVN插件的几种方法
  5. 微信小程序获取openid
  6. Android11怎么截屏,对标IOS?Android11或无缘屏幕长截图
  7. 官方中文版开源!快速入门PyTorch
  8. 【最全资料下载】Kubernetes and Cloud Native Meetup (北京站)
  9. 无人机rtmp推流直播解决方案
  10. Software--Architecture--SOA 面向服务体系结构
  11. 词频统计(30 分)(map vector sort)
  12. swustoj水王C语言,swust西南科技大学OJ数据结构80题答案
  13. 打印机如何手动正反面Mac设置黑白打印
  14. 递归走楼梯or斐波那契数列
  15. 狄利克雷条件和帕塞瓦尔定理
  16. 24点计算器c语言源代码,萌新求助!!24点游戏计算器
  17. ActiveX控件之制作图片属性页
  18. uipath锚点的使用
  19. 【CTF-misc】凯撒大帝在培根里藏了什么
  20. 基于 Java 的短视频实战项目

热门文章

  1. 开源开放 | 多模态地球科学知识图谱GAKG
  2. Gate8.2--安装
  3. memory compression关闭,亲测有效
  4. 斜线表头html怎么做,Excel三栏斜线表头的完美制作方法
  5. 数组题目:全局倒置与局部倒置
  6. V2X-ViT:基于Vision Transformer的V2X协同感知
  7. numpy array 增加一列(行)
  8. 星空璀璨,时光流逝,分享技术,记录生活——2016年11月22日
  9. 大数据主要学些什么?(大数据学习路线图)
  10. 【简单图形解释】刚体上任意一点速度与刚体角速度的关系