说明:
本实验基于本机macOS系统和Windows虚拟机完成,具体实验环境如下:
Linux环境配置如下:
Windows虚拟机环境配置如下:
Windows下没有直接创建共享内存区域的方式,于是实验中使用文件映射的方式实现共享内存。
以下数据结构均定义在头文件ProducerConsumer.hpp
中
定义缓冲区结构如下:
typedef struct buffer
{
int buff[BUFFER_LEN];
int head;
int tail;
int empty;
} buffer;
说明
buff
为缓冲区内容,是一个长度为BUFFER_LEN
的数组head
和tail
为缓冲区指针,用于指示缓冲区读写位置empty
为标志位,标识缓冲区是否为空定义共享内存结构如下:
typedef struct shareMemory
{
buffer buffer_data;
HANDLE sem_full;
HANDLE sem_empty;
HANDLE sem_mutex;
} shm;
说明
buffer_data
为缓冲区,为自定义数据结构sem_full
,sem_empty
,sam_mutex
为三个相关信号量,用于缓冲区同步与互斥使用CreateFileMapping()
是用于创建一个文件映射内核对象的函数,声明如下:
HANDLE WINAPI CreateFileMapping(
_In_HANDLE hFile,
_In_opt_LPSECURITY_ATTRIBUTES lpAttributes,
_In_DWORD flProtect,
_In_DWORD dwMaximumSizeHigh,
_In_DWORD dwMaximumSizeLow,
_In_opt_LPCTSTR lpName
);
实验中使用如下:
HANDLE handleFileMapping=
CreateFileMapping(INVALID_HANDLE_VALUE,NULL,PAGE_READWRITE,0,sizeof(shm),SHM_NAME);
说明
hFile
使用INVALID_HANDLE_VALUE
,表示在页面文件中创建一个可共享的文件映射,在本实验中用于作为共享内存flProtect
使用PAGE_READWRITE
,表示以可读、写的方式打开映射dwMaximumSizeLow
使用sizeof(shm)
,该数据为文件映射最大长度的低32位,表示该文件大小只有在4.1.1定义的数据结构sharememory
一样大。lpName
使用SHM_NAME
该值为宏定义,表示共享内存区名字MapViewOfFile()
是用于将一个文件映射对象映射到当前程序地址空间的函数,声明如下:
LPVOID WINAPI MapViewOfFile(
_In_HANDLE hFileMappingObject,
_In_DWORD dwDesiredAccess,
_In_DWORD dwFileOffsetHigh,
_In_DWORD dwFileOffsetLow,
_In_SIZE_T dwNumberOfBytesToMap
);
实验使用如下:
LPVOID shmaddr=MapViewOfFile(handleFileMapping,FILE_MAP_ALL_ACCESS,0,0,0);
说明
hFileMappingObject
使用CreateFileMapping
的返回句柄,表示将创建的对应的文件映射对象映射到程序地址空间dwDesiredAccess
使用FILE_MAP_ALL_ACCESS
,表示可以使用文件所有权限,是与创建文件映射对象相对应的权限OpenFileMapping()
是用于打开一个已经存在的文件映射对象的函数,返回相应打开的句柄,声明如下:
HANDLE OpenFileMapping(
_In_DWORD dwDesiredAccess,
_In_BOOL bInheritHandle,
_In_LPCSTR lpName
);
实验使用如下:
HANDLE hFileMapping=OpenFileMapping(FILE_MAP_ALL_ACCESS,FALSE,SHM_NAME);
说明
dwDesireAccess
使用FILE_MAP_ALL_ACCESS
,表示打开该映射对象时具有全部权限,和创建文件对象对应bInheritHandle
使用FALSE
,表示由该进程启动的新进程不允许继承该句柄,防止错误发生lpName
使用SHM_NAME
,表示打开创建的名为SHM_NAME
的文件映射对象CreateSemaphore()
是用于创建一个信号量的函数,返回对应信号量的句柄,声明如下:
HANDLE CreateSemaphore(
LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,
LONG lInitialCount,
LONG lMaximumCount,
LPCTSTR lpName
);
实验使用如下:
shmaddr->sem_empty=CreateSemaphore(NULL,BUFFER_LEN,BUFFER_LEN,"SEM_EMPTY");
说明
lInitialCount
和lMaximumCount
分别表示该信号量初始值和最大可以到达的值,实验中设置如下:
sem_empty
初始值:BUFFER_LEN
最大值:BUFFER_LEN
sem_full
初始值:0
最大值:BUFFER_LEN
sem_mutex
初始值:1
最大值:1
lpName
是信号量的名字CreateProcess()
是用于创建进程并为进程指定运行程序的函数,声明如下
BOOL CreateProcess(LPCTSTR lpApplicationName, LPTSTR lpCommandLine,
LPSECURITY_ATTRIBUTES lpProcessAttributes,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL bInheritHandles, DWORD dwCreationFlag,
LPVOID lpEnvironment, LPCTSTR lpCurrentDirectory,
LPSTARTUPINFO lpStartupInfo,
LPPROCESS_INFORMTION lpProcessInformation);
实验使用如下:
CreateProcess(szFilename, NULL, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
说明
lpApplication
:该参数指定新进程将使用的可执行文件lpCommandLine
:该参数指定里传递给新进程的命令行字符串,该函数将按照一定的顺序搜索该可执行文件位置,并执行lpProcessInformation
:该参数是只想包含返回的进程和线程的句柄、进程和线程标识符的指针。在等待同步函数中需要从该结构中调取句柄信息WaitForSingleObject()
是用于等待对象信号状态的函数,声明如下:
DWORD WaitForSingleObject(
HANLDE hHandle,
DWORD dwMillisecondes
);
实验使用如下:
WaitForSingleObject(shmaddr->sem_full, INFINITE);
说明
hHandle
指示要等待信号状态的对象句柄INFINITE
表示等待时间无限ReleaseSemaphore()
是用于给指定信号量增加指定值的函数,声明如下:
BOOL ReleaseSemaphore(
HANDLE hSemaphore,
LONG lReleaseCount,
LPLONG lpPreviousCount
);
实验使用如下:
ReleaseSemaphore(shmaddr->sem_mutex, 1, NULL);
说明
hSemaphore
是指定信号量的句柄lReleaseCount
是给信号量增加的值UnmapViewOfFile()
是用于停止当前程序的一个内存映射的函数,声明如下:
BOOL WINAPI UnmapViewOfFile(
_In_LPCVOID lpBaseAddress
);
实验使用如下:
UnmapViewOfFile(pFile);
说明
pFile
是函数MapViewOfFile()
函数返回的文件映射对象句柄CloseHandle()
是用于关闭现有已打开句柄的函数,声明如下:
BOOL CloseHandle(
HANDLE hObject
);
实验使用如下:
CloseHandle(hFileMapping);
说明
hFileMapping
是函数OpenFileMapping()
的返回值,是一个已经打开的文件映射对象句柄本实验程序源代码分为四个文件,分别是
ProducerConsumer.hpp
ProducerConsumer.cpp
Producer.cpp
Consumer.cpp
//ProducerConsumer.hpp
#include
#include
#include
#include
#include
#include
#define NUM_PRODUCER 2
#define TIME_PRODUCER 6
#define NUM_CONSUMER 3
#define TIME_CONSUMER 4
#define BUFFER_LEN 3
#define SHM_NAME "Buffer"
static HANDLE handleFileMapping;
typedef struct buffer
{
int buff[BUFFER_LEN];
int head;
int tail;
int empty;
} buffer;
typedef struct shareMemory
{
buffer buffer_data;
HANDLE sem_full;
HANDLE sem_empty;
HANDLE sem_mutex;
} shm;
//ProducerConsumer.cpp
#include "ProducerConsumer.hpp"
//Window下共享内存是由FileMapping实现的
HANDLE shareMemory(){
//创建临时文件映射对象,使用INVALID_HANDLE_VALUE代替文件句柄
HANDLE handleFileMapping=CreateFileMapping(INVALID_HANDLE_VALUE,NULL,PAGE_READWRITE,0,sizeof(shm),SHM_NAME);
if (handleFileMapping==NULL||handleFileMapping==INVALID_HANDLE_VALUE) {
printf("Create File Mapping Failed!\n");
exit(1);
}
//把文件映射对象的一个View映射到进程地址空间,返回文件映射其实地址
LPVOID shmaddr=MapViewOfFile(handleFileMapping,FILE_MAP_ALL_ACCESS,0,0,0);
if (shmaddr==NULL) {
printf("Map View of File Failed!\n");
exit(1);
}
ZeroMemory(shmaddr,sizeof(shm));
//解除当前地址空间的映射
UnmapViewOfFile(shmaddr);
return handleFileMapping;
}
int main(int argc, char const *argv[])
{
// SYSTEMTIME time;
//创建共享内存
handleFileMapping=shareMemory();
HANDLE hFileMapping=OpenFileMapping(FILE_MAP_ALL_ACCESS,FALSE,SHM_NAME);
if (hFileMapping==NULL) {
printf("Open File Mapping Failed!\n");
exit(1);
}
LPVOID pFile=MapViewOfFile(hFileMapping,FILE_MAP_ALL_ACCESS,0,0,0);
if (pFile==NULL) {
printf("Map View of File Failed!\n");
exit(1);
}
shm *shmaddr=(shm*)(pFile);
shmaddr->buffer_data.head=0;
shmaddr->buffer_data.tail=0;
shmaddr->buffer_data.empty = 1;
shmaddr->sem_empty=CreateSemaphore(NULL,BUFFER_LEN,BUFFER_LEN,"SEM_EMPTY");
shmaddr->sem_full=CreateSemaphore(NULL,0,BUFFER_LEN,"SEM_FULL");
shmaddr->sem_mutex=CreateSemaphore(NULL,1,1,"SEM_MUTEX");
UnmapViewOfFile(pFile);
pFile=NULL;
CloseHandle(hFileMapping);
//创建子进程
// char szCmdLine[MAX_PATH];
char szFilename[MAX_PATH];
HANDLE lphandles[NUM_CONSUMER+NUM_PRODUCER];
//创建生产者
for(int i = 0; i < NUM_PRODUCER; i++)
{
STARTUPINFO si;
PROCESS_INFORMATION pi;
sprintf(szFilename,"./Producer.exe");
// sprintf(szCmdLine,"\"%s\"","./Producer.exe");
ZeroMemory(&si,sizeof(STARTUPINFO));
si.cb=sizeof(STARTUPINFO);
if (!CreateProcess(szFilename,NULL,NULL,NULL,FALSE,0,NULL,NULL,&si,&pi)) {
printf("Create Producer Process Failed\n");
exit(1);
}
//把进程句柄存下来
lphandles[i]=pi.hProcess;
}
for(int i = 0; i < NUM_CONSUMER; i++)
{
STARTUPINFO si;
PROCESS_INFORMATION pi;
sprintf(szFilename,"./Consumer.exe");
// sprintf(szCmdLine,"\"%s\"","./Producer.exe");
ZeroMemory(&si, sizeof(STARTUPINFO));
si.cb = sizeof(STARTUPINFO);
if (!CreateProcess(szFilename, NULL, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi))
{
printf("Create Consumer Process Failed\n");
exit(1);
}
//把进程句柄存下来
lphandles[NUM_PRODUCER+i] = pi.hProcess;
}
// WaitForMultipleObjects(NUM_CONSUMER+NUM_PRODUCER,lphandles,TRUE,INFINITE);
for(int i = 0; i < NUM_CONSUMER+NUM_PRODUCER; i++)
{
WaitForSingleObject(lphandles[i],INFINITE);
CloseHandle(lphandles[i]);
}
CloseHandle(handleFileMapping);
handleFileMapping=INVALID_HANDLE_VALUE;
// Sleep(2000);
return 0;
}
//Producer.cpp
#include "ProducerConsumer.hpp"
int main(int argc, char const *argv[])
{
SYSTEMTIME time;
HANDLE hFileMapping=OpenFileMapping(FILE_MAP_ALL_ACCESS,FALSE,SHM_NAME);
if (hFileMapping==NULL) {
printf("In Producer Open File Mapping Failed!\n");
exit(1);
}
LPVOID pFile=MapViewOfFile(hFileMapping,FILE_MAP_ALL_ACCESS,0,0,0);
if (pFile==NULL) {
printf("Map View of File Failed!\n");
exit(1);
}
shm *shmaddr=(shm*)(pFile);
shmaddr->sem_empty=OpenSemaphore(SEMAPHORE_ALL_ACCESS,FALSE,"SEM_EMPTY");
shmaddr->sem_full=OpenSemaphore(SEMAPHORE_ALL_ACCESS,FALSE,"SEM_FULL");
shmaddr->sem_mutex=OpenSemaphore(SEMAPHORE_ALL_ACCESS,FALSE,"SEM_MUTEX");
int pid=GetCurrentProcessId();
for(int i = 0; i < TIME_PRODUCER; i++)
{
srand(pid+i);
int input=rand()%3000;
Sleep(input);
WaitForSingleObject(shmaddr->sem_empty,INFINITE);
WaitForSingleObject(shmaddr->sem_mutex,INFINITE);
shmaddr->buffer_data.buff[shmaddr->buffer_data.tail]=input;
shmaddr->buffer_data.tail=(shmaddr->buffer_data.tail+1)%BUFFER_LEN;
shmaddr->buffer_data.empty=0;
GetSystemTime(&time);
printf("Now,the time is %04d:%02d:%02d-%02d:%02d:%02d\n",time.wYear,time.wMonth,time.wDay,time.wHour+8,time.wMinute,time.wSecond);
fflush(stdout);
printf("Producer %d put %d into buffer\n",pid,input);
fflush(stdout);
printf("The buffer contains: ");
fflush(stdout);
//由于是生产者,所以缓冲区不会为空
for (int i = shmaddr->buffer_data.head;;)
{
printf("%d ", shmaddr->buffer_data.buff[i]);
fflush(stdout);
i = (i + 1) % BUFFER_LEN;
if (i == shmaddr->buffer_data.tail)
{
printf("\n\n");
fflush(stdout);
break;
}
}
ReleaseSemaphore(shmaddr->sem_mutex, 1, NULL);
ReleaseSemaphore(shmaddr->sem_full, 1, NULL);
}
UnmapViewOfFile(pFile);
pFile=NULL;
CloseHandle(hFileMapping);
return 0;
}
//Consumer.cpp
#include "ProducerConsumer.hpp"
int main(int argc, char const *argv[])
{
SYSTEMTIME time;
HANDLE hFileMapping = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, SHM_NAME);
if (hFileMapping == NULL)
{
printf("In Consumer Open File Mapping Failed!\n");
exit(1);
}
LPVOID pFile = MapViewOfFile(hFileMapping, FILE_MAP_ALL_ACCESS, 0, 0, 0);
if (pFile == NULL)
{
printf("Map View of File Failed!\n");
exit(1);
}
shm *shmaddr = (shm *)(pFile);
shmaddr->sem_empty = OpenSemaphore(SEMAPHORE_ALL_ACCESS, FALSE, "SEM_EMPTY");
shmaddr->sem_full = OpenSemaphore(SEMAPHORE_ALL_ACCESS, FALSE, "SEM_FULL");
shmaddr->sem_mutex = OpenSemaphore(SEMAPHORE_ALL_ACCESS, FALSE, "SEM_MUTEX");
int pid = GetCurrentProcessId();
for (int i = 0; i < TIME_CONSUMER; i++)
{
srand(pid);
int sleep_time = rand() % 3000;
Sleep(sleep_time);
WaitForSingleObject(shmaddr->sem_full, INFINITE);
WaitForSingleObject(shmaddr->sem_mutex, INFINITE);
int output=shmaddr->buffer_data.buff[shmaddr->buffer_data.head];
shmaddr->buffer_data.head=(shmaddr->buffer_data.head+1)%BUFFER_LEN;
GetSystemTime(&time);
printf("Now,the time is %04d:%02d:%02d-%02d:%02d:%02d\n", time.wYear, time.wMonth, time.wDay, time.wHour + 8, time.wMinute, time.wSecond);
fflush(stdout);
printf("Consumer %d read %d out buffer\n", pid, output);
fflush(stdout);
if (shmaddr->buffer_data.head==shmaddr->buffer_data.tail) {
shmaddr->buffer_data.empty=1;
printf("Now The buffer is Empty!!!\n\n");
}
else{
printf("Now The Buffer Contains: ");
fflush(stdout);
for (int i = shmaddr->buffer_data.head;;)
{
printf("%d ", shmaddr->buffer_data.buff[i]);
fflush(stdout);
i = (i + 1) % BUFFER_LEN;
if (i == shmaddr->buffer_data.tail)
{
printf("\n\n");
fflush(stdout);
break;
}
}
}
ReleaseSemaphore(shmaddr->sem_mutex, 1, NULL);
ReleaseSemaphore(shmaddr->sem_empty, 1, NULL);
}
UnmapViewOfFile(pFile);
pFile = NULL;
CloseHandle(hFileMapping);
return 0;
}
程序运行方式如下:
> g++ ProducerConsumer.cpp -o ProduceConsumer.exe
> g++ Producer.cpp -o Producer.exe
> g++ Consumer.cpp -o Consumer.exe
> ProducerConsumer.exe
两个生产者进程和三个消费者进程并行执行,所以结果每次都有所不同,以下只展示某次结果的部分内容
定义缓冲区结构体如下:
//定义缓冲区结构体
typedef struct buff
{
int buff[BUFFER_LEN];
int head;
int tail;
int empty;
} buffer;
说明
buff
:表示缓冲区内容head
:表示缓冲区头指针,用于生产者写入tail
:表示缓冲区尾指针,用于消费者读出empty
:标志位,判断buff是否为空,因为仅仅使用头尾指针无法判断缓冲区为满或为空semget()
用于获取与某个建关联的信号量集标识,声明如下:
int semget(
key_t key,
int nsems,
int semflg
);
实验使用如下:
int sem_id=semget(SEM_ID,NUM_SEM,IPC_CREAT|0600);
说明
SEM_ID
:宏定义,表示的是信号量集的键值NUM_SEM
:宏定义,表示信号量个数IPC_CREAT
:由于键值不为IPC_PRIVATE
,且键对应的信号量集不存在,在标志中指定IPC_CREAT
可以创建新的信号量集semctl()
是用于执行在信号量集上的控制操作的函数,声明如下:
int semctl(
int semid,
int semnum,
int cmd,
union semun arg
);
实验使用该函数用于信号量初始化和信号量集删除,使用如下:
semctl(sem_id,SEM_EMPTY,SETVAL,sem_val);
semctl(sem_id,IPC_RMID,0);
说明
sem_id
:函数semget
的返回值,标识一个信号量集SEM_EMPTY
:宏定义,标识出信号量集中的第几个信号量SETVAL
:控制操作命令,表示信号量初始化置值sem_val
:一个union semun
的变量,对其中val
进行赋值,用于对信号量进行初始化IPC_RMID
:控制操作命令,表示删除信号量集semop()
是用于信号量的值与相应资源使用情况相关的操作的函数,声明如下:
int semop(
int semid,
struct sembuf *sops,
size_t nsops
);
实验使用将在4.2.3中提到
说明
semid
:信号集标识符,函数semget()
返回值sops
:指向存储信号操作结构的数组指针,信号操作结构将在4.2.3提到nsops
:信号操作结构的数量,大于等于1shmget()
是用于创建共享内存对象的函数,声明如下:
int shmget(
key_t key,
size_t size,
int shmflg
);
实验使用如下:
int shm_id=shmget(SHM_KEY,sizeof(buffer),SHM_MODE|IPC_CREAT);
说明
SHM_KEY
:宏定义,作为共享内存的键值,当该值为0或IPC_PRIVATE时会建立新的共享内存对象SHM_MODE
:宏定义,表示对该共享内存区域的访问模式及权限IPC_CREAT
:用于shmflg
作为标志,当内存中不存在与键匹配的共享内存对象时创建一个共享内存shmat()
是用于把共享内存区对象映射到调用进程的地址空间的函数,声明如下:
void *shmat(
int shmid,
const void *shmaddr,
int shmflg
);
实验使用如下:
buffer* shmaddr=shmat(shm_id,0,0);
说明
shm_id
:共享内存标识符,为函数shmget()
函数的返回值shmaddr
:该函数返回的一个附加好的共享内存地址shmdt()
是用于断开共享内存连接的函数,声明如下:
int shmdt(
const void *shmaddr
);
实验使用如下:
shmdt(shmaddr);
说明
shmaddr
:连接共享内存的起始地址,函数shmat()
函数返回值shmctl()
是用于完成对共享内存控制的函数,声明如下:
int shmctl(
int shmid,
int cmd,
struct shmid_ds *buf
);
实验使用如下:
shmctl(shm_id,IPC_RMID,0);
说明
shm_id
:共享内存标识符,为函数shmget()
函数返回值IPC_RMID
:操作命令,表示删除这片共享内存对信号量集定义P、V操作时,需要使用到sembuf
结构体,声明如下:
struct sembuf{
unsigned short int sem_num;
short int sem_op;
short int sem_flg;
};
说明
sem_num
:标识信号量集中的第几个信号量,从_0_开始sem_op
:标识对信号量所进行的操作,有如下几种:
sem_op
决定semval
为0,若为0则返回,否则信号量的semzcnt
加1,阻塞等待sem_flag
:信号量操作属性标志,为0表示正常操作void P_op(int sem_id,int sem_num){
struct sembuf buf;
buf.sem_num=sem_num;
buf.sem_op=-1;
buf.sem_flg=0;
semop(sem_id,&buf,1);
}
void V_op(int sem_id,int sem_num){
struct sembuf buf;
buf.sem_num=sem_num;
buf.sem_op=1;
buf.sem_flg=0;
semop(sem_id,&buf,1);
}
本程序代码以附件的形式给出,包含于ProducerConsumer.c
文件中
#include
#include
#include
#include
#include
#include
#include
#define NUM_PRODUCER 2
#define TIME_PRODUCER 6
#define NUM_CONSUMER 3
#define TIME_CONSUMER 4
#define BUFFER_LEN 3
#define NUM_SEM 3
#define SEM_ID 233
#define SEM_EMPTY 0
#define SEM_FULL 1
#define SEM_MUTEX 2
#define SHM_KEY 666
#define SHM_MODE 0777
//定义缓冲区结构体
typedef struct buff
{
int buff[BUFFER_LEN]; //缓冲区内容
int head; //缓冲区头指针
int tail; //缓冲区尾指针
int empty; //设置标志位,判断是否buff为空
} buffer;
//定义P操作
void P_op(int sem_id,int sem_num){
struct sembuf buf;
buf.sem_num=sem_num;
buf.sem_op=-1;
buf.sem_flg=0;
semop(sem_id,&buf,1);
}
//定义V操作
void V_op(int sem_id,int sem_num){
struct sembuf buf;
buf.sem_num=sem_num;
buf.sem_op=1;
buf.sem_flg=0;
semop(sem_id,&buf,1);
}
void Producer_op(int sem_id,buffer* shmaddr){
srand((unsigned)(time(NULL))+getpid());
int input = rand();
int sleep_time = input % 10; //随机等待时间
sleep(sleep_time);
P_op(sem_id,SEM_EMPTY); //申请emtpy
P_op(sem_id, SEM_MUTEX); //加锁
shmaddr->buff[shmaddr->tail]=input;//取一个随机数,从尾写入
shmaddr->tail=(shmaddr->tail+1)%BUFFER_LEN;
shmaddr->empty=0;
time_t now;
time(&now);
struct tm *localnow = localtime(&now);
printf("现在时间为:%s", asctime(localnow));
fflush(stdout);
pid_t pid=getpid();
printf("生产者%d将%d放入缓冲区中\n",pid,input);
fflush(stdout);
printf("现在缓冲区中有:");
fflush(stdout);
//由于是生产者,所以缓冲区不会为空
for(int i = shmaddr->head;;)
{
printf("%d ",shmaddr->buff[i]);
fflush(stdout);
i = (i + 1) % BUFFER_LEN;
if (i==shmaddr->tail) {
printf("\n\n");
fflush(stdout);
break;
}
}
// fflush(stdout);
V_op(sem_id, SEM_MUTEX); //解锁
V_op(sem_id,SEM_FULL); //生成full
}
void Consumer_op(int sem_id, buffer *shmaddr){
srand((unsigned)(time(NULL)));
sleep(rand()%10);
P_op(sem_id,SEM_FULL); //申请消费
P_op(sem_id, SEM_MUTEX); //加锁
int output=shmaddr->buff[shmaddr->head];//读取数据
shmaddr->head=(shmaddr->head+1)%BUFFER_LEN;
if (shmaddr->head==shmaddr->tail) {//头尾相等,缓冲为空
shmaddr->empty=1;
}
time_t now;
time(&now);
struct tm *localnow = localtime(&now);
printf("现在时间为:%s", asctime(localnow));
fflush(stdout);
pid_t pid = getpid();
printf("消费者%d从缓冲区中读出%d\n", pid, output);
fflush(stdout);
if (shmaddr->empty) {
printf("此时缓冲区为空\n\n");
fflush(stdout);
}
else
{
printf("现在缓冲区中有:");
fflush(stdout);
for (int i = shmaddr->head;;)
{
printf("%d ", shmaddr->buff[i]);
fflush(stdout);
i = (i + 1) % BUFFER_LEN;
if (i == shmaddr->tail)
{
printf("\n\n");
fflush(stdout);
break;
}
}
}
// fflush(stdout);
V_op(sem_id, SEM_MUTEX); //解锁
V_op(sem_id,SEM_EMPTY); //释放empty
}
int main(int argc, char const *argv[])
{
//创建信号量
int sem_id=semget(SEM_ID,NUM_SEM,IPC_CREAT|0600); //信号量集
if (sem_id==-1) {
printf("Semget Failed!\n");
_Exit(1);
}
union semun sem_val;
sem_val.val=BUFFER_LEN;
semctl(sem_id,SEM_EMPTY,SETVAL,sem_val); //信号量empty初始化为缓冲区长度
sem_val.val=0;
semctl(sem_id,SEM_FULL,SETVAL,sem_val); //信号量full初始化为0
sem_val.val=1;
semctl(sem_id,SEM_MUTEX,SETVAL,sem_val); //信号量mutex初始化为1
//申请共享内存
int shm_id=shmget(SHM_KEY,sizeof(buffer),SHM_MODE|IPC_CREAT);
if (shm_id==-1) {
printf("Shmget Failed!\n");
_Exit(1);
}
//将共享内存附加到进程空间返回虚地址
buffer* shmaddr=shmat(shm_id,0,0);
if (shmaddr==(void*)-1) {
printf("Shmat Failed!\n");
_Exit(1);
}
else {
//为缓冲区结构头尾指针赋值
shmaddr->head=0;
shmaddr->tail=0;
shmaddr->empty=1;
}
for(int pn = 0; pn < NUM_PRODUCER; pn++)
{
pid_t pid=fork();
if (pid==-1) {
printf("Create Producer Failed!\n");
_Exit(1);
}
else if (pid==0){
//将共享区附加到子进程地址空间
shmaddr=shmat(shm_id,0,0);
if (shmaddr==(void*)-1) {
printf("Shmat to Producer Failed!\n");
}
for(int pt = 0; pt < TIME_PRODUCER; pt++)
{
Producer_op(sem_id,shmaddr);
}
//解除共享内存连接
shmdt(shmaddr);
_Exit(0);
}
}
for(int cn = 0; cn < NUM_CONSUMER; cn++)
{
pid_t pid=fork();
if (pid==-1) {
printf("Create Consumer Failed!\n");
_Exit(1);
}
if (pid==0) {
//将共享内存连接到进程空间
shmaddr=shmat(shm_id,0,0);
if (shmaddr==(void*)-1) {
printf("Shmat to Comsumer Failed!\n");
_Exit(1);
}
for(int ct = 0; ct < TIME_CONSUMER; ct++)
{
Consumer_op(sem_id,shmaddr);
}
shmdt(shmaddr);
_Exit(0);
}
}
//等待所有子进程完成
while(wait(0)!=-1);
shmdt(shmaddr);
shmctl(shm_id,IPC_RMID,0);
semctl(sem_id,IPC_RMID,0);
printf("That's end!\n");
return 0;
}
程序运行方式如下:
$ gcc ProducerConsumer.c -o ProducerConsumer
$ ./ProducerConsumer
两个生产者进程和三个消费者进程并行执行,所以结果每次都有所不同,以下只展示某次结果的部分内容
使用命令ipcs
可以查看内存中共享区域使用情况,运行时内存共享如下:
可以看出,由于程序中使用共享内存和信号量,所以内存中存在对应的数据
在程序执行之后,必须要把信号量和共享内存删除,否则会导致遗留,运行结束后内存共享应该如下: