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