HIT OS 2020 Lab4

實驗4 信號量的實現與應用

4.1 實驗目的

  • 加深對進程同步與互斥概念的認識;
  • 掌握信號量的使用,並應用它解決生產者——消費者問題;
  • 掌握信號量的實現原理。

4.2 實驗內容

本次實驗的基本內容是:linux

  • 在 Ubuntu 下編寫程序,用信號量解決生產者——消費者問題;
  • 在 0.11 中實現信號量,用生產者—消費者程序檢驗之。
  • 用信號量解決生產者—消費者問題
    在 Ubuntu 上編寫應用程序 pc.c ,解決經典的生產者—消費者問題,完成下面的功能:
  • 創建一個生產者進程, N 個消費者進程( N>1 );
  • 用文件創建一個共享緩衝區;
  • 生產者進程依次向緩衝區寫入整數 0,1,2,...,M, M>=500 ;
  • 消費者進程從緩衝區讀數,每次讀一個,並將讀出的數字從緩衝區刪除,而後將本進程 ID 和數字輸出到標準輸出;
  • 緩衝區同時最多隻能保存 10 個數。
    一種可能的輸出效果是:
10: 0
10: 1
10: 2
10: 3
10: 4
11: 5
11: 6
12: 7
10: 8
12: 9
12: 10
12: 11
12: 12
……
11: 498
11: 499

其中 ID 的順序會有較大變化,但冒號後的數字必定是從 0 開始遞增長一的。
pc.c 中將會用到 sem_open() 、 sem_close() 、 sem_wait() 和 sem_post() 等信號量相關的系統調用,請查閱相關文檔。
《UNIX環境高級編程》是一本關於 Unix/Linux 系統級編程的至關經典的教程。 電子版可在網站上下載,後續實驗也用獲得。若是你對 POSIX 編程感興趣,建議買一本常備手邊。編程

4.2.1 實現信號量

Linux 在 0.11 版尚未實現信號量,Linus 把這件富有挑戰的工做留給了你。 若是能實現一套山寨版的徹底符合 POSIX 規範的信號量,無疑是頗有成就感的。但時間暫時不容許咱們這麼作,因此先弄一套縮水版的類 POSIX 信號量,它的函數原型和標準並不徹底相同,並且只包含以下系統調用:緩存

sem_t *sem_open(const char *name, unsigned int value);
int sem_wait(sem_t *sem);
int sem_post(sem_t *sem);
int sem_unlink(const char *name);

sem_t是信號量類型,根據實現的須要自定義。
sem_open()的功能是建立一個信號量,或打開一個已經存在的信號量。
name是信號量的名字。不一樣的進程能夠經過提供一樣的name而共享同一個信號量。若是該信號量不存在,就建立新的名爲name的信號量;若是存在,就打開已經存在的名爲name的信號量。value是信號量的初值,僅當新建信號量時,此參數纔有效,其他狀況下它被忽略。當成功時,返回值是該信號量的惟一標識(好比,在內核的地址、ID等),由另兩個系統調用使用。如失敗,返回值是NULLsem_wait()就是信號量的P原子操做。若是繼續運行的條件不知足,則令調用進程等待在信號量sem上。返回0表示成功,返回-1表示失敗。
sem_post()就是信號量的V原子操做。若是有等待sem的進程,它會喚醒其中的一個。返回0表示成功,返回-1表示失敗。
sem_unlink()的功能是刪除名爲name的信號量。返回0表示成功,返回-1表示失敗。
kernel目錄下新建sem.c文件實現如上功能。而後將pc.c從Ubuntu移植到0.11下,測試本身實現的信號量。函數

4.3 實驗問題

4.3.1 在 pc.c 中去掉全部與信號量有關的代碼,再運行程序,執行效果有變化嗎?爲何會這樣?

  • 會有變化
  • 可能形成以下的狀況
    - 亂序輸出:可能緩衝區已經滿了,可是生產者仍然在持續寫入其餘數據,致使以前的數據被覆蓋,從而致使無序輸出;亦或者可能緩衝區已經爲空,可是此時消費者仍然在讀取數據,此時也會致使亂序輸出,
    - 程序崩潰:可能出現多個進程由於沒有MUTEX信號而出現越界訪問代碼的狀況,這樣會使得程序崩潰。

4.3.2 實驗的設計者在第一次編寫生產者——消費者程序的時候,是這麼作的,這樣可行嗎?若是可行,那麼它和標準解法在執行效果上會有什麼不一樣?若是不可行,那麼它有什麼問題使它不可行?

Producer()
{
    P(Mutex);  //互斥信號量
    // 生產一個產品item;
    P(Empty);  //空閒緩存資源
    // 將item放到空閒緩存中;
    V(Full);  //產品資源
    V(Mutex);
}

Consumer()
{
    P(Mutex);
    P(Full);
    // 從緩存區取出一個賦值給item;
    V(Empty);
    // 消費產品item;
    V(Mutex);
}
  • 不能夠,這樣會形成死鎖的狀況出現。當Empty信號爲0,而Mutex爲1的時候,各個進程就會相互等待,從而進入死鎖狀態。

4.4 實驗過程

4.4.1 總體說明

在實驗過程當中,咱們實現的多個函數都要以系統調用的形式在Linux0.11中進行使用,所以,須要根據實驗2的內容對makefile、unistd.h、system_call.s等文件進行修改,修改的過程不在此贅述,能夠參考另外一篇講實驗2的博客。post

4.4.2 sem_open()函數

sem_t *sys_sem_open(const char *name,unsigned int value) //sem_t的定義會在後邊給出,name是信號量的名稱,value信號量對應的初值
{
    char kernelname[100]; 
    int isExist = 0;
    int i = 0;
    int name_cnt = 0;
    while(get_fs_byte(name + name_cnt) != '\0')//進行信號量名稱長度的讀取
    {
        name_cnt++;
    }
    if(name_cnt > SEM_NAME_LEN)//信號量名稱需小於最大長度
    {
        return NULL;
    }
    for(i = 0;i < name_cnt;i++)//將信號量的名稱讀入
    {
        kernelname[i] = get_fs_byte(name + i);
    }
    int name_len = strlen(kernelname);
    int sem_name_len =0;
    sem_t *p = NULL;
    for(i = 0;i < cnt;i++)//判斷是否當前信號已經存在
    {
        sem_name_len = strlen(semtable[i].name);
        if(sem_name_len == name_len)
        {
                if( !strcmp(kernelname,semtable[i].name) )
                {
                    isExist = 1;
                    break;
                }
        }
    }
    if(isExist == 1)//若是已經存在,那麼返回已經存在的信號
    {
        p = (sem_t*)(&semtable[i]);
    }
    else//不然新建一個信號
    {
        i = 0;
        for(i = 0;i < name_len;i++)
        {
            semtable[cnt].name[i] = kernelname[i];
        }
        semtable[cnt].value = value;
        p = (sem_t*)(&semtable[cnt]);
        cnt++;//而且將這個信號放入信號表中
     }
    return p;
}

這個函數實現的是打開信號量的操做。測試

4.4.3 sem_wait()函數

int sys_sem_wait(sem_t *sem)
{
    cli();//關中斷
    while(sem->value <= 0) //進程等待直到信號量的值大於0
    {        
        sleep_on(&(sem->queue));   
    }
    sem->value--;
    sti();//開啓中斷
    return 0;
}

這個函數實現了進程的等待過程。網站

4.4.4 sem_post()函數

int sys_sem_post(sem_t *sem)
{
    cli();
    sem->value++;
    if((sem->value) <= 1)//喚醒在信號量上等待的進程
    {
        wake_up(&(sem->queue));
    }    
    sti();
    return 0;
}

這個函數用於喚醒對應的進程。設計

4.4.5 sem_wait()函數

int sys_sem_unlink(const char *name)
{
    char kernelname[100];
    int isExist = 0;
    int i = 0;
    int name_cnt = 0;
    while( get_fs_byte(name + name_cnt) != '\0')
    {
            name_cnt++;
    }
    if(name_cnt > SEM_NAME_LEN)
    {
            return NULL;
    }
    for(i=0;i<name_cnt;i++)
    {
            kernelname[i] = get_fs_byte(name + i);
    }
    int name_len = strlen(name);
    int sem_name_len =0;
    for(i = 0;i < cnt;i++)
    {
        sem_name_len = strlen(semtable[i].name);
        if(sem_name_len == name_len)
        {
                if( !strcmp(kernelname,semtable[i].name))
                {
                        isExist = 1;
                        break;
                }
        }
    }
    if(isExist == 1)
    {
        int tmp = 0;
        for(tmp = i;tmp <= cnt;tmp++)
        {
            semtable[tmp] = semtable[tmp + 1];
        }
        cnt = cnt - 1;
        return 0;
    }
    else
        return -1;
}

這個函數用於刪除名爲name的信號量。指針

4.4.6 sem.h

這個文件定義了sem_t這個數據類型。code

#ifndef _SEM_H
#define _SEM_H

#include <linux/sched.h>

#define SEM_NAME_LEN 50
#define SEMTABLE_LEN 20
typedef struct sem_t{
    char name[SEM_NAME_LEN];//信號量名稱
    unsigned int value;//初值
    struct task_struct *queue;//等待信號量的進程指針
}sem_t;
extern sem_t semtable[SEMTABLE_LEN];
sem_t *sem_open(const char name, unsigned int value);
int sem_wait(sem_t *sem);
int sem_post(sem_t *sem);
int sem_unlink(const char *name);

#endif

4.4.7 pc.c

#define __LIBRARY__
#include <unistd.h>
#include <stdio.h>
#include <linux/sem.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>

_syscall2(sem_t*, sem_open, const char*, name, unsigned int, value)
_syscall1(int, sem_wait, sem_t *, sem)
_syscall1(int, sem_post, sem_t *, sem)
_syscall1(int, sem_unlink, const char *, name)

#define NUMBUFFER 550 /*打出數字的總數*/
#define NUMPRO 5 /*消費者進程個數*/
#define MAXSIZE 10 /*緩衝區大小*/

sem_t *empty, *full, *mutex;

int main()
{
    int i, j, k;
    FILE *fp = NULL;
    int buf_out = 0;/*從緩衝區讀取的位置*/
    int buf_in = 0;/*寫入緩衝區的位置*/
    empty = (sem_t *)sem_open("EMPTY", MAXSIZE);/*打開對應的信號量*/
    full = (sem_t *)sem_open("FULL", 0);
    mutex = (sem_t *)sem_open("MUTEX", 1);
    fp = fopen("/var/buffer.dat", "wb+"); /*存在文件中。*/
    fseek(fp, MAXSIZE * sizeof(int), SEEK_SET);
    fwrite(&buf_out, 1, sizeof(buf_out), fp);
    fflush(fp);
    if(!fork())
    {
        for(i = 0;i < NUMBUFFER;i ++)
        {
            sem_wait(empty);
            sem_wait(mutex);
            fseek(fp, buf_in * sizeof(int), SEEK_SET);
            fwrite(&i, 1, sizeof(i), fp);
            fflush(fp);
            buf_in = (buf_in + 1) % MAXSIZE;
            sem_post(mutex);
            sem_post(full);
        }
        return 0;
    }

    for(i = 0;i < NUMPRO; i++)
    {
        if(!fork())
        {
            for(j = 0;j < NUMBUFFER / NUMPRO; j++)
            {
                int cost;
                sem_wait(full);
                sem_wait(mutex);
                fflush(stdout);
                fseek(fp, MAXSIZE * sizeof(int), SEEK_SET);
                fread(&buf_out, sizeof(int), 1, fp);
                fseek(fp, buf_out * sizeof(int), SEEK_SET);
                fread(&cost, sizeof(int), 1, fp);
                printf("%d:\t%d\n", getpid(), cost);
                fflush(stdout);
                buf_out = (buf_out + 1) % MAXSIZE;
                fseek(fp, MAXSIZE * sizeof(int), SEEK_SET);
                fwrite(&buf_out, 1, sizeof(buf_out),fp);
                fflush(fp);
                sem_post(mutex);
                sem_post(empty);
            }
            return 0;
        }
    }

    wait(NULL);
    sem_unlink("EMPTY");
    sem_unlink("FULL");
    sem_unlink("MUTEX");
    fclose(fp);
    return 0;
}

4.4.8 實驗結果

....
17:	543
17:	544
17:	545
17:	546
17:	547
17:	548
17:	549

最終能夠輸出550個數字。

相關文章
相關標籤/搜索