1. 程式人生 > >linux同步機制之訊號量down 和up

linux同步機制之訊號量down 和up

訊號量(semaphore)

  Linux核心的訊號量在概念和原理上和使用者態的System V的IPC機制訊號量是相同的,不過他絕不可能在核心之外使用,因此他和System V的IPC機制訊號量毫不相干。
  訊號量在建立時需要設定一個初始值,表示同時能有幾個任務能訪問該訊號量保護的共享資源,初始值為1就變成互斥鎖(Mutex),即同時只能有一個任務能訪問訊號量保護的共享資源。
   一個任務要想訪問共享資源,首先必須得到訊號量,獲取訊號量的操作將把訊號量的值減1,若當前訊號量的值為負數,表明無法獲得訊號量,該任務必須掛起在 該訊號量的等待佇列等待該訊號量可用;若當前訊號量的值為非負數,表示能獲得訊號量,因而能即時訪問被該訊號量保護的共享資源。
  當任務訪問完被訊號量保護的共享資源後,必須釋放訊號量,釋放訊號量通過把訊號量的值加1實現,如果訊號量的值為非正數,表明有任務等待當前訊號量,因此他也喚醒所有等待該訊號量的任務。

  訊號量的API有:

DECLARE_MUTEX(name)
  該巨集宣告一個訊號量name並初始化他的值為1,即宣告一個互斥鎖。

DECLARE_MUTEX_LOCKED(name)
  該巨集宣告一個互斥鎖name,但把他的初始值設定為0,即鎖在建立時就處在已鎖狀態。因此對於這種鎖,一般是先釋放後獲得。

void sema_init (struct semaphore *sem, int val);
  該函用於數初始化設定訊號量的初值,他設定訊號量sem的值為val。

void init_MUTEX (struct semaphore *sem);
  該函式用於初始化一個互斥鎖,即他把訊號量sem的值設定為1。

void init_MUTEX_LOCKED (struct semaphore *sem);
  該函式也用於初始化一個互斥鎖,但他把訊號量sem的值設定為0,即一開始就處在已鎖狀態。

void down(struct semaphore * sem);
  該函式用於獲得訊號量sem,他會導致睡眠,因此不能在中斷上下文(包括IRQ上下文和softirq上下文)使用該函式。該函式將把sem的值減1,如果訊號量sem的值非負,就直接返回,否則呼叫者將被掛起,直到別的任務釋放該訊號量才能繼續執行。

int down_interruptible(struct semaphore * sem);
  該函式功能和down類似,不同之處為,down不會被訊號(signal)打斷,但down_interruptible能被訊號打斷,因此該函式有返回值來區分是正常返回還是被訊號中斷,如果返回0,表示獲得訊號量正常返回,如果被訊號打斷,返回-EINTR。

int down_trylock(struct semaphore * sem);
  該函式試著獲得訊號量sem,如果能夠即時獲得,他就獲得該訊號量並返回0,否則,表示不能獲得訊號量sem,返回值為非0值。因此,他不會導致呼叫者睡眠,能在中斷上下文使用。

int down_killable(struct semaphore *sem);

int down_timeout(struct semaphore *sem, long jiffies);

int down_timeout_interruptible(struct semaphore *sem, long jiffies);


void up(struct semaphore * sem);
  該函式釋放訊號量sem,即把sem的值加1,如果sem的值為非正數,表明有任務等待該訊號量,因此喚醒這些等待者。

  訊號量在絕大部分情況下作為互斥鎖使用,下面以console驅動系統為例說明訊號量的使用。
  在核心原始碼樹的kernel/printk.c中,使用巨集DECLARE_MUTEX聲明瞭一個互斥鎖console_sem,他用於保護console驅動列表console_drivers及同步對整個console驅動系統的訪問。
   其中定義了函式acquire_console_sem來獲得互斥鎖console_sem,定義了release_console_sem來釋放互斥 鎖console_sem,定義了函式try_acquire_console_sem來盡力得到互斥鎖console_sem。這三個函式實際上是分別 對函式down,up和down_trylock的簡單包裝。
  需要訪問console_drivers驅動列表時就需要使用acquire_console_sem來保護console_drivers列表,當訪問完該列表後,就呼叫release_console_sem釋放訊號量console_sem。
   函式 console_unblank,console_device,console_stop,console_start,register_console 和unregister_console都需要訪問console_drivers,因此他們都使用函式對acquire_console_sem和 release_console_sem來對console_drivers進行保護。

入淺出down_interruptible函式

int down_interruptible(struct semaphore *sem)
這個函式的功能就是獲得訊號量,如果得不到訊號量就睡眠,此時沒有訊號打斷,那麼進入睡眠。但是在睡眠過程中可能被訊號打斷,打斷之後返回-EINTR,主要用來程序間的互斥同步。

下面是該函式的註釋:
/**
* down_interruptible - acquire the semaphore unless interrupted
* @sem: the semaphore to be acquired
*
* Attempts to acquire the semaphore. If no more tasks are allowed to
* acquire the semaphore, calling this function will put the task to sleep.
* If the sleep is interrupted by a signal, this function will return -EINTR.
* If the semaphore is successfully acquired, this function returns 0.
*/

一個程序在呼叫down_interruptible()之後,如果sem<0,那麼就進入到可中斷的睡眠狀態並排程其它程序執行, 但是一旦該程序收到訊號,那麼就會從down_interruptible函式中返回。並標記錯誤號為:-EINTR。一個形象的比喻:傳入的訊號量為1好比天亮,如果當前訊號量為0,程序睡眠,直到(訊號量為1)天亮才醒,但是可能中途有個鬧鈴(訊號)把你鬧醒。又如:小強下午放學回家,回家了就要開始吃飯嘛,這時就會有兩種情況:情況一:飯做好了,可以開始吃;情況二:當他到廚房去的時候發現媽媽還在做,媽媽就對他說:“你先去睡會,待會做好了叫你。”小強就答應去睡會,不過又說了一句:“睡的這段時間要是小紅來找我玩,你可以叫醒我。”小強就是down_interruptible,想吃飯就是獲取訊號量,睡覺對應這裡的休眠,而小紅來找我玩就是中斷休眠。

使用可被中斷的訊號量版本的意思是,萬一出現了semaphore的死鎖,還有機會用ctrl+c發出軟中斷,讓等待這個核心驅動返回的使用者態程序退出。而不是把整個系統都鎖住了。在休眠時,能被中斷訊號終止,這個程序是可以接受中斷訊號的!比如你在命令列中輸入# sleep 10000,按下ctrl + c,就給上面的程序傳送了程序終止訊號。訊號傳送給使用者空間,然後通過系統呼叫,會把這個訊號傳給遞給驅動。訊號只能傳送給使用者空間,無權直接傳送給核心的,那1G的核心空間,我們是無法直接去操作的。