源出處:http://www.startos.com/linux/tips/2011011921499.html
1 休眠與同步
一個驅動當它無法立刻滿足請求應當如何響應? 一個對 read 的調用可能當沒有資料時到來, 而以後會期待更多的資料。或者一個進程可能試圖寫, 但是你的裝置沒有準備好接受資料, 因為你的輸出緩衝滿了。調用進程往往不關心這種問題; 程式員只希望調用 read 或 write 並且使調用返回, 在必要的工作已完成後. 這樣, 在這樣的情形中。驅動應當(預設地)阻塞進程, 使它進入睡眠直到請求可繼續。
進程被置為休眠,意味著它被標識為處於一個特殊的狀態並且從調度器的運行隊列中移走。直到發生某些事情改變了那個狀態,否則這個進程將不被任何 CPU調度運行。
安全地進入休眠的兩條規則:
1) 永遠不要在原子上下文中進入休眠。
當驅動在持有一個自旋鎖、seqlock或者 RCU 鎖時不能睡眠;
關閉中斷也不能睡眠;
持有一個訊號量時休眠是合法的,但你應當仔細查看代碼:如果代碼在持有一個訊號量時睡眠,任何其他的等待這個訊號量的線程也會休眠。因此發生在持有訊號量時的休眠必須短暫,而且決不能阻塞那個將最終喚醒你的進程。
2) 當進程被喚醒,重新檢查其所需資源。
它並不知道休眠了多長時間以及休眠時發生什麼;也不知道是否另有進程也在休眠等待同一事件,且那個進程可能在它之前醒來並擷取了所等待的資源。所以不能對喚醒後的系統狀態做任何的假設,並必須重新檢查等待條件來確保正確的響應。
除非確信其他進程會在其他地方喚醒休眠的進程,否則也不能睡眠。使進程可被找到意味著:需要維護一個稱為等待隊列的資料結構。它是一個進程鏈表,其中飽含了等待某個特定事件的所有進程。在 Linux 中, 一個等待隊列由一個wait_queue_head_t 結構體來管理。
2 休眠的基礎
2.1 wait_queue系列資料結構
2.1.1 wait_queue_head_t
\include\linux\wait.h
struct __wait_queue_head {
spinlock_t lock;
struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;
它包含一個自旋鎖和一個鏈表。這個鏈表是一個等待隊列入口。
關於自訂結構體的風格,若需要提供別名,則原始類型前面加”__”或者“tag_”,表示其為內部資料類型,對外是不可見的。typedef之後的類型為了和原始類型分開一般會在後面添加“_t”,表示是typedef的,對外使用。
#define __WAIT_QUEUE_HEAD_INITIALIZER(name) { \
.lock = __SPIN_LOCK_UNLOCKED(name.lock), \
.task_list = { &(name).task_list, &(name).task_list } \
}
因為Linux核心對於鏈表的遍曆方式的問題,通常一個雙向迴圈鏈表中有一個前端節點,其與其他節點的結構不一樣,並且通常無有效資訊。此處的等待隊列頭有兩個域:
1) 操作迴圈鏈表的互斥鎖;
2) 嵌入到等待隊列頭中的鏈表頭。
為了用“.”域的形式初始化成員不能採用單獨的初始化鎖和鏈表頭部的宏,但可以採用聲明一個結構體類型的宏,如 __SPIN_LOCK_UNLOCKED(name.lock);.task_list的初始化應該採用LIST_HEAD_INIT宏的,這樣提高了可移植性。定義等待隊列頭部的同時,初始化了其成員,尤其是鏈表頭的初始化是添加後續等待隊列的前提。
#define DECLARE_WAIT_QUEUE_HEAD(name) \
wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name)
定義一個等待隊列頭同時分配記憶體並進行初始化。對外的介面。
extern void init_waitqueue_head(wait_queue_head_t *q);
void init_waitqueue_head(wait_queue_head_t *q)
{
spin_lock_init(&q->lock);
INIT_LIST_HEAD(&q->task_list);
}
動態初始化一個已經分配了記憶體的 wait_queue_head_t結構。當wait_queue_head_t類型成員內嵌到其他結構體中時需要採用此方法,而不能採用 DECLARE_WAIT_QUEUE_HEAD全域或在棧中定義初始化一個wait_queue_head_t結構。
2.1.2 wait_queue_t
struct __wait_queue {
unsigned int flags; //在等待隊列上喚醒時是否具備排他性
#define WQ_FLAG_EXCLUSIVE 0x01
void *private;
wait_queue_func_t func; //從等待隊列中喚醒進程時執行的統一操作,將進程更改為可運行狀態,具體誰運行將由調度策略決定
struct list_head task_list; //與該等待隊列對應的鏈表
};
/*Macros for declaration and initialisaton of the datatypes*/
#define __WAITQUEUE_INITIALIZER(name, tsk) { \
.private = tsk, \
.func = default_wake_function, \
.task_list = { NULL, NULL } \
}
GNU文法中對於結構體成員賦初值採用了域的形式,如“.private =”,其好處在於:
a) 可以選擇性的對部分成員賦值。當結構體成員變數較多而大部分無須初始值時,此方法顯得尤為重要,因此減少了不必要的賦值。
b) 賦值順序與資料結構定義中成員的順序無關,因此若結構體成員順序變化,初始化部分不會受到任何影響。
c) 各個域之間用“,”分隔,最後一個無“,”。
#define DECLARE_WAITQUEUE(name, tsk) \
wait_queue_t name = __WAITQUEUE_INITIALIZER(name, tsk)