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)、首先理解在理论中学习的生产者消费者问题,然后根据本实验内容的要求,将已知条件转化为编译器能识别的宏或者变量,在此我定义为宏;(如图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个子进程运行结束,并关闭各个子进程的句柄。
①、利用OpenFileMapping()函数打开文件映射,利用MapViewOfFile()函数把打开的文件映射到此进程的地址空间;
②、如果第①步映射成功,对此进程进行处理,利用OpenSemaphore()函数打开信号量、并将打开的信号量赋值给共享内存中的信号量;每个生产者各工作6次,利用WaitForSingleObject()函数等待一个子进程结束并睡眠一个随机的时间,利用get_letter()函数随机产生一个字母放入环形缓冲区中,并将当前位置记录下来,此处从0开始;取当前的系统时间,并将每一次写入后的环形缓冲区状态输出,并显示进程的操作;利用UnmapViewOfFile()函数将当前进程句柄关闭;
③、如果第①步映射失败,输出提示信息;
④、利用CloseHandle()函数将第①步打开的文件映射对象句柄关闭。
①、利用OpenFileMapping()函数打开文件映射,利用MapViewOfFile()函数把打开的文件映射到此进程的地址空间;
②、如果第①步映射成功,对此进程进行处理,利用OpenSemaphore()函数打开信号量、并将打开的信号量赋值给共享内存中的信号量;每个消费者各工作4次,利用WaitForSingleObject()函数等待一个子进程结束并睡眠一个随机的时间,取出环形缓冲区中的数据,若缓冲区为空(头尾指针指向同一个位置),并将当前位置记录下来,并赋值给缓冲区结构体中的is_empty成员,取当前的系统时间,并将每一次读取后的环形缓冲区状态输出,并显示进程的操作;利用ReleaseSemaphore()函数释放此进程的信号量,并利用UnmapViewOfFile()函数将当前进程句柄关闭;
③、如果第①步映射失败,输出提示信息;
④、利用CloseHandle()函数将第①步打开的文件映射对象句柄关闭。
(7)、各个子进程运行结束后,关闭主进程的句柄。
(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则输出相应提示;
⑤、初始化环形缓冲区中的数据成员。
①、创建新的进程,若所创建的进程标识符小于0,则创建进程失败,并输出相应提示;
②、若此进程为子进程,将共享段附加到申请通信的进程空间;成功时返回共享内存附加到进程空间的虚地址,失败时返回-1,并输出相应提示;
③、2个生产者进程各执行6次,利用上面的P操作,然后睡眠一段随机时间;利用get_letter()函数得到一个随机的字母并写入环形缓冲区,将is_empty设置为0;取当前的系统时间,对每一次写入后、输出当前缓冲区的状态,并显示进程的操作;将第①步创建的信号量执行V操作;
④、将共享段与进程之间解除连接。
①、创建新的进程,若所创建的进程标识符小于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操作。
// 名称: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 = 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;
}
/********************************************/
/*名称: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);
}