訊號量機制中的down和up函式
轉自:https://blog.csdn.net/fzubbsc/article/details/37737159
參考:
https://blog.csdn.net/liuxd3000/article/details/17913363
http://blog.chinaunix.net/uid-25845340-id-3017214.html
https://blog.csdn.net/xiao229404041/article/details/7031776
查閱檔案:
kernel\linux\linux-4.4.3\kernel\locking\semaphore.c
kernel\linux\linux-4.4.3\include\linux\semaphore.h
DOWN操作:linux核心中,對訊號量的DOWN操作有如下幾種:
1、void down(struct semaphore *sem); //不可中斷
down介面用於請求一個訊號量。此函式的呼叫將會到致呼叫執行緒的睡眠, 直到獲取到訊號。同時,該函式的呼叫不允許中斷。
在此函式中首先進行訊號量資源數的檢視,如果訊號量資料(count)不為0,則把其減1,並返回,呼叫成功;否則呼叫__down進行等待,呼叫者進行睡眠。
2、int down_interruptible(struct semaphore *sem);//可中斷
該函式功能和down類似,不同之處為,down不會被訊號(signal)打斷,但down_interruptible能被訊號打斷,因此該函式有返回值來區分是正常返回還是被訊號中斷,如果返回0,表示獲得訊號量正常返回,如果被訊號打斷,返回-EINTR。
3、int down_killable(struct semaphore *sem);//睡眠的程序可以因為受到致命訊號而被喚醒,中斷獲取訊號量的操作。
down_killable與down_interruptible相近,最終傳入的__down_common的實參有所不同(TASK_KILLABLE和TASK_INTERRUPTIBLE),所以,在此不再進行分析。
4、int down_trylock(struct semaphore *sem);//試圖獲取訊號量,若無法獲得則直接返回1而不睡眠。返回0則 表示獲取到了訊號量
down_trylock介面用於試著獲取一個訊號量,但是,此介面不會引起呼叫者的睡眠
5、int down_timeout(struct semaphore *sem,long jiffies);//表示睡眠時間是有限制的,如果在jiffies指明的時間到期時仍然無法獲得訊號量,則將返回錯誤碼。
down_timeout介面的實現過程與down介面的實現過程差不多,只是此介面 可以自定義超時時間,也就是如果在超時間內不能得到訊號量,呼叫者會因為超時而自行喚醒。其實現過程如下,請注意超時引數的傳入。其中TASK_UNINTERRUPTIBLE
在以上五種函式中,驅動程式使用的最頻繁的就是down_interruptible函式,以下將對該函式進行分析。
down_interruptible函式的定義如下:
函式分析:函式首先通過spin_lock_irqsave的呼叫來保證對sem->count操作的原子性。如果count>0,表示當前程序可以獲得訊號量,將count的值減1然後退出。如果count不大於0,表明當前程序無法獲取訊號量,則呼叫__down_interruptible,後者會繼續呼叫__down_common。
__down_common 函式定義如下:
static inline int __sched __down_common(struct semaphore *sem, longstate,
longtimeout)
{
struct task_struct *task= current;
struct semaphore_waiterwaiter;
list_add_tail(&waiter.list,&sem->wait_list);
waiter.task = task;
waiter.up = 0;
for (;;) {
if(signal_pending_state(state, task))
gotointerrupted;
if (timeout <=0)
gototimed_out;
__set_task_state(task,state);
spin_unlock_irq(&sem->lock);
timeout =schedule_timeout(timeout);
spin_lock_irq(&sem->lock);
if (waiter.up)
return 0;
}
timed_out:
list_del(&waiter.list);
return -ETIME;
interrupted:
list_del(&waiter.list);
return -EINTR;
}
函式分析:在__down_common函式數執行了以下操作。
(1)將當前程序放到訊號量成員變數wait_list所管理的佇列中。
(2)在一個for迴圈中把當前的程序狀態這是為TASK_INTERRUPTIBLE,在呼叫schedule_timeout使當前程序進入睡眠狀態,函式將停留在schedule_timeout呼叫上,知道再次被排程執行。
(3) 當該程序再一次被排程時,按原因執行相應的操作:如果waiter.up不為0說明程序被該訊號量的up操作所喚醒,程序可以獲得訊號量。如果程序是因為被使用者空間的訊號所中斷或超時訊號所引起的喚醒,則返回相應的錯誤程式碼。
UP操作:LINUX核心只提供了一個up函式
up函式定義如下:
void up(struct semaphore *sem)
{
unsigned long flags;
spin_lock_irqsave(&sem->lock,flags);
if(likely(list_empty(&sem->wait_list)))
sem->count++;
else
__up(sem);
spin_unlock_irqrestore(&sem->lock,flags);
}
函式分析:如果sem的wait_list佇列為空,則表明沒有其他程序正在等待該訊號量,那麼只需要把sem的count加1即可。如果wait_list佇列不為空,則說明有其他程序正睡眠在wait_list上等待該訊號,此時呼叫__up(sem)來喚醒程序:
__up()函式定義如下:
static noinline void __sched __up(struct semaphore *sem)
{
struct semaphore_waiter*waiter = list_first_entry(&sem->wait_list,
structsemaphore_waiter, list);
list_del(&waiter->list);
waiter->up = 1;
wake_up_process(waiter->task);
}
函式分析:在函式中,呼叫了wake_up_process來喚醒程序,這樣程序就從之前的__down_interruptible呼叫中的timeout=schedule_timeout(timeout)處醒來,wait-up=1, __down_interruptible返回0,程序獲得了訊號量。
up()與down()函式之間的聯絡:由上面對兩個函式的分析可以知道,__down_common函式中timeout=schedule_timeout(timeout) 有著很重要的作用。
Note:一個程序在呼叫down_interruptible()之後,如果sem<0,那麼就進入到可中斷的睡眠狀態並排程其它程序執行, 但是一旦該程序收到訊號,那麼就會從down_interruptible函式中返回。並標記錯誤號為:-EINTR。一個形象的比喻:傳入的訊號量為1好比天亮,如果當前訊號量為0,程序睡眠,直到(訊號量為1)天亮才醒,但是可能中途有個鬧鈴(訊號)把你鬧醒。又如:小強下午放學回家,回家了就要開始吃飯嘛,這時就會有兩種情況:情況一:飯做好了,可以開始吃;情況二:當他到廚房去的時候發現媽媽還在做,媽媽就對他說:“你先去睡會,待會做好了叫你。”小強就答應去睡會,不過又說了一句:“睡的這段時間要是小紅來找我玩,你可以叫醒我。”小強就是down_interruptible,想吃飯就是獲取訊號量,睡覺對應這裡的休眠,而小紅來找我玩就是中斷休眠。
使用可被中斷的訊號量版本的意思是,萬一出現了semaphore的死鎖,還有機會用ctrl+c發出軟中斷,讓等待這個核心驅動返回的使用者態程序退出。而不是把整個系統都鎖住了。在休眠時,能被中斷訊號終止,這個程序是可以接受中斷訊號的!比如你在命令列中輸入# sleep 10000,按下ctrl + c,就給上面的程序傳送了程序終止訊號。訊號傳送給使用者空間,然後通過系統呼叫,會把這個訊號傳給遞給驅動。訊號只能傳送給使用者空間,無權直接傳送給核心的,那1G的核心空間,我們是無法直接去操作的。 --------------------- 本文來自 liuxd3000 的CSDN 部落格 ,全文地址請點選:https://blog.csdn.net/liuxd3000/article/details/17913363?utm_source=copy