等待队列是内核中实现进程调度的一个十分重要的数据结构,其任务是维护一个链表,链表中每一个节点都是一个PCB(进程控制块),内核会将PCB挂在等待队列中的所有进程都调度为睡眠状态,直到某个唤醒的条件发生。应用层的阻塞IO与非阻塞IO的使用我已经在Linux I/O多路复用一文中讨论过了,本文主要讨论驱动中怎么实现对设备IO的阻塞与非阻塞读写。显然,实现这种与阻塞相关的机制要用到等待队列机制。本文的内核源码使用的是3.14.0版本
设备阻塞IO的实现
当我们读写设备文件的IO时,最终会回调驱动中相应的接口,而这些接口也会出现在读写设备进程的进程(内核)空间中,如果条件不满足,接口函数使进程进入睡眠状态,即使读写设备的用户进程进入了睡眠,也就是我们常说的发生了阻塞。In a word,读写设备文件阻塞的本质是驱动在驱动中实现对设备文件的阻塞,其读写的流程可概括如下:
1. 定义-初始化等待队列头
//定义等待队列头wait_queue_head_t waitq_h;//初始化,等待队列头init_waitqueue_head(wait_queue_head_t *q); //或//定义并初始化等待队列头DECLARE_WAIT_QUEUE_HEAD(waitq_name);
上面的几条选择中,最后一种会直接定义并初始化一个等待头,但是如果在模块内使用全局变量传参,用着并不方便,具体用哪种看需求。
我们可以追一下源码,看一下上面这几行都干了什么:
//include/linux/wait.h
35 struct __wait_queue_head {
36 spinlock_t lock; 37 struct list_head task_list; 38 }; 39 typedef struct __wait_queue_head wait_queue_head_t;wait_queue_head_t
--36-->这个队列用的自旋锁
--27-->将整个队列"串"在一起的纽带
然后我们看一下初始化的宏:
55&nbs
