阻塞操作指進程在無法獲得資源時掛起(睡眠),直到條件滿足才繼續;非阻塞則直接返回。Linux 驅動中通過等待隊列實現阻塞,等待隊列作為內核異步事件通知的基礎機制,與進程調度緊密結合,信號量內部也基於等待隊列實現。
該設備允許多進程打開,但規定:只有當有進程寫入數據後,本進程或其他進程才能讀取該數據,否則讀操作一直阻塞。使用信號量保護共享變量,等待隊列實現讀寫同步。
static int global_var = 0; // 共享數據
static struct semaphore sem; // 互斥信號量
static wait_queue_head_t outq; // 等待隊列頭
static int flag = 0; // 數據就緒標誌
static int __init globalvar_init(void) {
int ret = register_chrdev(MAJOR_NUM, "globalvar", &globalvar_fops);
if (!ret) {
init_MUTEX(&sem);
init_waitqueue_head(&outq);
}
return ret;
}
static void __exit globalvar_exit(void) {
unregister_chrdev(MAJOR_NUM, "globalvar");
}
static ssize_t globalvar_read(struct file *filp, char *buf, size_t len, loff_t *off) {
// 1. 等待數據就緒(可被信號中斷)
if (wait_event_interruptible(outq, flag != 0))
return -ERESTARTSYS;
// 2. 獲取信號量保護臨界區
if (down_interruptible(&sem))
return -ERESTARTSYS;
flag = 0; // 清除標誌
if (copy_to_user(buf, &global_var, sizeof(int))) {
up(&sem);
return -EFAULT;
}
up(&sem);
return sizeof(int);
}
static ssize_t globalvar_write(struct file *filp, const char *buf, size_t len, loff_t *off) {
if (down_interruptible(&sem))
return -ERESTARTSYS;
if (copy_from_user(&global_var, buf, sizeof(int))) {
up(&sem);
return -EFAULT;
}
up(&sem);
flag = 1; // 設置數據就緒
wake_up_interruptible(&outq); // 喚醒所有等待讀的進程
return sizeof(int);
}
編寫兩個程式:一個阻塞讀 /dev/globalvar,另一個寫入數據。打開兩個終端分別運行,寫端輸入數值後,讀端立即輸出該值,驗證阻塞/喚醒機制正常工作。
若將 down_interruptible() 放在 wait_event_interruptible() 之前,會導致持有鎖睡眠,嚴重降低並發性能甚至死鎖。正確順序是先等待條件滿足,再獲取鎖訪問臨界資源,如範例所示。