阻塞I/O笔记

✅ Linux 驱动中阻塞 I/O 完整流程总结


📌 阶段一:初始化等待队列(设备初始化时)

目的: 准备一个等待队列,用于后续让进程挂起/唤醒

1
2
wait_queue_head_t r_wait;               // 声明等待队列头
init_waitqueue_head(&r_wait); // 初始化等待队列

📌 阶段二:用户调用 read(),驱动开始执行阻塞流程

目的: 如果当前设备没有准备好数据,让当前进程阻塞

🔹 判断是否需要阻塞

1
if (atomic_read(&releasekey) == 0)      // 判断是否有事件(数据)可读

🔹 创建等待队列项(绑定当前进程)

1
2
DECLARE_WAITQUEUE(wait, current);       // 定义一个等待队列节点,指向当前进程
add_wait_queue(&r_wait, &wait); // 将该节点添加到等待队列中

🔹 设置当前任务状态为“可中断睡眠”

1
__set_current_state(TASK_INTERRUPTIBLE); // 表示愿意进入睡眠状态

🔹 真正进入阻塞,放弃 CPU 控制权

1
schedule();                              // 进程挂起,直到被唤醒(阻塞点)

📌 阶段三:外部事件发生,驱动准备唤醒阻塞进程

目的: 在事件发生时唤醒之前阻塞的 read()

🔹 外部中断触发(如按键中断)

1
2
3
4
5
6
7
8
static irqreturn_t key0_handler(int irq, void *dev_id)
{
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)dev_id;
dev->curkeynum = 0;
dev->timer.data = (volatile long)dev_id;
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10));
return IRQ_HANDLED;
}

🔹 定时器超时,进入回调函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void timer_function(unsigned long arg)
{
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg;
struct irq_keydesc *keydesc = &dev->irqkeydesc[dev->curkeynum];
unsigned char value = gpio_get_value(keydesc->gpio);

if (value == 0) {
atomic_set(&dev->keyvalue, keydesc->value2); // 按下
atomic_set(&dev->releasekey, 2);
} else {
atomic_set(&dev->keyvalue, 0x80 | keydesc->value); // 释放
atomic_set(&dev->releasekey, 1);
}

if (atomic_read(&dev->releasekey)) {
wake_up_interruptible(&dev->r_wait); // 唤醒等待队列
}
}

📌 阶段四:read()被唤醒后,恢复执行并读取数据

目的: 被唤醒后完成数据读取并返回用户空间

🔹 被唤醒 → 判断是否是信号中断

1
2
3
4
if (signal_pending(current)) {
ret = -ERESTARTSYS;
goto wait_error;
}

🔹 恢复进程状态 → 从睡眠状态转为运行

1
2
__set_current_state(TASK_RUNNING);
remove_wait_queue(&r_wait, &wait);

🔹 拷贝数据返回给用户

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
keyvalue = atomic_read(&dev->keyvalue);
releasekey = atomic_read(&dev->releasekey);

if (releasekey == 1) {
if (keyvalue & 0x80) {
keyvalue &= ~0x80;
ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));
} else {
goto data_error;
}
atomic_set(&dev->releasekey, 0);
} else if (releasekey == 2) {
ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));
atomic_set(&dev->releasekey, 0);
} else {
goto data_error;
}

✅ 补充概念说明(简洁版)

名称 说明
wait_queue_head_t 等待队列头,管理一批阻塞任务
DECLARE_WAITQUEUE() 创建等待队列节点,指向当前进程(current
add_wait_queue() 把节点挂到等待队列上
__set_current_state() 设置当前进程状态为 TASK_INTERRUPTIBLE 等待唤醒
schedule() 阻塞当前任务,进入睡眠,等待被唤醒
wake_up_interruptible() 唤醒队列中所有处于“可中断睡眠”的任务

✅ 一句话总结流程

用户态调用 read() → 驱动进入阻塞 → 外部中断触发定时器 → 定时器中唤醒任务 → read() 恢复执行,返回按键值。
```


阻塞I/O笔记
https://chrisy0618.github.io/2025/04/15/20250415/
作者
Chris.Y
发布于
2025年4月15日
许可协议