訊號量的實現與應用

2021-09-25 18:34:49 字數 3714 閱讀 5307

本實驗需要完成兩個任務:

(1)在 ubuntu 下編寫程式,用訊號量解決生產者——消費者問題;

(2)在 linux-0.11 中實現訊號量,用生產者—消費者程式檢驗之。

訊號量,英文為 semaphore,最早由荷蘭科學家、圖靈獎獲得者 e. w. dijkstra 設計,任何作業系統教科書的「程序同步」部分都會有詳細敘述。

linux 的訊號量秉承 posix 規範,用man sem_overview可以檢視相關資訊。

本次實驗涉及到的訊號量系統呼叫包括:sem_open()、sem_wait()、sem_post() 和 sem_unlink()。

在0.11中實現訊號量,用生產者-消費者程式檢驗之

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);

功能是建立乙個訊號量,或開啟乙個已經存在的訊號量。 name是訊號量的名字。不同的程序可以通過提供同樣的name而共享同乙個訊號量。如果該訊號量不存在,就建立新的名為name的訊號量;如果存在,就開啟已經存在的名為name的訊號量。

value是訊號量的初值,僅當新建訊號量時,此引數才有效,其餘情況下它被忽略。

當成功時,返回值是該訊號量的唯一標識(比如,在核心的位址,id等),由另外兩個系統呼叫使用。如失敗,返回值是null。

訊號量的p原子操作。如果繼續執行的條件不滿足,則令呼叫程序等待在訊號量sem上。

返回0表示成功,返回-1表示失敗。

訊號量的v原子操作。如果有等待sem的程序,它會喚醒其中的乙個。返回0表示成功,返回-1表示失敗。

功能是刪除名為name的訊號量。返回0表示成功,返回-1表示失敗。

在kernel目錄下新建sem.c檔案實現如下功能。然後將pc.c從ubuntu移植到0.11下,測試自己實現的訊號量。

生產者—消費者問題

生產者—消費者問題的解法幾乎在所有作業系統教科書上都有,其基本結構為:

producer()

consumer()

顯然在演示這一過程時需要建立兩類程序,一類執行函式 producer(),另一類執行函式 consumer()。

多程序共享檔案

在 linux 下使用 c 語言,可以通過三種方法進行檔案的讀寫:

使用標準 c 的 fopen()、fread()、fwrite()、fseek() 和 fclose() 等;

使用系統呼叫 open()、read()、write()、lseek() 和 close() 等;

通過記憶體映象檔案,使用 mmap() 系統呼叫。

在 linux 0.11 上只能使用前兩種方法。

fork() 呼叫成功後,子程序會繼承父程序擁有的大多數資源,包括父程序開啟的檔案。所以子程序可以直接使用父程序建立的檔案指標/描述符/控制代碼,訪問的是與父程序相同的檔案。

使用標準 c 的檔案操作函式要注意,它們使用的是程序空間內的檔案緩衝區,父程序和子程序之間不共享這個緩衝區。因此,任何乙個程序做完寫操作後,必須 fflush() 一下,將資料強制更新到磁碟,其它程序才能讀到所需資料。

建議直接使用系統呼叫進行檔案操作。

原子操作、睡眠和喚醒

linux 0.11 是乙個支援併發的現代作業系統,雖然它還沒有面向應用實現任何鎖或者訊號量,但它內部一定使用了鎖機制,即在多個程序訪問共享的核心資料時一定需要通過鎖來實現互斥和同步。

鎖必然是一種原子操作。通過模仿 0.11 的鎖,就可以實現訊號量。

多個程序對磁碟的併發訪問是乙個需要鎖的地方。linux 0.11 訪問磁碟的基本處理辦法是在記憶體中劃出一段磁碟快取,用來加快對磁碟的訪問。程序提出的磁碟訪問請求首先要到磁碟快取中去找,如果找到直接返回;如果沒有找到則申請一段空閒的磁碟快取,以這段磁碟快取為引數發起磁碟讀寫請求。請求發出後,程序要睡眠等待(因為磁碟讀寫很慢,應該讓出 cpu 讓其他程序執行)。這種方法是許多作業系統(包括現代 linux、unix 等)採用的較通用的方法。這裡涉及到多個程序共同操作磁碟快取,而程序在操作過程可能會被排程而失去 cpu。因此操作磁碟快取時需要考慮互斥問題,所以其中必定用到了鎖。而且也一定用到了讓程序睡眠和喚醒。

下面是從 kernel/blk_drv/ll_rw_blk.c 檔案中取出的兩個函式

static inline void lock_buffer(struct buffer_head * bh)

static inline void unlock_buffer(struct buffer_head * bh)

分析 lock_buffer() 可以看出,訪問鎖變數時用開、關中斷來實現原子操作,阻止程序切換的發生。當然這種方法有缺點,且不適合用於多處理器環境中,但對於 linux 0.11,它是一種簡單、直接而有效的機制。

另外,上面的函式表明 linux 0.11 提供了這樣的介面:用 sleep_on() 實現程序的睡眠,用 wake_up() 實現程序的喚醒。它們的引數都是乙個結構體指標—— struct task_struct *,即程序都睡眠或喚醒在該引數指向的乙個程序 pcb 結構鍊錶上。

因此,我們可以用開關中斷的方式實現原子操作,而呼叫 sleep_on() 和 wake_up() 進行程序的睡眠和喚醒。

sleep_on() 的功能是將當前程序睡眠在引數指定的鍊錶上(注意,這個鍊錶是乙個隱式鍊錶,詳見《注釋》一書)。wake_up() 的功能是喚醒鍊錶上睡眠的所有程序。這些程序都會被排程執行,所以它們被喚醒後,還要重新判斷一下是否可以繼續執行。可參考 lock_buffer() 中的那個 while 迴圈。

訊號量的實現

訊號量的組成

1、需要有乙個整形變數value,作為共享資料的計數器。

2、需要乙個等待佇列,在缺少共享資源時存放等待該資源的程序。

3、 需要有乙個名字在多個程序之間引用該訊號量。

同時,系統需要支援多個訊號量,可以考慮將這些訊號量存放在乙個陣列中。這樣,在新建訊號量時要在陣列中尋找空閒空間存放該訊號量。在使用訊號量的名字開啟該訊號量時,需要在陣列中查詢該訊號量的名字。在刪除該訊號量時,需要刪除該訊號量的名字並將該訊號量所佔的陣列位置標記為可用。

等待佇列的構造

當前程序呼叫sem_wait()函式進行訊號量的p操作時,如果缺乏資源就需要將當前程序阻塞在等待佇列上;當呼叫sem_post()函式進行訊號量的v操作時,如果有程序阻塞在等待佇列,就喚醒隊首程序。所以等待佇列的構造有兩種思路:一是自己建立等待佇列,然後手動將需要阻塞的程序置為不可中斷睡眠狀態,將其放入等待佇列。或將需要喚醒的隊首程序出隊,並將其由不可中斷睡眠置為就緒狀態;另外一種是利用linux 0.11提供的sleep_on()函式實現程序的睡眠,用wake_up()函式喚醒程序,由於sleep_on()函式會利用睡眠程序核心棧中的tmp變數形成乙個隱式的等待佇列,所以無需我們在自己構造等待佇列。

systemV訊號量 與 Posix訊號量

一 函式上的區別 訊號量有兩種實現 傳統的system v訊號量和新的posix訊號量。它們所提供的函式很容易被區分 對於所有system v訊號量函式,在它們的名字裡面沒有下劃線。例如,應該是semget 而不是sem get 然而,所有的的posix訊號量函式都有乙個下劃線。下面列出了它們提供的...

訊號量的實現和應用實驗報告 訊號量概述

程序互斥 mutex 進入臨界區的程序只能有乙個,當然前提是這些程序有共同的臨界區。程序同步 有邏輯關聯的程序先後執行,比如b只有等a執行完了才能執行,a沒執行完程序b只能掛起。這實際上是一種約束,更是一種通訊。需要同步的程序之間不一定有共享臨界區。1 禁用硬體中斷和基於硬體的原子操作 testan...

整型訊號量與記錄型訊號量

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