生产者消费者问题

时间:2022-06-18 20:21:04

一、实验目的

1、对于不同系统的平台学会使用调用系统函数,掌握进程创建的方法和步骤。

2、理解不同进程之间的通信机制,掌握生产者消费者问题。

二、实验内容

       生产者消费者问题(需要Windows版本和Linux版本)

•   一个大小为3的缓冲区,初始为空

•   2个生产者

– 随机等待一段时间,往缓冲区添加数据

– 若缓冲区已满,等待消费者取走数据后再添加

– 重复6次

•   3个消费者

– 随机等待一段时间,从缓冲区读取数据

– 若缓冲区为空,等待生产者添加数据后再读取

– 重复4次

说明:

1、显示每次添加和读取数据的时间及缓冲区的状态

2、生产者和消费者用进程模拟。

三、实验环境

1、Windows环境下使用Dev-c++和Notepad++;

2、Linux环境是在虚拟机中装Ubuntu15.10;如图1所示

生产者消费者问题

图1

3、在Linux中使用Linux自带的文本编辑器(gedit)进行编辑,在终端进行编译和运行。

四、实验方法与步骤

1、在Windows下实现

(1)、首先理解在理论中学习的生产者消费者问题,然后根据本实验内容的要求,将已知条件转化为编译器能识别的宏或者变量,在此我定义为宏;(如图2所示)

生产者消费者问题

图2

(2)、定义缓冲区的结构,此缓冲区用共享内存实现的。(如图3所示)

生产者消费者问题

图3

(3)、生产者与消费者需要用进程模拟,就需要考虑进程之间的通信;同时生产者往缓冲区写入数据、消费者从缓冲区读取数据,不同进程之间同步进行,牵涉到进程通信和共享内存,解决这个问题的方案比较多,此处我才用的是文件映射。(如图4所示) 

生产者消费者问题

图4

(4)、编写通用函数:本实验中的随机等待时间函数get_random()、生产者往缓冲区写入数据的函数get_letter()、通过参数传递进程的ID创建当前进程的克隆进程函数StartClone()、创建共享内存并返回创建后的文件句柄的函数MakeSharedFile();

(5)、MakeShareFile()函数思路:首先利用CreateFileMapping()函数创建临时的文件映射对象;如果文件映射成功,利用MapViewOfFile()函数把文件映射的一个视图映射到当前进程的地址空间、并返回文件映射的起始地址;若起始地址有效,将前一步的地址所指向的存储空间清0;在当前进程的地址空间中解除对一个文件映射对象的映射;最终返回临时的文件映射对象句柄。(如图5所示)

生产者消费者问题

图5

(6)、编写主函数

对于主进程:

①、利用上述所写的MakeSharedFile()函数创建数据文件;

②、利用OpenFileMapping()函数打开文件映射,利用MapViewOfFile()函数把打开的文件映射到主进程的地址空间;

③、如果第②步映射成功,对主进程进行处理,将缓冲区的头尾指针都指向0,然后创建信号量,利用UnmapViewOfFile()函数在当前进程的地址空间中解除对缓冲区文件的映射;

④、如果第②步映射失败,输出提示信息;

⑤、利用CloseHandle()函数关闭文件映射对象句柄;

⑥、利用StartClone()函数创建2个生产者进程和3个消费者进程;

⑦、利用WaitForSingleObject()函数和CloseHandle()函数等待5个子进程运行结束,并关闭各个子进程的句柄。

对于2个生产者进程:

①、利用OpenFileMapping()函数打开文件映射,利用MapViewOfFile()函数把打开的文件映射到此进程的地址空间;

②、如果第①步映射成功,对此进程进行处理,利用OpenSemaphore()函数打开信号量、并将打开的信号量赋值给共享内存中的信号量;每个生产者各工作6次,利用WaitForSingleObject()函数等待一个子进程结束并睡眠一个随机的时间,利用get_letter()函数随机产生一个字母放入环形缓冲区中,并将当前位置记录下来,此处从0开始;取当前的系统时间,并将每一次写入后的环形缓冲区状态输出,并显示进程的操作;利用UnmapViewOfFile()函数将当前进程句柄关闭;

③、如果第①步映射失败,输出提示信息;

④、利用CloseHandle()函数将第①步打开的文件映射对象句柄关闭。

对于3个消费者进程:

①、利用OpenFileMapping()函数打开文件映射,利用MapViewOfFile()函数把打开的文件映射到此进程的地址空间;

②、如果第①步映射成功,对此进程进行处理,利用OpenSemaphore()函数打开信号量、并将打开的信号量赋值给共享内存中的信号量;每个消费者各工作4次,利用WaitForSingleObject()函数等待一个子进程结束并睡眠一个随机的时间,取出环形缓冲区中的数据,若缓冲区为空(头尾指针指向同一个位置),并将当前位置记录下来,并赋值给缓冲区结构体中的is_empty成员,取当前的系统时间,并将每一次读取后的环形缓冲区状态输出,并显示进程的操作;利用ReleaseSemaphore()函数释放此进程的信号量,并利用UnmapViewOfFile()函数将当前进程句柄关闭;

③、如果第①步映射失败,输出提示信息;

④、利用CloseHandle()函数将第①步打开的文件映射对象句柄关闭。

(7)、各个子进程运行结束后,关闭主进程的句柄。

2、在Linux下实现

(1)、首先理解在理论中学习的生产者消费者问题,然后根据本实验内容的要求,将已知条件转化为编译器能识别的宏或者变量,在此我定义为宏;如下所示:

//2个生产者,每个生产者工作6次

#defineNeed_Producer 2

#defineWorks_Producer 6

 

//3个消费者,每个消费者工作4次

#defineNeed_Customer 3

#defineWorks_Customer 4

 

//缓冲区为3

#definebuffer_len 3

#defineMYBUF_LEN (sizeof(struct mybuffer))

 

#define SHM_MODE0600

#defineSEM_ALL_KEY 1234

#defineSEM_EMPTY 0

#define SEM_FULL1

(2)、定义缓冲区的结构(如图6所示)

生产者消费者问题

图6

(3)、编写通用函数:本实验中的随机等待时间函数get_random()、生产者往缓冲区写入数据的函数get_letter()、生产者与消费者的PV操作(如图7所示)

生产者消费者问题

图7

(4)、编写主函数:

对于主函数:

①、创建一个信号量集合,若返回的信号量集合的标识号不小于0,输出提示;

②、对信号量进行控制操作;

③、申请一个共享内存区,成功返回共享内存区的标识,若此标识小于0,则申请共享内存区失败并输出相应提示;

④、将共享段附加到申请通信的进程空间;成功时返回共享内存附加到进程空间的虚地址,失败时返回-1,若返回-1则输出相应提示;

⑤、初始化环形缓冲区中的数据成员。

对于2个生产者进程:

①、创建新的进程,若所创建的进程标识符小于0,则创建进程失败,并输出相应提示;

②、若此进程为子进程,将共享段附加到申请通信的进程空间;成功时返回共享内存附加到进程空间的虚地址,失败时返回-1,并输出相应提示;

③、2个生产者进程各执行6次,利用上面的P操作,然后睡眠一段随机时间;利用get_letter()函数得到一个随机的字母并写入环形缓冲区,将is_empty设置为0;取当前的系统时间,对每一次写入后、输出当前缓冲区的状态,并显示进程的操作;将第①步创建的信号量执行V操作;

④、将共享段与进程之间解除连接。

对于3个消费者进程:

①、创建新的进程,若所创建的进程标识符小于0,则创建进程失败,并输出相应提示;

②、若此进程为子进程,将共享段附加到申请通信的进程空间;成功时返回共享内存附加到进程空间的虚地址,失败时返回-1,并输出相应提示;

③、3个消费者进程各执行4次,利用上面的P操作,然后睡眠一段随机时间;读取环形缓冲区中的数据,如头尾指针指向同一单元,则将当前单元索引赋值给is_empty;取当前的系统时间,对每一次读取后、输出当前缓冲区的状态,并显示进程的操作;将第①步创建的信号量执行V操作;

④、将共享段与进程之间解除连接。

(5)、主进程等待所有子进程运行结束,将共享段与进程之间解除连接。

五、实验结果

1、Windows下的实验截图(如图8所示)

生产者消费者问题

图8 

2、Linux下的实验截图(如图9所示)

生产者消费者问题

生产者消费者问题

图9 

六、实验分析与总结

Windows内存管理器使用局部对象来实现共享内存。文件中的字节一对一映射到虚地址空间。读内存的时候实际上是从文件获取数据,修改内存最终将写回到文件。进程间通信、共享数据有很多种方法,文件映射是常用的一种方法。因为mapping对象在系统中是全局的,一个进程创建的Mapping对象可以从另外一个进程打开,映射视图就是进程间共享的内存了。一般在共享的内存数据量比较大时,选择使用文件映射进行共享。使用CreateFileMapping()创建文件映射,OpenFileMapping()打开文件映射。调用 MapViewOfFile()将文件映射到进程的地址空间。

共享主存段为进程提供了直接通过主存进行通信的有效手段。使用shmget()系统调用实现共享主存段的创建,shmget()返回共享内存区的 ID。对于已经申请到的共享段,进程需把它附加到自己的虚拟空间中才能对其进行读写。使用shmat()将共享内存附加到进程的地址空间。程序退出时调用 shmdt()将共享存储区从本地进程中解除连接,但它不删除共享存储区。shmctl()调用可实现多种共享存储区操作,包括删除和获取信息。在UNIX系统中,一个或多个信号量构成一个信号量集合。使用信号量机制可以实现进程之间的同步和互斥,允许并发进程一次对一组信号量进行相同或不同的操作。每个P、V操作不限于减1或加1,而是可以加减任何整数。在进程终止时,系统可根据需要自动消除所有被进程操作过的信号量的影响。 semget()调用建立一个信号量集合,semctl()调用对信号量执行控制操作,进而实现P、V操作。

七、实验源代码

Windows版本
// 名称:ProducerAndCustomer.h
// 描述:生产者消费者问题
// 作者:野狼
// 日期:2017.3.27

#ifndef __PRODUCERANDCUSTOMER_H
#define __PRODUCERANDCUSTOMER_H
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <windows.h>
#include <unistd.h>

//1个主进程序号记为0
#define mainNum 0
//2个生产者序号(1~2)
#define Producer_Num_from 1
#define Producer_Num_to 2
//3个消费者序号(3~5)
#define Customer_Num_from 3
#define Customer_Num_to 5

//2个生产者,每个生产者工作6次
#define Need_Producer 2
#define Works_Producer 6

//3个消费者,每个消费者工作4次
#define Need_Customer 3
#define Works_Customer 4

//缓冲区为3
#define buffer_len 3

#define SHM_NAME "BUFFER"

//文件映射对象句柄
static HANDLE handleFileMapping;

//子进程句柄数组
static HANDLE subProHandleArray[5+1];

//缓冲区的结构
struct mybuffer
{
	char str[buffer_len];
	int head;
	int tail;
	int is_empty;
};

//共享主存区的结构
struct shareMemory
{
	struct mybuffer data;
	HANDLE semEmpty;
	HANDLE semFull;
};

//得到1000以内的一个随机数
int get_random()
{
	int digit;
	srand((unsigned)(GetCurrentProcessId() + time(NULL)));
	digit = rand() % 1000;
	return digit;
}

//得到A~Z的一个随机字母
char get_letter()
{
	char letter;
	srand((unsigned)(getpid() + time(NULL)));
	letter = (char)((rand() % 26) + 'A');
	return letter;
}

//通过参数传递进程的ID创建当前进程的克隆进程
void StartClone(int subProID)
{
	char szFilename[MAX_PATH];
	char szCmdLine[MAX_PATH];
	STARTUPINFO si;
	PROCESS_INFORMATION pi;
	//获得当前可执行文件名,hModule为NULL返回当前可执行文件的路径名;存放给定模块的路径和文件名;缓冲区大小
	GetModuleFileName(NULL, szFilename, MAX_PATH);
	sprintf(szCmdLine, "\"%s\" %d",szFilename, subProID);
	
	memset(&si, 0, sizeof(STARTUPINFO));
	si.cb = sizeof(STARTUPINFO);
	
	//创建子进程
	BOOL bCreateOK = CreateProcess(szFilename, szCmdLine, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
	//将新创建进程的句柄赋值给进程ID为subProID的子进程
	subProHandleArray[subProID] = pi.hProcess;	
	return;
}

//创建共享内存
HANDLE MakeSharedFile()
{
	//创建临时的文件映射对象(用INVALID_HANDLE_VALUE代替真正的文件句柄)
	HANDLE handleFileMapping = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeof(struct shareMemory), SHM_NAME);
    if (handleFileMapping == NULL || handleFileMapping == INVALID_HANDLE_VALUE)
	{
		printf("创建文件映射失败:%d\n",GetLastError());
		return;
	}
	if (handleFileMapping != INVALID_HANDLE_VALUE)
	{
		//把文件映射对象的一个视图映射到当前进程的地址空间,返回值为文件映射的起始地址
		LPVOID pData = MapViewOfFile(handleFileMapping, FILE_MAP_ALL_ACCESS, 0, 0, 0);//高32位,低32位,整个文件映射
		if (pData == NULL)
		{
			printf("创建文件映射视图失败:%d\n",GetLastError());
			return;
		}
		if (pData != NULL)
		{
			//将指定的存储空间清0
			ZeroMemory(pData, sizeof(struct shareMemory));
		}
		//在当前进程的地址空间中解除对一个文件映射对象的映射
		UnmapViewOfFile(pData);
	}
	return handleFileMapping;
}

#endif
 
 
 
 
// 名称:ProducerAndCustomer.c
// 描述:生产者消费者问题
// 作者:野狼
// 日期:2017.3.27

#include "ProducerAndCustomer.h"

int main(int argc, char * argv[])
{
	int i, j, k;
	int nextIndex = 1;	//下一个要执行的进程序号 
	int curProNum = mainNum;
	char lt;
	SYSTEMTIME time;
	
	//printf("缓冲区大小为:%d.\n",buffer_len);
	//printf("%d个生产者,分别写入%d次.\n",Need_Producer, Works_Producer);
	//printf("%d个消费者,分别读取%d次.\n",Need_Customer, Works_Customer);
	
	//如果有参数,就作为子进程ID
	if (argc > 1)
	{
		sscanf(argv[1], "%d", &curProNum);
	}
	
	//对于主进程
	if (curProNum == mainNum)
	{
		printf("主进程开始运行!\n");
		
		//创建共享内存 
		handleFileMapping = MakeSharedFile();
		//映射视图
		HANDLE hFileMapping = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, SHM_NAME);
		if (hFileMapping == NULL)
		{
			printf("打开文件映射失败:%d\n",GetLastError());
			return;
		}
		LPVOID pFile = MapViewOfFile(hFileMapping, FILE_MAP_ALL_ACCESS, 0, 0, 0);
		if (pFile == NULL)
		{
			printf("文件映射视图失败:%d\n",GetLastError());
			return;
		}
		else
		{
			struct shareMemory *sham = (struct shareMemory*)(pFile);
			sham->data.head = 0;
			sham->data.tail = 0;
			sham->semEmpty = CreateSemaphore(NULL, buffer_len, buffer_len, "SEM_EMPTY");
			sham->semFull = CreateSemaphore(NULL, 0, buffer_len, "SEM_FULL");
			//取消文件映射
			UnmapViewOfFile(pFile);
			pFile = NULL;
		}
		CloseHandle(hFileMapping);
		
		//创建5个子进程
		while (nextIndex <= 5)
		{
			StartClone(nextIndex++);
		}
		
		//等待子进程运行结束
		for (k=1; k<6; k++)
		{
			WaitForSingleObject(subProHandleArray[k], INFINITE);
			CloseHandle(subProHandleArray[k]);
		}
		
		//输出结束信息
		printf("主进程运行结束!\n");
	}
	
	//2个生产者进程
	else if (curProNum >= Producer_Num_from && curProNum <= Producer_Num_to)
	{
		//映射视图
		HANDLE hFileMapping = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, SHM_NAME);
		if (hFileMapping == NULL)
		
		{
			printf("打开文件映射失败:%d\n",GetLastError());
			return;
		}
		LPVOID pFile = MapViewOfFile(hFileMapping, FILE_MAP_ALL_ACCESS, 0, 0, 0);
		if (pFile == NULL)
		{
			printf("文件映射视图失败:%d\n",GetLastError());
			return;
		}
		else
		{
			struct shareMemory *sham = (struct shareMemory*)(pFile);
			sham->semEmpty = OpenSemaphore(SEMAPHORE_ALL_ACCESS,FALSE,"SEM_EMPTY");
			sham->semFull = OpenSemaphore(SEMAPHORE_ALL_ACCESS,FALSE,"SEM_FULL");
			
			for (i=0; i<Works_Producer; i++)
			{
				WaitForSingleObject(sham->semEmpty, INFINITE);
				Sleep(get_random());
				sham->data.str[sham->data.tail] = lt = get_letter();
				sham->data.tail = (sham->data.tail + 1) % buffer_len;
				sham->data.is_empty = 0;

				GetSystemTime(&time);
				printf("%04d:%02d:%02d-%02d:%02d:%02d\t",time.wYear,time.wMonth,time.wDay,time.wHour+8,time.wMinute,time.wSecond);
				
				j = (sham->data.tail-1 >= sham->data.head) ? (sham->data.tail - 1) : (sham->data.tail -1 + buffer_len);
				
				for (j; !(sham->data.is_empty)&&(j>=sham->data.head); j--)
				{
					printf("%c", sham->data.str[j % buffer_len]);
				}
				printf("\t 生产者%d进程 写入 '%c'.\n",curProNum-mainNum, lt);
				ReleaseSemaphore(sham->semFull, 1, NULL);
			}
			UnmapViewOfFile(pFile);
			pFile = NULL;
		}
		CloseHandle(hFileMapping);
	}
	
	//3个消费者进程
	else if (curProNum >= Customer_Num_from && curProNum <= Customer_Num_to)
	{
		HANDLE hFileMapping = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, SHM_NAME);
		if (hFileMapping == NULL)
		{
			printf("打开文件映射失败:%d\n",GetLastError());
			return;
		}
		LPVOID pFile = MapViewOfFile(hFileMapping, FILE_MAP_ALL_ACCESS, 0, 0, 0);
		if (pFile == NULL)
		{
			printf("文件映射视图失败:%d\n",GetLastError());
			return;
		}
		else
		{
			struct shareMemory *sham = (struct shareMemory*)(pFile);
			sham->semEmpty = OpenSemaphore(SEMAPHORE_ALL_ACCESS,FALSE,"SEM_EMPTY");
			sham->semFull = OpenSemaphore(SEMAPHORE_ALL_ACCESS,FALSE,"SEM_FULL");
			
			for (i=0; i<Works_Customer; i++)
			{
				WaitForSingleObject(sham->semFull, INFINITE);
				Sleep(get_random());
				lt = sham->data.str[sham->data.head];
				sham->data.head = (sham->data.head + 1) % buffer_len;
				sham->data.is_empty = (sham->data.head == sham->data.tail);
				GetSystemTime(&time);
				printf("%04d:%02d:%02d-%02d:%02d:%02d\t",time.wYear,time.wMonth,time.wDay,time.wHour+8,time.wMinute,time.wSecond);
				
				j = (sham->data.tail-1 >= sham->data.head) ? (sham->data.tail - 1) : (sham->data.tail -1 + buffer_len);
				
				for (j; !(sham->data.is_empty)&&(j>=sham->data.head); j--)
				{
					printf("%c", sham->data.str[j % buffer_len]);
				}
				printf("\t 消费者%d进程 读取 '%c'. \n",curProNum-Producer_Num_to, lt);
				ReleaseSemaphore(sham->semEmpty, 1, NULL);
			}
			UnmapViewOfFile(pFile);
			pFile = NULL;
		}
		CloseHandle(hFileMapping);
	}
	
	//关闭主进程的句柄
	CloseHandle(handleFileMapping);
	handleFileMapping = INVALID_HANDLE_VALUE;
	return 0;
}

 
Linux版本
/********************************************/
/*名称:ProducerAndCustomer.h
/*描述:生产者与消费者的子函数定义和声明
/*作者:野狼
/*日期:2017-03-26
/********************************************/

#ifndef __PRODUCERANDCUSTOMER_H
#define __PRODUCERANDCUSTOMER_H
#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>

//2个生产者,每个生产者工作6次
#define Need_Producer 2
#define Works_Producer 6

//3个消费者,每个消费者工作4次
#define Need_Customer 3
#define Works_Customer 4

//缓冲区为3
#define buffer_len 3

#define MYBUF_LEN (sizeof(struct mybuffer))

#define SHM_MODE 0600//可读可写
#define SEM_ALL_KEY 1234
#define SEM_EMPTY 0
#define SEM_FULL 1

//缓冲区的结构
struct mybuffer
{
	char str[buffer_len];
	int head;
	int tail;
	int is_empty;
};

//得到10以内的一个随机数
int get_random()
{
	int digit;
	srand((unsigned)(getpid() + time(NULL)));
	digit = rand() % 10;
	return digit;
}

//得到A~Z的一个随机字母
char get_letter()
{
	char letter;
	srand((unsigned)(getpid() + time(NULL)));
	letter = (char)((rand() % 26) + 'A');
	return letter;
}

//P操作
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);//一次需进行的操作的数组sembuf中的元素数为1
}

//V操作
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);
}

#endif
/********************************************/
/*名称:ProducerAndCustomer.c
/*描述:生产者与消费者的主函数
/*作者:野狼
/*日期:2017-03-28
/********************************************/

#include "ProducerAndCustomer.h"

int main(int argc, char *argv[])
{
	int i, j;
	int shm_id, sem_id;
	int num_Producer = 0, num_Customer = 0;
	struct mybuffer *shmptr;
	char lt;

	time_t now;
	struct tm *timenow;

	pid_t pid_p, pid_c;

	//创建一个信号量集合(信号量数为2),返回值为信号量集合的标识号(关键字,信号量数,创建或打开的标志)
	sem_id = semget(SEM_ALL_KEY,2,IPC_CREAT|0660);
	if (sem_id >= 0)
	{
		printf("主进程开始运行! \n");
	}
	//对信号量执行控制操作(信号量集合标识,信号量的索引,要执行的操作命令,设置或返回信号量的参数)
	semctl(sem_id, SEM_EMPTY, SETVAL, buffer_len);
	semctl(sem_id, SEM_FULL, SETVAL, 0);

	//申请一个共享内存区,成功返回为共享内存区的标识
	shm_id = shmget(IPC_PRIVATE, MYBUF_LEN, SHM_MODE);
	if (shm_id < 0)
	{
		printf("申请共享内存区失败!\n");
		exit(1);
	}

	//将共享段附加到申请通信的进程空间;成功时返回共享内存附加到进程空间的虚地址,失败时返回-1
	shmptr = shmat(shm_id, 0, 0);
	if (shmptr == (void *)-1)
	{
		printf("将共享段附加到申请通信的进程空间失败!\n");
		exit(1);
	}

	shmptr->head = 0;
	shmptr->tail = 0;
	shmptr->is_empty = 1;
	
	//2个生产者进程
	while ((num_Producer++) < Need_Producer)
	{
		pid_p = fork();
		if (pid_p < 0)
		{
			printf("创建进程失败!\n");
			exit(1);
		}
		//如果是生产者子进程,开始创建生产者
		if (pid_p == 0)
		{
			//将共享段附加到申请通信的进程空间;成功时返回共享内存附加到进程空间的虚地址,失败时返回-1
			shmptr = shmat(shm_id, 0, 0);
			if (shmptr == (void *)-1)
			{
				printf("将共享段附加到申请通信的进程空间失败!\n");
				exit(1);
			}
		 	for (i=0; i<Works_Producer; i++)
			{
				P(sem_id, SEM_EMPTY);
				sleep(get_random());
				shmptr->str[shmptr->tail] = lt = get_letter();
				shmptr->tail = (shmptr->tail + 1) % buffer_len;
				shmptr->is_empty = 0;
				time(&now);
				timenow = localtime(&now);
				now = time(NULL);
				printf("%s ",asctime(timenow));
				
				j = (shmptr->tail-1 >= shmptr->head) ? (shmptr->tail-1) : (shmptr->tail-1+buffer_len);
				for (j; !(shmptr->is_empty) && j >= shmptr->head; j--)
				{
					printf("%c", shmptr->str[j%buffer_len]);
				}
				printf("\t 生产者 %d  放入 '%c'. \n",num_Producer,lt);
				fflush(stdout);
				V(sem_id,SEM_FULL);
			}
			//将共享段与进程之间解除连接
			shmdt(shmptr);
			exit(0);
		}
	}
	//3个消费者进程
	while ((num_Customer++) < Need_Customer)
	{
		pid_c = fork();
		if (pid_c < 0)
		{
			printf("创建进程失败!\n");
			exit(1);
		}
		//如果是消费者子进程,开始创建消费者
		if (pid_c == 0)
		{
			//将共享段附加到申请通信的进程空间;成功时返回共享内存附加到进程空间的虚地址,失败时返回-1
			shmptr = shmat(shm_id, 0, 0);
			if (shmptr == (void *)-1)
			{
				printf("将共享段附加到申请通信的进程空间失败!\n");
				exit(1);
			}
		 	for (i=0; i<Works_Customer; i++)
			{
				P(sem_id, SEM_FULL);
				sleep(get_random());
				lt = shmptr->str[shmptr->head];
				shmptr->head = (shmptr->head + 1) % buffer_len;
				shmptr->is_empty = (shmptr->head == shmptr->tail);

				time(&now);
				timenow = localtime(&now);
				now = time(NULL);
				printf("%s ",asctime(timenow));

				j = (shmptr->tail-1 >= shmptr->head) ? (shmptr->tail-1) : (shmptr->tail-1+buffer_len);
				for (j; !(shmptr->is_empty) && j >= shmptr->head; j--)
				{
					printf("%c", shmptr->str[j%buffer_len]);
				}
				printf("\t 消费者 %d  取出 '%c'. \n",num_Customer,lt);
				fflush(stdout);
				V(sem_id,SEM_EMPTY);
			}
			//将共享段与进程之间解除连接
			shmdt(shmptr);
			exit(0);
		}
	}

	//主进程最后退出
	while (wait(0) != -1);
	//将共享段与进程之间解除连接
	shmdt(shmptr);
	//对共享内存区执行控制操作
	shmctl(shm_id,IPC_RMID,0);//当cmd为IPC_RMID时,删除该共享段
	shmctl(sem_id,IPC_RMID,0);
	printf("主进程运行结束!\n");
	fflush(stdout);
	exit(0);
}