加深对进程概念的理解,明确进程与程序的区别。
认识并发执行的本质。
理解和掌握Linux和Windows进程通信系统调用的功能,通过实验和学习,提高对进程痛惜系统调用的编程能力。
掌握使用信号量机制完成进程间同步和互斥的方法。
设置一个大小为的缓冲区,且初始为空。
创建两个生产者和三个消费者。
生产者和消费者都必须用进程模拟。
对于生产者而言,随机等待一段时间后,往缓冲区中存数据,若缓冲区满,则等待消费者取走数据后再存放。
对于消费者而言,随机等待一段时间后,从缓冲区中取数据,若缓冲区空,则等待生产者存放数据后再取走。
每个生产者进程需要执行6次存数据的操作,每个消费者进程需要执行4次取数据的操作。
显示每次向缓冲区添加或取走的数据以及时间,每次操作后都要显示缓冲区中的全部数据,生产者、消费者进程号。
虚拟机: Ubuntu 18.04
Linux操作系统计:Linux-5.9.10
操作系统:Windows10
Win10下的IDE:Visual Studio2019
定义大小为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的合理搭配保证不会有死锁的产生。
(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_VALUE
LPSECURITY_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
(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
(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:
父进程流程
在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.2 Windows下的实验结果
本次实验主要是针对操作系统中的进程间同步和互斥等知识的运用。
在实验中,掌握了Linux和Windows下共享内存的创建、使用和销毁,掌握了信号量的申请、使用记忆销毁,对Linux和Windows下关于共享内存、信号量创建、使用、销毁以及进程创建、销毁的系统调用有了更深刻的认识和理解。
在实验中,掌握了如何通过信号量实现进程间的同步和互斥。
在实验中,要注意释放已申请的共享内存和信号量。在Linux下,需要调用shmctl来销毁共享内存区,调用semctl来销毁信号量;在Windows下,应记得调用CloseHandle()关闭句柄。
Windows下用WaitForSingleObject()来实现信号量的P操作。
Windows下的实验代码
#include
#include
#include
#include
#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);
//sleep
Sleep(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);
//sleep
Sleep(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
#include
#include
#include
#include
#include
#include
#include
#include
#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 300
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);
}
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 semget
if ((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);
}