✅ 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 控制权
📌 阶段三:外部事件发生,驱动准备唤醒阻塞进程
目的: 在事件发生时唤醒之前阻塞的 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() 恢复执行,返回按键值。
```