1. 程式人生 > >作業系統實驗報告-訊號量的實現和應用

作業系統實驗報告-訊號量的實現和應用

實驗內容

在Linux-0.11中實現訊號量,並編寫生產者-消費者程式進行檢驗。

實驗步驟

新增訊號量結構體與相應的系統呼叫函式

在include/unistd.h中新增程式碼:

複製程式碼
#define SEM_NAME_LEN 32                /* 訊號量名稱最大長度 */
typedef struct sem_t{
    char name[SEM_NAME_LEN];        /* 訊號量名稱 */
    unsigned int value;                /* 訊號量的值 */
    struct task_struct *s_wait;        /*
等待訊號量的程序的pcb指標 */ struct sem_t *next; /* 用於連線訊號量形成連結串列 */ }sem_t; sem_t *sem_open(const char *name, unsigned int value); /* 開啟或新建訊號量 *// int sem_wait(sem_t *sem); /* 等待訊號量至其值大於0,將其值減1;對應P原語 */ int sem_post(sem_t *sem); /* 喚醒在訊號量上等待的程序,將訊號量值加1;對應V原語 */ int sem_unlink(const
char *name); /* 銷燬訊號量 */
複製程式碼

接下來將上面定義的4個函式新增為系統呼叫(步驟同作業系統實驗報告-系統呼叫),新增kernel/sem.c實現它們:

複製程式碼
#include <linux/kernel.h>
#include <asm/system.h>
#include <linux/sched.h>
#include <asm/segment.h>
#include <unistd.h>

sem_t *sem_head = &((sem_t *){"\0", 0, NULL, NULL});    /* 連結串列頭結點,方便統一操作 
*/ /* 將使用者態中的ustr複製到核心態的kstr */ static inline int str_u2k(const char *ustr, char *kstr, unsigned int length) { char c; int i; for(i=0; (c=get_fs_byte(ustr++))!='\0' && i<length; i++) *(kstr+i)=c; *(kstr+i)='\0'; return i; } sem_t *sys_sem_open(const char *name, unsigned int value) { sem_t *sem_cur, *sem_pre; char pname[SEM_NAME_LEN]; /* 將使用者態引數name指向的訊號量名稱拷貝到核心態指標pname中 */ str_u2k(name, pname, SEM_NAME_LEN); /* 遍歷連結串列,檢驗訊號量是否已存在 */ for(sem_pre=sem_head, sem_cur=sem_head->next; sem_cur && strcmp(pname, sem_cur->name); sem_pre=sem_cur, sem_cur=sem_cur->next); /* sem_cur為空,表明訊號量不存在,分配一塊記憶體新建一個訊號量 */ if(!sem_cur) { printk("semaphore %s no found. created a new one. \n", pname); sem_cur = (sem_t *)malloc(sizeof(sem_t)); strcpy(sem_cur->name, pname); sem_cur->value = value; sem_cur->next = NULL; sem_pre->next = sem_cur; } printk("pid %d opens semaphore %s(value %u) OK. \n", current->pid, pname, sem_cur->value); return sem_cur; } int sys_sem_wait(sem_t *sem) { cli(); /* 關閉中斷 */ /* 程序等待直到訊號量的值大於0 */ while(sem->value<=0) sleep_on(&(sem->s_wait)); sem->value--; sti(); /* 開啟中斷 */ return 0; } int sys_sem_post(sem_t *sem) { sem->value++; /* 喚醒在訊號量上等待的程序 */ if(sem->s_wait) { wake_up(&(sem->s_wait)); return 0; } return -1; } int sys_sem_unlink(const char *name) { sem_t *sem_cur, *sem_pre; char pname[SEM_NAME_LEN]; int i; str_u2k(name, pname, SEM_NAME_LEN); for(sem_pre=sem_head, sem_cur=sem_head->next; sem_cur && strcmp(pname, sem_cur->name); sem_pre=sem_cur, sem_cur=sem_cur->next); /* 找不到則返回錯誤程式碼-1 */ if(!sem_cur) return -1; /* 找到了將其從連結串列中移除,並釋放空間 */ sem_pre->next = sem_cur->next; free(sem_cur); printk("unlink semaphore %s OK. \n", pname); return 0; }
複製程式碼

其中sys_sem_wait()和sys_sem_post()參考自kernel/blk_drv/ll_rw_blk.c:

複製程式碼
static inline void lock_buffer(struct buffer_head * bh)
{
    cli();
    while (bh->b_lock)
        sleep_on(&bh->b_wait);
    bh->b_lock=1;
    sti();
}

static inline void unlock_buffer(struct buffer_head * bh)
{
    if (!bh->b_lock)
        printk("ll_rw_block.c: buffer not locked\n\r");
    bh->b_lock = 0;
    wake_up(&bh->b_wait);
}
複製程式碼

其中的sleep_on()為在kernel/sched.c中實現的函式:

複製程式碼
void sleep_on(struct task_struct **p)
{
    /* 引數p指向原等待程序pcb */

    struct task_struct *tmp;

    if (!p)
        return;
    if (current == &(init_task.task))
        panic("task[0] trying to sleep");
    tmp = *p;        /* 本地指標tmp指向原等待程序 */
    *p = current;    /* 引數p指向當前程序,使其成為下一次呼叫此方法的等待程序 */
    current->state = TASK_UNINTERRUPTIBLE;    /* 休眠程序 */
    schedule();        /* 執行排程 */
    /* 由於是不可中斷睡眠,不會自動就緒,只能通過呼叫wake_up()來喚醒。
     * 如果排程後又回到這裡,說明是訊號量的值已經大於0了,於是就呼叫了wake_up()將此程序喚醒
     */
    if (tmp)    /* 將原等待程序也喚醒 */
        tmp->state=0;
    /* 等到原等待程序拿到CPU進入執行狀態,
     * 它也會將它以前呼叫此函式時產生的另一個本地指標tmp指向的等待程序喚醒。
     * 就這樣遞迴喚醒,就好像遍歷喚醒了一條等待的程序佇列
     */
}
複製程式碼

下面是《Linux核心完全註釋》裡面的一張圖,形象地描述了此函式中的指標變化:

wake_up()也是在kernel/sched.c中實現,是一個簡單的喚醒判斷:

複製程式碼
void wake_up(struct task_struct **p)
{
    if (p && *p) {
        (**p).state=0;
        *p=NULL;
    }
}
複製程式碼

編寫生產者-消費者檢驗程式

生產者-消費者問題

生產者-消費者問題是互斥的一個經典例子,下面是實驗指導書給出的功能要求:

  1. 建立一個生產者程序,N個消費者程序(N>1);
  2. 用檔案建立一個共享緩衝區;
  3. 生產者程序依次向緩衝區寫入整數0,1,2,...,M,M>0;
  4. 消費者程序從緩衝區讀數,每次讀一個,並將讀出的數字從緩衝區刪除,然後將本程序ID和數字輸出到標準輸出;
  5. 緩衝區同時最多隻能儲存10個數。

為了增加可讀性,我以句子的形式輸出資訊。

生產者-消費者問題的解決演算法的虛擬碼描述:

複製程式碼
Producer()
{
    生產一個產品item;
    P(Empty);
    P(Mutex);
    將item放到空閒快取中;
    V(Mutex);
    V(Full);
}

Consumer()
{
    P(Full);  
    P(Mutex);  
    從快取區取出一個賦值給item;
    V(Mutex);
    V(Empty);
    消費產品item;
} 
複製程式碼

新建pc.c檔案,編寫測試程式:

複製程式碼
#define __LIBRARY__
#include <unistd.h>
#include <fcntl.h>
#include <stdio.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)

const char *FILENAME = "/usr/root/buffer_file";    /* 消費生產的產品存放的緩衝檔案的路徑 */
const int NR_CONSUMERS = 5;                        /* 消費者的數量 */
const int NR_ITEMS = 50;                        /* 產品的最大量 */
const int BUFFER_SIZE = 10;                        /* 緩衝區大小,表示可同時存在的產品數量 */
sem_t *metux, *full, *empty;                    /* 3個訊號量 */
unsigned int item_pro, item_used;                /* 剛生產的產品號;剛消費的產品號 */
int fi, fo;                                        /* 供生產者寫入或消費者讀取的緩衝檔案的控制代碼 */


int main(int argc, char *argv[])
{
    char *filename;
    int pid;
    int i;

    filename = argc > 1 ? argv[1] : FILENAME;
    /* O_TRUNC 表示:當檔案以只讀或只寫開啟時,若檔案存在,則將其長度截為0(即清空檔案)
     * 0222 和 0444 分別表示檔案只寫和只讀(前面的0是八進位制標識)
     */
    fi = open(filename, O_CREAT| O_TRUNC| O_WRONLY, 0222);    /* 以只寫方式開啟檔案給生產者寫入產品編號 */
    fo = open(filename, O_TRUNC| O_RDONLY, 0444);            /* 以只讀方式開啟檔案給消費者讀出產品編號 */

    metux = sem_open("METUX", 1);    /* 互斥訊號量,防止生產消費同時進行 */
    full = sem_open("FULL", 0);        /* 產品剩餘訊號量,大於0則可消費 */
    empty = sem_open("EMPTY", BUFFER_SIZE);    /* 空訊號量,它與產品剩餘訊號量此消彼長,大於0時生產者才能繼續生產 */

    item_pro = 0;

    if ((pid = fork()))    /* 父程序用來執行消費者動作 */
    {
        printf("pid %d:\tproducer created....\n", pid);
        /* printf()輸出的資訊會先儲存到輸出緩衝區,並沒有馬上輸出到標準輸出(通常為終端控制檯)。
         * 為避免偶然因素的影響,我們每次printf()都呼叫一下stdio.h中的fflush(stdout)
         * 來確保將輸出立刻輸出到標準輸出。
         */
        fflush(stdout);

        while (item_pro <= NR_ITEMS)    /* 生產完所需產品 */
        {
            sem_wait(empty);
            sem_wait(metux);

            /* 生產完一輪產品(檔案緩衝區只能容納BUFFER_SIZE個產品編號)後
             * 將緩衝檔案的位置指標重新定位到檔案首部。
             */
            if(!(item_pro % BUFFER_SIZE))
                lseek(fi, 0, 0);

            write(fi, (char *) &item_pro, sizeof(item_pro));        /* 寫入產品編號 */
            printf("pid %d:\tproduces item %d\n", pid, item_pro);
            fflush(stdout);
            item_pro++;

            sem_post(full);        /* 喚醒消費者程序 */
            sem_post(metux);
        }
    }
    else    /* 子程序來建立消費者 */
    {
        i = NR_CONSUMERS;
        while(i--)
        {
            if(!(pid=fork()))    /* 建立i個消費者程序 */
            {
                pid = getpid();
                printf("pid %d:\tconsumer %d created....\n", pid, NR_CONSUMERS-i);
                fflush(stdout);

                while(1)
                {
                    sem_wait(full);
                    sem_wait(metux);

                    /* read()讀到檔案末尾時返回0,將檔案的位置指標重新定位到檔案首部 */
                    if(!read(fo, (char *)&item_used, sizeof(item_used)))
                    {
                        lseek(fo, 0, 0);
                        read(fo, (char *)&item_used, sizeof(item_used));
                    }

                    printf("pid %d:\tconsumer %d consumes item %d\n", pid, NR_CONSUMERS-i+1, item_used);
                    fflush(stdout);

                    sem_post(empty);    /* 喚醒生產者程序 */
                    sem_post(metux);

                    if(item_used == NR_ITEMS)    /* 如果已經消費完最後一個商品,則結束 */
                        goto OK;
                }
            }
        }
    }
OK:
    close(fi);
    close(fo);
    return 0;
}
複製程式碼

我們先將虛擬硬碟掛載,將檔案pc.c拷貝到虛擬硬碟下:

cd workspace/oslab/
sudo ./mount-hdc
cp pc.c hdc/usr/root/

編譯執行linux-0.11:

cd linux-0.11
make
../run

在linux-0.11中,編譯執行pc.c:

gcc -o pc pc.c
./pc > sem_output    # 這裡我將輸出重定向到檔案sem_output,因為輸出的內容比較多,而linux-0.11終端不能滾屏,
              # 而且輸出內容多了還會顯示錯亂(可以用Ctrl+L重新整理螢幕),不能複製終端輸出的內容

一定要記得把修改的資料寫入磁碟:

sync

關閉linux-0.11,掛載虛擬磁碟,檢視我們的檔案(當然也可以在linux-0.11中直接檢視,只是顯示內容多時會錯亂,需要反覆按Ctrl+L重新整理):

cd ..
sudo ./mount-hdc
sudo less hdc/usr/root/sem_output

得到輸出:

複製程式碼
pid 20: producer created....
pid 20: produces item 0
pid 20: produces item 1
.......
pid 20: produces item 8
pid 20: produces item 9
pid 24: consumer 5 created....
pid 24: consumer 5 consumes item 0
pid 24: consumer 5 consumes item 1
pid 24: consumer 5 consumes item 2
......
pid 24: consumer 5 consumes item 7
pid 24: consumer 5 consumes item 8
pid 24: consumer 5 consumes item 9
pid 23: consumer 4 created....
......
pid 20: produces item 47
pid 20: produces item 48
pid 20: produces item 49
pid 21: consumer 2 consumes item 40
pid 21: consumer 2 consumes item 41
......
pid 21: consumer 2 consumes item 48
pid 21: consumer 2 consumes item 49
pid 20: produces item 50
pid 22: consumer 3 consumes item 50
複製程式碼

可以看到得出正確結果。

再看一下緩衝檔案:

sudo cat  hdc/usr/root/buffer_file
2^@^@^@)^@^@^@*^@^@^@+^@^@^@,^@^@^@-^@^@^@.^@^@^@/^@^@^@0^@^@^@1^@^@^@

它是一個數據檔案,我們把它轉成十六進位制輸出到終端:

sudo xxd hdc/usr/root/buffer_file
00000000: 3200 0000 2900 0000 2a00 0000 2b00 0000  2...)...*...+...
00000010: 2c00 0000 2d00 0000 2e00 0000 2f00 0000  ,...-......./...
00000020: 3000 0000 3100 0000                      0...1...

8個十六進位制位 = 32個二進位制位 = 4 byte = sizeof(unsigned int),所以上面翻譯為十進位制則是:

00000000: 50 41 42 43  2...)...*...+...
00000010: 44 45 46 47  ,...-......./...
00000020: 48 49        0...
            
           

相關推薦

作業系統實驗報告-訊號實現應用

實驗內容 在Linux-0.11中實現訊號量,並編寫生產者-消費者程式進行檢驗。 實驗步驟 新增訊號量結構體與相應的系統呼叫函式 在include/unistd.h中新增程式碼: #define SEM_NAME_LEN 32 /

實驗四 圖的實現應用 實驗報告 20162305

peek 有關 打印 隊列 廣度 dex 是否 深度優先 遍歷 實驗四 圖的實現和應用 實驗報告 20162305 實驗一 鄰接矩陣實現無向圖 實驗要求 用鄰接矩陣實現無向圖(邊和頂點都要保存),實現在包含添加和刪除結點的方法,添加和刪除邊的方法,size(),isEmp

簡單應用訊號實現程序同步控制

               --------參考文獻   W.Richard Stevens, Stephen A.Rago.UNIX環境高階程式設計[M].北京:人民郵電出版社,2014.6:45

作業系統-使用訊號實現生產者與消費者(C++實現

常用函式: HANDLE WINAPI CreateSemaphore(                  _In_opt_  LPSECURITY_ATTRIBUTES lpSemaphoreAttributes   _In_      LONG lInitialCoun

訊號:整型、記錄型訊號以及利用訊號實現程序互斥前驅關係

訊號量機構是一種功能較強的機制,可用來解決互斥與同步的問題,它只能被兩個標準的原語wait(S)和signal(S)來訪問,也可以記為“P操作”和“V操作”。原語是指完成某種功能且不被分割不被中斷執行的操作序列,通常可由硬體來實現完成不被分割執行特性的功能。如前述的“Tes

20162313 苑洪銘 實驗四 圖的實現應用

isempty 元素 list div 包含 之間 分代 自己 png 20162313 苑洪銘 實驗四 圖的實現與應用 實驗1 要求 用鄰接矩陣實現無向圖(邊和頂點都要保存),實現在包含添加和刪除結點的方法,添加和刪除邊的方法,size(),isEmpty(),廣度優先叠

20162304 2017-2018-1 實驗四-圖的實現應用

連線 寫實 int 鏈式 http 同時 lis 遇到 初始化 實驗四-圖的實現與應用 實驗四 圖的實現與應用-1 試驗內容 用鄰接矩陣實現無向圖(邊和頂點都要保存),實現在包含添加和刪除結點的方法,添加和刪除邊的方法,size(),isEmpty(),廣度優先叠代器,深度

實驗四-圖的實現應用

取出 現在 結果 java 一個數 鏈表實現 頂點 廣度遍歷 矩陣 實驗四-圖的實現與應用 圖的實現與應用-1 實驗要求: 用鄰接矩陣實現無向圖(邊和頂點都要保存),實現在包含添加和刪除結點的方法,添加和刪除邊的方法,size(),isEmpty(),廣度優先叠代器,深度優

20162328蔡文琛 實驗四 圖的實現應用

創建 參考 完全 沒有 position iter 循環 由器 gen 20162328蔡文琛 大二 實驗四 任務詳情 實驗1 用鄰接矩陣實現無向圖(邊和頂點都要保存),實現在包含添加和刪除結點的方法,添加和刪除邊的方法,size(),isEmpty(),廣度優先叠代器,深

Java的RMI遠程方法調用實現應用

描述 應用程序 get bubuko stringbu 會有 cati locate set 最近在學習Dubbo,RMI是很重要的底層機制,RMI(Remote Method Invocation)遠程方法調用是一種計算機之間利用遠程對象互相調用實現雙方通訊的一種通訊機制

安卓開發:SmartImageView簡單實現應用

overload override ans geb actor dsta pub pac 獲取 通常從服務器端獲取的圖片是URL地址,如果簡單地通過URL地址獲取圖片? 有一個開源項目:SmartImageView,做到了這個功能,同時還有其他功能,下載不便,過於龐大 這裏

Linux c++,用訊號實現消費者生產者佇列(程式碼可直接通過編譯)

//用訊號量實現的一個消費者生產者佇列, #include <iostream> #include <pthread.h> #include <semaphore.h> #include <errno.h> #include <queue>

作業系統 程序同步訊號

~~ 一、訊號量機制 ~~ 1、整型訊號量 1)訊號量定義為一個整型量; 2)根據初始情況賦相應的值; 3)僅能通過兩個原子操作來訪問。 整型訊號量符合“有限等待”原則 signal釋放資源後,當CPU被分配給等待程序後,等待程序仍可繼續執行,可以符合“有限等待”。 但整型訊號量

支援向量機演算法的實現應用(Python3超詳細的原始碼實現+圖介紹)

支援向量機演算法的實現和應用,因為自己推到過SVM,建議自己推到一遍, 這裡不對SVM原理做詳細的說明。 原理公式推到推薦看:https://blog.csdn.net/jcjx0315/article/details/61929439 #!/usr/bin/env python # enc

[作業系統實驗]使用命名通道實現程序通訊

      實驗目的和要求   瞭解windows系統環境下的程序通訊機制   熟悉windows系統提供的程序通訊API     完成兩個程序之間的通訊,需要建立兩個工程檔案,在Microsoft

TensorFlow學習筆記(二十三)四種Cross Entropy交叉熵演算法實現應用

交叉熵(Cross-Entropy) 交叉熵是一個在ML領域經常會被提到的名詞。在這篇文章裡將對這個概念進行詳細的分析。 1.什麼是資訊量? 假設是一個離散型隨機變數,其取值集合為,概率分佈函式為 p ( x ) = r (

計算機作業系統感悟隨筆--訊號機制

2.訊號量的基本應用 實現程序互斥 實現程序間的前趨關係(有序) 3.整型訊號量 把整型訊號量定義為一個表示資源數目的整型量S,除初始化外,僅能通過兩個標準的原子操作wait(S)和signal(S)來訪問。 wait(S)和signal(S)操作可以描述為

哲學家問題 pthread訊號實現

#include<stdio.h> #include<string.h> #include<pthread.h> #include<semaphore.h> #define false 0 #define true 1 lon

Linux利用訊號實現執行緒的同步與互斥

執行緒使用互斥鎖可以實現執行緒間的互斥,而互斥鎖本身就是對資源的一種標識狀態,當可以申請到鎖時說明此時資源可以使用,當申請鎖失敗時說明資源此時被其他執行緒所佔用不可使用,我們可以使用訊號量來代替互斥鎖實現。 訊號量用來表示資源數目,當一個執行緒要去訪問資源時,必須先去申請

【C】單鏈表的簡單實現應用!!!

在單鏈表裡面,每個節點包含一個指向下一個節點 的指標。連結串列的最後一個節點的指標欄位是一個值為NULL的指標,他的作用就是提示連結串列後面不再有其他的節點。在你找到連結串列的第一個節點的時候呼叫節點裡面的指標就可以依次訪問剩下所有的節點。為了記住連結串列的起始位置,可以用