哈工大作業系統實驗4—程序同步
阿新 • • 發佈:2019-02-02
實驗步驟
(1)在ubuntu下,用系統提供的sem_open()、sem_close()、sem_wait()和sem_post()等訊號量相關的系統呼叫編寫pc.c程式。
(2)在ubuntu上編譯並執行pc.c,檢查執行結果。
終端也是臨界資源
用printf()向終端輸出資訊是很自然的事情,但當多個程序同時輸出時,終端也成為了一個臨界資源,需要做好互斥保護,否則輸出的資訊可能錯亂。
另外,printf()之後,資訊只是儲存在輸出緩衝區內,還沒有真正送到終端上,這也可能造成輸出資訊時序不一致。用fflush(stdout)可以確保資料送到終端。
本次實驗相較於往屆已極大地簡化,畢竟時間有限,所以不用在Linux0.11下實現訊號量(裡面沒有sem_open()等系統呼叫,需要自己新增),僅需要在Ubantu下執行生產者,消費者的相關程式。
首先介紹一下所需函式
int fseek(FILE *stream, long offset, int fromwhere); 函式設定檔案指標stream的位置。 如果執行成功,stream將指向以fromwhere為基準,偏移offset(指標偏移量)個位元組的位置,函式返回0。 如果執行失敗(比如offset超過檔案自身大小),則不改變stream指向的位置,函式返回一個非0值。 size_t fread(void *buffer,size_t size,size_t count, FILE *stream ); buffer 是讀取的資料存放的記憶體的指標 size 是每次讀取的位元組數 count 是讀取次數 stream 是要讀取的檔案的指標 從一個檔案流中讀資料,最多讀取count個元素,每個元素size位元組,如果呼叫成功返回實際讀取到的元素個數,如果不成功或讀到檔案末尾返回 0。 size_t fwrite(const void* buffer, size_t size, size_t count, FILE* stream); (1)buffer:是一個指標,對fwrite來說,是要獲取資料的地址; (2)size:要寫入內容的單位元組數; (3)count:要進行寫入size位元組的資料項的個數; (4)stream:目標檔案指標; (5)返回實際寫入的資料項個數count。
演算法的思想是:建立一個檔案緩衝區,0~9位儲存生產出的資料,第10位儲存當前讀到的位置;因為緩衝區是覆蓋寫入,例如當消費者消費到第6位,而生產者此時可以生產覆蓋前5位,但消費者消費是順序消費的,必須要讀到緩衝區尾才可以再從頭讀。
這就有必要儲存當前讀取的位置(因為程序可能被中斷,下次再來就不知道讀到哪裡了),所以下面要執行兩次,第一次讀出當前所讀位置,再根據此位置計算位偏移。
fseek( fp, 10*sizeof(int), SEEK_SET ); fread( &Outpos, sizeof(int), 1, fp); fseek( fp, Outpos*sizeof(int), SEEK_SET ); fread( &costnum, sizeof(int), 1, fp);
pc.c
#define __LIBRARY__
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <semaphore.h>
#define Total 500
#define PNUM 5
#define BUFFERSIZE 10
/*
*/
int main()
{
int i, j, k;
int costnum;
int Outpos = 0;
int Inpos = 0;
sem_t *empty, *full, *mutex;
FILE *fp = NULL;
empty =(sem_t *)sem_open("empty", O_CREAT, 0064, 10);
full = (sem_t *)sem_open("full", O_CREAT, 0064, 0);
mutex = (sem_t *)sem_open("mutex",O_CREAT, 0064, 1);
fp=fopen("FileBuffer.txt", "wb+");
fseek( fp, 10*sizeof(int) , SEEK_SET );
fwrite( &Outpos, sizeof(int), 1, fp);
fflush(fp);
if( !fork() )
{
for( i = 0 ; i < Total; i++)
{
sem_wait(empty);
sem_wait(mutex);
fseek( fp, Inpos * sizeof(int), SEEK_SET );
fwrite( &i, sizeof(int), 1, fp );
fflush(fp);
Inpos = ( Inpos + 1 ) % BUFFERSIZE;
sem_post(mutex);
sem_post(full);
}
exit(0);
}
for( k = 0; k < PNUM ; k++ )
{
if( !fork() )
{
for( j = 0; j < Total/PNUM; j++ )
{
sem_wait(full);
sem_wait(mutex);
fflush(stdout);
fseek( fp, 10*sizeof(int), SEEK_SET );
fread( &Outpos, sizeof(int), 1, fp);
fseek( fp, Outpos*sizeof(int), SEEK_SET );
fread( &costnum, sizeof(int), 1, fp);
printf("%d: %d\n",getpid(),costnum);
fflush(stdout);
Outpos = (Outpos + 1) % BUFFERSIZE;
fseek( fp, 10*sizeof(int), SEEK_SET );
fwrite( &Outpos, sizeof(int),1, fp );
fflush(fp);
sem_post(mutex);
sem_post(empty);
}
exit(0);
}
}
wait(NULL);
wait(NULL);
wait(NULL);
wait(NULL);
wait(NULL);
sem_unlink("empty");
sem_unlink("full");
sem_unlink("mutex");
fclose(fp);
return 0;
}
附report
1.在pc.c中去掉所有與訊號量有關的程式碼,再執行程式,執行效果有變化嗎?為什麼會這樣?
答:在去掉與訊號量有關的程式碼後,執行結果Customer的消費資料沒有按遞增的順序輸出,且fread()函式將產生錯誤。
因為沒有訊號量P(S)控制,導致生產者可能在緩衝區滿後繼續生產,導致沒有被消費的資料被覆蓋,使得消費者消費的資料不是遞增序列。
同時,沒有訊號量V(S)控制,導致消費者可能在讀取所有資料後仍然繼續讀取,導致讀取的資料無效。
沒有mutex訊號量控制導致出現多程序併發訪問緩衝區,導致出現fread()錯誤。
2.這樣可行嗎?如果可行,那麼它和標準解法在執行效果上會有什麼不同?如果不可行,那麼它有什麼問題使它不可行?
答:這樣不可行。程式在某種情況下會出現死鎖狀態。
例如:當mutex = 1,並且生產者要進入生產一個數據,假設此時empty = 0,mutex = 0,P(empty)後小於0,生產者程序進入等待在訊號量empty的等待佇列上面呼叫schedule(),
可是此時並未解鎖,即mutex.value值仍然為0。它們都等待在訊號量mutex上面。同理,消費者程序也是如此,若mutex.value = 1,full.value = 0,
在執行完P(mutex)P(full)之後,mutex = 0,並且將消費者程序放入等待在訊號量full的等待佇列上面,而此時也並未釋放mutex,
因此消費者和生產者程序都等待在mutex訊號量上面。進而產生飢餓狀態進入死鎖。