一、實驗目的
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操作。
七、實驗源代碼
// 名稱: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;
}
/********************************************/
/*名稱: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);
}