信號量通信之semget()、semctl()、semop()及其基礎實驗
這個信號量理解起來是有點不容易啊,我看書看了好幾遍才知道怎麼回事。在講這一節信號量之前,我還 是想先說幾個小知識點,這也是我在學習完後最終理解的“精華”,哈哈!
信號量是干啥的?
信號量就是用來解決進程間的同步與互斥問題的一種進程間通信機制。
同步與互斥的通俗理解
這兩個名詞咱們從字面上就能理解。舉個例子吧,在創建子進程時,你是怎麼保證父子進程執行的先後順序呢 ?我在以前的時候是通過sleep()函數來實現的,比如我想讓子進程先運行再讓父進程運行,那麼我就在父進 程的程序中加一個sleep()函數,讓父進程先睡眠,這樣子就能先執行子進程了。有的時候咱們事先無法知道 父進程和子進程哪一個先執行,但是要向我那樣使用sleep()函數,只能保證先執行子進程,但是不能保證子 進程執行完後再執行父進程,這樣說能理解吧。所以如果我們想要子進程完全執行完後再執行父進程,就可以 利用信號量來解決它們之間的同步問題。還不理解也沒關系,我會在後面的實驗中再結合實際講。再舉個更通 俗的例子,一條食品生產線上,假設A、B共同完成一個食品的包裝任務,A負責將食品放到盒子裡,B和C負責 將盒子打包。必須得是A先裝食品B再打包吧,要是B不按規則先打包,那A還裝啥,所以就需要一種機制方法保 證A先進行B再進行,“信號量”就是這種機制方法,AB之間的關系就是同步關系;再假設打包要用到刀子,而 車間就有一把刀子,這時候B和C就構成了互斥關系。
信號量與信號的區別
不瞞你說,我剛開始 學的時候所理解的就是“信號量是很多個信號”,當然這是錯誤的哈!光從英語上看就不一樣:信號量是 Semaphore,而信號是 Signal 。其實,信號和信號量是不同的。它們雖然都可以實現同步和互斥,但是前者 是使用信號處理器來進行的,而後者是使用P,V操作來實現的。(PV操作後邊有講)
好了,進入正題。
信號量概述
在多任務操作系統環境下,多個進程會同時運行,並且一些進程間可能會存在一定 的關聯。多個進程可能為了完成同一個任務相互協作,這就形成了進程間的同步關系。而且在不同進程間,為 了爭奪有限的系統資源(硬件或軟件資源)會進入競爭狀態,這就是進程間的互斥關系。
進程間的互 斥關系與同步關系存在的根源在於臨界資源。臨界資源是在同一時刻只允許有限個(通常只有一個)進程可以 訪問(讀)或修改(寫)的資源,通常包括硬件資源(處理器、內存、存儲器及其它外圍設備等)和軟件資源 (共享代碼段、共享結構和變量等)。訪問臨界資源的代碼叫做臨界區,臨界區本身也會稱為臨界資源。
信號量是用來解決進程間的同步與互斥問題的一種進程間通信機制,包括一個稱為信號量的變量和在 該信號量下等待資源的進程等待隊列,以及對信號量進行的兩個原子操作(P/V操作)。其中,信號量對應於 某一種資源,取一個非負的整形值。信號量值(常用sem_id表示)指的是當前可用的該資源的數量,若等於0 則意味著目前沒有可用的資源。
PV原子操作(很重要的)
PV原子操作的具體定義如下:(好好 理解,很重要的啊)
● P操作:如果有可用的資源(信號量值>0),則此操作所在的進程占 用一個資源(此時信號量值減1,進入臨界區代碼);如果沒有可用的資源(信號量值=0),則此操作所在的進程 被阻塞直到系統將資源分配給該進程(進入等待隊列,一直等到資源輪到該進程)。
● V操作 :如果在該信號量的等待隊列中有進程在等待資源,則喚醒一個阻塞進程;如果沒有進程等待它,則釋放一個 資源(即信號量值加1)。
常見的使用信號量訪問臨界區的偽代碼如下圖1:
圖1中的非臨界 區和臨界區在咱們這裡就是代碼,具體這張圖的理解還要結合著後面的實驗才能理解。
最簡單的信號 量只能取0和1值,這種信號量叫做二維信號量,在本節中,主要討論二維信號量。二維信號量學好了,比較容 易擴展到使用多維信號量的情況。
信號量編程
函數說明
在Linux系統中,使用信號量通 常分為以下4個步驟:
① 創建信號量或獲得在系統中已存在的信號量,此時需要調用 semget() 函數。不同進程通過使用同一個信號量鍵值來獲得同一個信號量。
② 初始化信號量,此時使用 semctl() 函數的SETVAL操作。當使用二維信號量時,通常將信號量初始化為1。
③ 進行信號量 的PV操作,此時,調用 semop()函數。這一步是實現進程間的同步和互斥的核心工作部分。
④ 如果不需要信號量,則從系統中刪除它,此時使用semctl()函數的 IPC_RMID操作。需要注意的是,在程序中 不應該出現對已經被刪除的信號量的操作。
函數格式
、
表2 semctl()函數
基礎實驗1
這兩個實驗主要是練習熟悉一下信號量的概念和基本用法,首先,我先在實驗1的代碼中 不添加與信號量相關的代碼,觀察運行結果,實驗代碼如下
simple_fork.c文件:http://download.csdn.net/detail/mybelief321/5577841
編譯運行結果如下
這個運行結果是有點意思哈,由結果可以看到父進程先結束,然後子進程結束,但是我本意不是這樣啊, 我想讓子進程先執行,父進程再執行,也就是父進程等待子進程結束。下面咱們就用信號量來實現它。
基礎實驗2
本實驗使用信號量來解決上面實驗1的多進程間存在的同步問題,完成的功能是使父 進程等待子進程結束。因為信號量相關的函數調用接口比較復雜,咱們將它們封裝成二維單個信號量的基本函 數,分別為信號量初始化函數(或者信號量賦值函數)init_sem()、P操作函數sem_p()、V操作函數sem_v()及 刪除信號量函數 del_sem()等,具體實驗代碼如下
sem_fork.c文件點此下載
編譯運行結果如下
這樣通過信號量就保證了它們 之間的同步問題
實驗分析
下面再進一步結合實驗結果重點理解一下 P/V操作,下面再貼出來圖 1
由實驗2的第47~49行可以看出,47行的 P操作和49行的 V操作決定了48行的代碼為圖1中的臨界區(資源R )。
程序執行流程就是,先將信號量值初始化為0(第31行),假設程序先執行父進程,通過前邊講的 P操作的特點可知,由於信號量值為0,該進程被阻塞,也就是轉而去執行子進程,由V操作可知,執行完子進程 後,由於信號量的等待隊列中有進程(這裡是父進程)在等待資源,則喚醒一個阻塞進程(這裡是父進程)。