如果我們深入 <linux/wait.h>, 你見到在 wait_queue_head_t 類型后面的數據結構是非 常簡單的; 它包含一個自旋鎖和一個鏈表. 這個鏈表是一個等待隊列入口, 它被聲明做 wait_queue_t. 這個結構包含關於睡眠進程的信息和它想怎樣被喚醒.
使一個進程睡眠的第一步常常是分配和初始化一個 wait_queue_t 結構, 隨后將其添加到 正確的等待隊列. 當所有東西都就位了, 負責喚醒工作的人就可以找到正確的進程.
下一步是設置進程的狀態來標志它為睡眠. 在 <linux/sched.h> 中定義有幾個任務狀態. TASK_RUNNING 意思是進程能夠運行, 盡管不必在任何特定的時刻在處理器上運行. 有 2 個狀態指示一個進程是在睡眠: TASK_INTERRUPTIBLE 和 TASK_UNTINTERRUPTIBLE; 當然, 它們對應 2 類的睡眠. 其他的狀態正常地和驅動編寫者無關.
在 2.6 內核, 對於驅動代碼通常不需要直接操作進程狀態. 但是, 如果你需要這樣做, 使用的代碼是:
void set_current_state(int new_state); 在老的代碼中, 你常常見到如此的東西: current->state = TASK_INTERRUPTIBLE;
但是象這樣直接改變 current 是不鼓勵的; 當數據結構改變時這樣的代碼會輕易地失效. 但是, 上面的代碼確實展示了自己改變一個進程的當前狀態不能使其睡眠. 通過改變 current 狀態, 你已改變了調度器對待進程的方式, 但是你還未讓出處理器.
放棄處理器是最后一步, 但是要首先做一件事: 你必須先檢查你在睡眠的條件. 做這個檢 查失敗會引入一個競爭條件; 如果在你忙於上面的這個過程並且有其他的線程剛剛試圖喚 醒你, 如果這個條件變為真會發生什么? 你可能錯過喚醒並且睡眠超過你預想的時間. 因 此, 在睡眠的代碼下面, 典型地你會見到下面的代碼:
if (!condition) schedule();
通過在設置了進程狀態后檢查我們的條件, 我們涵蓋了所有的可能的事件進展. 如果我們 在等待的條件已經在設置進程狀態之前到來, 我們在這個檢查中注意到並且不真正地睡眠. 如果之后發生了喚醒, 進程被置為可運行的不管是否我們已真正進入睡眠.
調用 schedule , 當然, 是引用調度器和讓出 CPU 的方式. 無論何時你調用這個函數, 你是在告訴內核來考慮應當運行哪個進程並且轉換控制到那個進程, 如果必要. 因此你從 不知道在 schedule 返回到你的代碼會是多長時間.
在 if 測試和可能的調用 schedule (並從其返回)之后, 有些清理工作要做. 因為這個代 碼不再想睡眠, 它必須保證任務狀態被重置為 TASK_RUNNING. 如果代碼只是從 schedule 返回, 這一步是不必要的; 那個函數不會返回直到進程處於可運行態. 如果由於不再需要 睡眠而對 schedule 的調用被跳過, 進程狀態將不正確. 還有必要從等待隊列中去除這個 進程, 否則它可能被多次喚醒.