1. 程式人生 > >哈工大作業系統實驗4—程序同步

哈工大作業系統實驗4—程序同步

實驗步驟

(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訊號量上面。進而產生飢餓狀態進入死鎖。