等待队列是内核中实现进程调度的一个十分重要的数据结构,其任务是维护一个链表,链表中每个节点都是一个PCB(进程控制块),内核会将PCB挂在等待队列中的全部进程都调度为睡眠状态,直到某个唤醒的条件发生。应用层的阻塞IO与非阻塞IO的使用我已经在Linux I/O多路复用一文中讨论过了,本文主要讨论驱动中怎么实现对设备IO的阻塞与非阻塞读写。显然,实现这种与阻塞相关的机制要用到等待队列机制。本文的内核源码使用的是3.14.0版本html
当咱们读写设备文件的IO时,最终会回调驱动中相应的接口,而这些接口也会出如今读写设备进程的进程(内核)空间中,若是条件不知足,接口函数使进程进入睡眠状态,即便读写设备的用户进程进入了睡眠,也就是咱们常说的发生了阻塞。In a word,读写设备文件阻塞的本质是驱动在驱动中实现对设备文件的阻塞,其读写的流程可归纳以下:linux
//定义等待队列头 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 #define __WAIT_QUEUE_HEAD_INITIALIZER(name) { \ 56 .lock = __SPIN_LOCK_UNLOCKED(name.lock), \ 57 .task_list = { &(name).task_list, &(name).task_list } } 58 59 #define DECLARE_WAIT_QUEUE_HEAD(name) \ 60 wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name)
DECLARE_WAIT_QUEUE_HEAD()
--60-->根据传入的字符串name,建立一个名为name的等待队列头
--57-->初始化上述task_list域,居然没有用内核标准的初始化宏,无语。。。code
为等待队列添加事件,即进程进入睡眠状态直到condition为真才返回。_interruptible的版本版本表示睡眠可中断,_timeout版本表示超时版本,超时就会返回,这种命名规范在内核API中随处可见。htm
void wait_event(wait_queue_head_t *waitq_h,int condition); void wait_event_interruptible(wait_queue_head_t *waitq_h,int condition); void wait_event_timeout(wait_queue_head_t *waitq_h,int condition); void wait_event_interruptible_timeout(wait_queue_head_t *waitq_h,int condition);
这但是等待队列的核心,咱们来看一下blog
wait_event
└── wait_event
└── _wait_event
├── abort_exclusive_wait
├── finish_wait
├── prepare_to_wait_event
└── ___wait_is_interruptible接口
244 #define wait_event(wq, condition) \ 245 do { \ 246 if (condition) \ 247 break; \ 248 __wait_event(wq, condition); \ 249 } while (0)
wait_event
--246-->若是condition为真,当即返回
--248-->不然调用__wait_event队列
194 #define ___wait_event(wq, condition, state, exclusive, ret, cmd) \ 195 ({ \ 206 for (;;) { \ 207 long __int = prepare_to_wait_event(&wq, &__wait, state);\ 208 \ 209 if (condition) \ 210 break; \ 212 if (___wait_is_interruptible(state) && __int) { \ 213 __ret = __int; \ 214 if (exclusive) { \ 215 abort_exclusive_wait(&wq, &__wait, \ 216 state, NULL); \ 217 goto __out; \ 218 } \ 219 break; \ 220 } \ 222 cmd; \ 223 } \ 224 finish_wait(&wq, &__wait); \ 225 __out: __ret; \ 226 })
___wait_event
--206-->死循环
--207-->进程进入睡眠
--209-->进程被wake_up唤醒,再次检查条件,若是条件为真,跳出循环,执行finish_wait(),wait_event()返回;若是醒来发现条件仍然不知足, 则执行下一个循环进入睡眠, 周而复始...
--212-->若是进程睡眠的方式是interruptible的,那么当中断来的时候也会abort_exclusive_wait被唤醒
--222-->若是上面两条都不知足,就会回调传入的schedule(),即继续睡眠
wait_event是睡在一个条件上, 内核还提供了下面的API进行无条件的睡眠, 只要被wake_up了就会醒来
//在等待队列上睡眠 sleep_on(wait_queue_head_t *wqueue_h); sleep_on_interruptible(wait_queue_head_t *wqueue_h);
条件不知足, wait_event就不会返回, 当前调用该接口的进程就会进入睡眠, 为了唤醒这个进程, 一般在另一个接口或中断处理程序中知足条件并调用wake_up, 另一个进程调用这个接口的时候,就会唤醒全部睡在这个条件上(这个等待队列头)的进程, 这个这样其实也实现了两个进程之间的"通讯"
//唤醒等待的进程 void wake_up(wait_queue_t *wqueue); void wake_up_interruptible(wait_queue_t *wqueue);
struct wait_queue_head_t xj_waitq_h; static ssize_t demo_read(struct file *filp, char __user *buf, size_t size, loff_t *offset) { if(!condition) //条件能够在中断处理函数或另外的接口中置位 wait_event_interruptible(&xj_waitq_h,condition); } static ssize_t demo_write(struct file *, const char __user *, size_t, loff_t *) { condition = 1; wake_up(&xj_waitq_h); } static file_operations fops = { .read = demo_read, .write= demo_write, }; static __init demo_init(void) { init_waitqueue_head(&xj_waitq_h); }
对于普通的非阻塞IO,咱们只须要在驱动中注册的read/write接口时不使用阻塞机制便可,这里我要讨论的是IO多路复用,即当驱动中的read/write并无实现阻塞机制的时候,咱们如何利用内核机制来在驱动中实现对IO多路复用的支持。下面这个就是咱们要用的API
int poll(struct file *filep, poll_table *wait); void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
当应用层调用select/poll/epoll机制的时候,内核其实会遍历回调相关文件的驱动中的poll接口,经过每个驱动的poll接口的返回值,来判断该文件IO是否有相应的事件发生,咱们知道,这三种IO多路复用的机制的核心区别在于内核中管理监视文件的方式,分别是位,数组,链表,但对于每个驱动,回调的接口都是poll。
struct wait_queue_head_t waitq_h; static unsigned int demo_poll(struct file *filp, struct poll_table_struct *pts) { unsigned int mask = 0; poll_wait(filp, &wwaitq_h, pts); if(counter){ mask = (POLLIN | POLLRDNORM); } return mask; } static struct file_operations fops = { .owner = THIS_MODULE, .poll = demo_poll, }; static __init demo_init(void) { init_waitqueue_head(&xj_waitq_h); }