設(shè)備驅(qū)動開發(fā)中的等待隊列實現(xiàn):睡眠與喚醒機制的C語言模型
在Linux設(shè)備驅(qū)動開發(fā)中,等待隊列(Wait Queue)是實現(xiàn)進程睡眠與喚醒的核心機制,它允許進程在資源不可用時主動放棄CPU,進入可中斷睡眠狀態(tài),待資源就緒后再被喚醒。本文通過C語言模型解析等待隊列的實現(xiàn)原理,結(jié)合代碼示例說明其關(guān)鍵機制。
一、等待隊列的核心數(shù)據(jù)結(jié)構(gòu)
等待隊列的本質(zhì)是一個包含進程描述符(task_struct)的鏈表,每個節(jié)點代表一個處于等待狀態(tài)的進程。Linux內(nèi)核通過wait_queue_head_t和wait_queue_t兩個結(jié)構(gòu)體管理等待隊列:
c
// 內(nèi)核源碼中的簡化定義(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;
struct __wait_queue {
unsigned int flags; // 等待標志(WQ_FLAG_EXCLUSIVE等)
void *private; // 私有數(shù)據(jù)(通常指向等待條件)
struct list_head task_list; // 鏈表節(jié)點
wait_queue_func_t func; // 喚醒回調(diào)函數(shù)
};
typedef struct __wait_queue wait_queue_t;
二、等待隊列的初始化與銷毀
1. 靜態(tài)初始化(編譯時確定)
c
// 靜態(tài)初始化等待隊列頭
DECLARE_WAIT_QUEUE_HEAD(my_wait_queue);
// 靜態(tài)初始化等待隊列項
static wait_queue_t my_wait_item = {
.flags = 0,
.private = NULL,
.func = default_wake_function, // 內(nèi)核默認喚醒函數(shù)
};
2. 動態(tài)初始化(運行時確定)
c
// 動態(tài)初始化等待隊列頭
wait_queue_head_t dynamic_queue;
init_waitqueue_head(&dynamic_queue);
// 動態(tài)初始化等待隊列項
wait_queue_t *item = kmalloc(sizeof(wait_queue_t), GFP_KERNEL);
init_waitqueue_entry(item, current); // 綁定當前進程
三、睡眠與喚醒的核心機制
1. 進程睡眠模型
c
// 模擬設(shè)備驅(qū)動中的等待邏輯
void device_wait_example(wait_queue_head_t *queue) {
DEFINE_WAIT(wait); // 創(chuàng)建等待隊列項并初始化
// 將當前進程添加到等待隊列(未睡眠狀態(tài))
add_wait_queue(queue, &wait);
for (;;) {
// 設(shè)置進程狀態(tài)為可中斷睡眠
set_current_state(TASK_INTERRUPTIBLE);
// 檢查條件是否滿足(模擬設(shè)備就緒檢查)
if (device_is_ready()) {
break;
}
// 主動調(diào)度讓出CPU(進入睡眠)
schedule();
// 被喚醒后檢查是否被信號中斷
if (signal_pending(current)) {
printk("Process interrupted by signal\n");
remove_wait_queue(queue, &wait);
return -ERESTARTSYS;
}
}
// 恢復(fù)進程狀態(tài)并移除等待項
__set_current_state(TASK_RUNNING);
remove_wait_queue(queue, &wait);
}
2. 喚醒機制模型
c
// 模擬設(shè)備中斷喚醒等待進程
void device_wakeup_example(wait_queue_head_t *queue) {
// 遍歷等待隊列喚醒所有進程(非獨占模式)
wake_up_interruptible_all(queue);
/* 實際內(nèi)核實現(xiàn)(簡化版):
struct list_head *tmp;
list_for_each(tmp, &queue->task_list) {
wait_queue_t *curr = list_entry(tmp, wait_queue_t, task_list);
if (curr->func(curr)) // 執(zhí)行喚醒回調(diào)
try_to_wake_up(curr->private, TASK_INTERRUPTIBLE, 0);
}
*/
}
四、完整案例:字符設(shè)備驅(qū)動中的等待隊列
c
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/wait.h>
static wait_queue_head_t data_available;
static int device_ready = 0;
// 模擬設(shè)備就緒(如中斷處理程序調(diào)用)
void trigger_device_ready(void) {
device_ready = 1;
wake_up_interruptible(&data_available); // 喚醒所有等待進程
}
// 阻塞式讀取實現(xiàn)
ssize_t my_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) {
DECLARE_WAITQUEUE(wait, current);
int ret = 0;
add_wait_queue(&data_available, &wait);
while (!device_ready) {
set_current_state(TASK_INTERRUPTIBLE);
if (filp->f_flags & O_NONBLOCK) {
ret = -EAGAIN;
goto out;
}
schedule(); // 睡眠
if (signal_pending(current)) {
ret = -ERESTARTSYS;
goto out;
}
}
// 模擬數(shù)據(jù)傳輸
if (copy_to_user(buf, "Data", 4)) {
ret = -EFAULT;
} else {
ret = 4;
device_ready = 0; // 重置狀態(tài)
}
out:
__set_current_state(TASK_RUNNING);
remove_wait_queue(&data_available, &wait);
return ret;
}
static int __init my_init(void) {
init_waitqueue_head(&data_available);
printk("Wait queue demo initialized\n");
return 0;
}
module_init(my_init);
MODULE_LICENSE("GPL");
五、關(guān)鍵注意事項
狀態(tài)管理:
必須在調(diào)用schedule()前設(shè)置TASK_INTERRUPTIBLE/UNINTERRUPTIBLE
喚醒后必須恢復(fù)狀態(tài)為TASK_RUNNING
競態(tài)條件:
c
// 錯誤示例:檢查與睡眠間存在競態(tài)
if (!device_ready) { // A
schedule(); // B
// 競態(tài)窗口:設(shè)備可能在A和B之間就緒
}
喚醒策略:
wake_up():喚醒所有非獨占等待者
wake_up_interruptible():僅喚醒可中斷等待者
wake_up_nr():限制喚醒數(shù)量
性能優(yōu)化:
使用DEFINE_WAIT()替代手動初始化
考慮使用wait_event_*()宏簡化代碼
結(jié)論:等待隊列是設(shè)備驅(qū)動中實現(xiàn)異步通知的核心機制,其正確實現(xiàn)需要嚴格管理進程狀態(tài)、處理競態(tài)條件并選擇合適的喚醒策略?,F(xiàn)代Linux內(nèi)核提供了wait_event()、wake_up_poll()等高級抽象,但理解底層原理仍是調(diào)試復(fù)雜睡眠-喚醒問題的關(guān)鍵。開發(fā)者應(yīng)通過內(nèi)核文檔(Documentation/core-api/wait.rst)和實際案例深入掌握其工作機制。