Operating|生产者消费者问题

一、实验目的 1、对于不同系统的平台学会使用调用系统函数,掌握进程创建的方法和步骤。
2、理解不同进程之间的通信机制,掌握生产者消费者问题。
二、实验内容 生产者消费者问题(需要Windows版本和Linux版本)
?一个大小为3的缓冲区,初始为空
?2个生产者
– 随机等待一段时间,往缓冲区添加数据
– 若缓冲区已满,等待消费者取走数据后再添加
– 重复6次
?3个消费者
– 随机等待一段时间,从缓冲区读取数据
– 若缓冲区为空,等待生产者添加数据后再读取
– 重复4次
说明:
1、显示每次添加和读取数据的时间及缓冲区的状态
2、生产者和消费者用进程模拟。
三、实验环境 1、Windows环境下使用Dev-c++和Notepad++;
2、Linux环境是在虚拟机中装Ubuntu15.10;如图1所示
Operating|生产者消费者问题
文章图片

图1
3、在Linux中使用Linux自带的文本编辑器(gedit)进行编辑,在终端进行编译和运行。
四、实验方法与步骤 1、在Windows下实现 (1)、首先理解在理论中学习的生产者消费者问题,然后根据本实验内容的要求,将已知条件转化为编译器能识别的宏或者变量,在此我定义为宏;(如图2所示)
Operating|生产者消费者问题
文章图片

图2
(2)、定义缓冲区的结构,此缓冲区用共享内存实现的。(如图3所示)
Operating|生产者消费者问题
文章图片

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


图4
(4)、编写通用函数:本实验中的随机等待时间函数get_random()、生产者往缓冲区写入数据的函数get_letter()、通过参数传递进程的ID创建当前进程的克隆进程函数StartClone()、创建共享内存并返回创建后的文件句柄的函数MakeSharedFile();
(5)、MakeShareFile()函数思路:首先利用CreateFileMapping()函数创建临时的文件映射对象;如果文件映射成功,利用MapViewOfFile()函数把文件映射的一个视图映射到当前进程的地址空间、并返回文件映射的起始地址;若起始地址有效,将前一步的地址所指向的存储空间清0;在当前进程的地址空间中解除对一个文件映射对象的映射;最终返回临时的文件映射对象句柄。(如图5所示)
Operating|生产者消费者问题
文章图片

图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所示)
Operating|生产者消费者问题
文章图片

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

图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所示)
Operating|生产者消费者问题
文章图片


图8
2、Linux下的实验截图(如图9所示)
Operating|生产者消费者问题
文章图片


Operating|生产者消费者问题
文章图片


图9
六、实验分析与总结 Windows内存管理器使用局部对象来实现共享内存。文件中的字节一对一映射到虚地址空间。读内存的时候实际上是从文件获取数据,修改内存最终将写回到文件。进程间通信、共享数据有很多种方法,文件映射是常用的一种方法。因为mapping对象在系统中是全局的,一个进程创建的Mapping对象可以从另外一个进程打开,映射视图就是进程间共享的内存了。一般在共享的内存数据量比较大时,选择使用文件映射进行共享。使用CreateFileMapping()创建文件映射,OpenFileMapping()打开文件映射。调用 MapViewOfFile()将文件映射到进程的地址空间。
【Operating|生产者消费者问题】共享主存段为进程提供了直接通过主存进行通信的有效手段。使用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 #include #include #include #include //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 = https://www.it610.com/article/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; isemEmpty, 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; isemFull, 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 #include #include #include #include #include //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; istr[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; istr[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); }


    推荐阅读