等待队列(阻塞IO)
等待队列在内核中有很多用途,尤其适合用于中断处理,进程同步及定时。进程经常必须等待某些事件的发生。例如,等待一个磁盘操作的终止,等待释放系统资源,或者等待时间经过固定的间隔。等待队列实现了在事件上的条件等待,希望等待特定事件的进程把自己放进合适的等待队列,并放弃控制权。等待队列表示一组睡眠的进程,当某一条件为真时,由内核唤醒它们。
![2018-07-30_094602](新建文本文档 (6).assets/2018-07-30_094602.jpg)
1.常用的数据结构
1.1 wait_queue_t
wait_queue_t定义
typedef struct __wait_queue wait_queue_t;
typedef int (*wait_queue_func_t)(wait_queue_t *wait, unsigned mode, int flags, void *key);
int default_wake_function(wait_queue_t *wait, unsigned mode, int flags, void *key);
struct __wait_queue {
unsigned int flags;
#define WQ_FLAG_EXCLUSIVE 0x01
void *private;
wait_queue_func_t func;
struct list_head task_list;
};
指定函数来唤醒进程
//等待队列到期执行时使用默认的default_wake_function函数来唤醒进程
static inline void init_waitqueue_entry(wait_queue_t *q, struct task_struct *p)
{
q->flags = 0;
q->private = p;
q->func = default_wake_function;
}
//可以指定等待队列到期执行的函数为用户指定的函数
static inline void init_waitqueue_func_entry(wait_queue_t *q,
wait_queue_func_t func)
{
q->flags = 0;
q->private = NULL;
q->func = func;
}
wait_queue_t的5种初始化
[1]
wait_queue_t wait;
init_waitqueue_entry(&wait, current); //将当前进程添加到容器中,current指的是当前进程
[2]
wait_queue_t wait = __WAITQUEUE_INITIALIZER(wait,current);
[3]
DECLARE_WAITQUEUE(wait,current); //current指的是当前进程
[4]
DEFINE_WAIT(wait)
[5]
wait_queue_t wait;
init_wait(&wait);
wait_queue_t的其他操作
//将等待队列添加到等待队列头
extern void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
//将等待队列添加到等待队列头尾部
extern void add_wait_queue_exclusive(wait_queue_head_t *q, wait_queue_t *wait);
//从等待队列头中移除待队列
extern void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
1.2 wait_queue_head_t
wait_queue_head_t定义
struct __wait_queue_head {
spinlock_t lock;
struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;
wait_queue_head_t的3种初始化
[1]
wait_queue_head_t wait_head;
init_waitqueue_head(&wait_head);
[2]
DECLARE_WAIT_QUEUE_HEAD(wait_head);
[3]
wait_queue_head_t wait_head = __WAIT_QUEUE_HEAD_INITIALIZER(wait_head);
2.唤醒宏接口
//唤醒等待队列头上的一个进程
#define wake_up(x) __wake_up(x, TASK_NORMAL, 1, NULL)
//唤醒等待队列头上nr个进程
#define wake_up_nr(x, nr) __wake_up(x, TASK_NORMAL, nr, NULL)
//唤醒等待队列头上的所有进程
#define wake_up_all(x) __wake_up(x, TASK_NORMAL, 0, NULL)
//与__wake_up相同,但在wait_queue_head_t中使用自旋锁调用
#define wake_up_locked(x) __wake_up_locked((x), TASK_NORMAL, 1)
//与__wake_up相同,但在wait_queue_head_t中使用自旋锁调用。
#define wake_up_all_locked(x) __wake_up_locked((x), TASK_NORMAL, 0)
//唤醒等待队列头上的一个进程,可被中断打断,直到中断执行完成,condition条件成立才被唤醒
#define wake_up_interruptible(x) __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)
//唤醒等待队列头上的nr个进程,可被中断打断,直到中断执行完成,condition条件成立才被唤醒
#define wake_up_interruptible_nr(x, nr) __wake_up(x, TASK_INTERRUPTIBLE, nr, NULL)
//唤醒等待队列头上的所有进程,可被中断打断,直到中断执行完成,condition条件成立才被唤醒
#define wake_up_interruptible_all(x) __wake_up(x, TASK_INTERRUPTIBLE, 0, NULL)
//可能函数还没执行完,就被唤起来进程给抢占了,这个函数能够保证wake_up完整的执行完成
#define wake_up_interruptible_sync(x) __wake_up_sync((x), TASK_INTERRUPTIBLE, 1)
//和wake_up一样,m指针传递给default_wake_function(DECLARE_WAITQUEUE)或autoremove_wake_function(DEFINE_WAIT)
#define wake_up_poll(x, m) __wake_up(x, TASK_NORMAL, 1, (void *) (m))
//和wake_up_locked一样,m指针传递给default_wake_function(DECLARE_WAITQUEUE)或autoremove_wake_function(DEFINE_WAIT)
#define wake_up_locked_poll(x, m) __wake_up_locked_key((x), TASK_NORMAL, (void *) (m))
//和wake_up_interruptible一样,m指针传递给default_wake_function(DECLARE_WAITQUEUE)或autoremove_wake_function(DEFINE_WAIT)
#define wake_up_interruptible_poll(x, m) __wake_up(x, TASK_INTERRUPTIBLE, 1, (void *) (m))
//和wake_up_interruptible_sync一样,m指针传递给default_wake_function(DECLARE_WAITQUEUE)或autoremove_wake_function(DEFINE_WAIT)
#define wake_up_interruptible_sync_poll(x, m) __wake_up_sync_key((x), TASK_INTERRUPTIBLE, 1, (void *) (m))
//旧的唤醒方法不建议使用
extern void sleep_on(wait_queue_head_t *q);
extern long sleep_on_timeout(wait_queue_head_t *q,signed long timeout);
extern void interruptible_sleep_on(wait_queue_head_t *q);
extern long interruptible_sleep_on_timeout(wait_queue_head_t *q,signed long timeout);
3.阻塞宏接口
//条件为假时进程睡眠,不可中断
wait_event(wq, condition)
//不可中断睡眠,当超过指定的timeout(单位是jiffies)时间,不管有没有wake_up,还是条件没满足,都要唤醒进程,此时返回的是0。在timeout时间内条件满足返回值为timeout或者1;
wait_event_timeout(wq, condition, timeout)
//可被信号中断的睡眠,被信号打断唤醒时,返回负值-ERESTARTSYS;wake_up时,条件满足的,返回0。
wait_event_interruptible(wq, condition)
//结合了wait_event_timeout和wait_event_interruptible_timeout两个的特点
wait_event_interruptible_timeout(wq, condition, timeout)
wait_event_hrtimeout(wq, condition, timeout)
wait_event_interruptible_hrtimeout(wq, condition, timeout)
wait_event_interruptible_exclusive(wq, condition)
wait_event_interruptible_locked(wq, condition)
wait_event_interruptible_locked_irq(wq, condition)
wait_event_interruptible_exclusive_locked(wq, condition)
wait_event_interruptible_exclusive_locked_irq(wq, condition)
wait_event_killable(wq, condition)
wait_event_lock_irq_cmd(wq, condition, lock, cmd)
wait_event_lock_irq(wq, condition, lock)
wait_event_interruptible_lock_irq_cmd(wq, condition, lock, cmd)
wait_event_interruptible_lock_irq(wq, condition, lock)
wait_event_interruptible_lock_irq_timeout(wq, condition, lock, timeout)
4.注意事项
//不可中断
wait_event 对应 wait_up
//可中断,推荐使用
wake_up_interruptible 对应 wait_event_interruptible
5.等待队列的使用方法
5.1 方法一
/* 分配一个读的等待队列头, 全局变量 */
wait_queue_head_t rwq;
/* 在驱动入口函数初始化 */
init_waitqueue_head(&wq);
/* 读取函数实现 */
//分配等待队列
wait_queue_t wait;
//将当前进程添加到容器中
init_waitqueue_entry(&wait, current);
//将当前进程添加到队列头中
add_wait_queue(&rwq, &wait);
//设置当前进程的状态
set_current_state(TASK_INTERRUPTIBLE);
//进入真正的休眠状态(CPU资源让给别的任务)
schedule();
set_current_state(TASK_RUNNING);
//将唤醒的进程从等待队列头所在的数据连中移除
remove_wait_queue(&rwq, &wait);
//一旦被唤醒,要判断是哪个原因引起的唤醒
if(signal_pending(current))
{
printk("RECV SIN!\n");
//返回用户空间的read
return -ERESTARTSYS;
}
else
{
//由于数据可用引起的唤醒,读取数据
copy_to_user(...);
}
5.1 方法二
/* 分配等待队列头 */
wait_queue_heat_t rwq;
/* 初始化等待队列头 */
init_waitqueue_head(&rwq);
/* 在read函数中调用 */
//wait_event/wait_evnet_timeout/wait_event_interruptible_timeout
wait_event_interruptible(&rwq, condition); //如果数据可用,condition为真,如果数据不可用,condition为假,当前进程就会进入休眠
/* 在中断处理函数或其他任务中调用一下函数即可实现唤醒进程 */
//wake_up/wake_up_interruptible
wake_up(condition);
6.驱动实例
6.1 驱动 wait_head_driver.c
/*
* 应用调用read的时候开启定时器3秒,进程休眠,定时器超时唤醒进程将数据传递到用户空间
*/
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <linux/wait.h>
#include <linux/sched.h>
#define DRIVER_NAME "waithead"
#define err(msg) printk(KERN_ERR "%s: " msg "\n", DRIVER_NAME)
#define __debug(fmt, arg...) printk(KERN_INFO fmt, ##arg)
static int major = 0;
static int minor = 0;
struct class *wq_class;
static struct device *wq_device;
struct timer_list timer;
/*定义等待队列头,该等待队列头属于该驱动程序*/
static wait_queue_head_t wait_queue;
static unsigned char key;
static unsigned char condition;
void wait_head_timer(unsigned long arg)
{
__debug("==========wait_head_timer==========\n");
condition = 1;
key = 100;
wake_up_interruptible(&wait_queue); //唤醒等待队列
}
static ssize_t wait_head_read(struct file *filp, char *buf, size_t count, loff_t *f_pos)
{
int key_num;
int len;
int ret_val;
//设置超时时间3s
mod_timer(&timer, jiffies + HZ * 3);
/* 该函数体内部会定义struct wait_queue结构体变量,并将将当前进程
* 添加到队列中睡眠,(wait_queue_head_t为等待队列链表的头,struct
* wait_queue记录着链表节点信息)
*/
wait_event_interruptible(wait_queue,condition);
key_num = key; //读取键值
len = min(sizeof(key_num), count);
ret_val = copy_to_user(buf, &key_num, len);
return (len - ret_val);
}
/* Driver Operation structure */
static struct file_operations key_fops = {
.owner = THIS_MODULE,
.read = wait_head_read,
};
static int __init wait_head_init(void)
{
int retval;
condition = 0;
init_timer(&timer);
timer.function= wait_head_timer;
add_timer(&timer);
//初始化等待队列头
init_waitqueue_head(&wait_queue);
major = register_chrdev(major, DRIVER_NAME, &key_fops);
if(major < 0){
err("register char device fail");
retval = major;
goto error_register;
}
wq_class=class_create(THIS_MODULE,DRIVER_NAME);
if(IS_ERR(wq_class)){
err("class create failed!");
retval = PTR_ERR(wq_class);
goto error_class;
}
wq_device=device_create(wq_class,NULL, MKDEV(major, minor), NULL,DRIVER_NAME);
if(IS_ERR(wq_device)){
err("device create failed!");
retval = PTR_ERR(wq_device);
goto error_device;
}
__debug("register myDriver OK! Major = %d\n", major);
__debug("===================wait_head_init====================\n");
return 0;
error_device:
class_destroy(wq_class);
error_class:
unregister_chrdev(major, DRIVER_NAME);
error_register:
return retval;
}
static void __exit wait_head_exit(void)
{
del_timer(&timer);
unregister_chrdev(major, DRIVER_NAME);
device_destroy(wq_class,MKDEV(major, minor));
class_destroy(wq_class);
__debug("\n===================wait_head_exit====================\n");
return;
}
module_init(wait_head_init);
module_exit(wait_head_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("wyy");
6.2 测试 wait_head_test.c
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
char *devname = "/dev/waithead";
int fd;
unsigned char key;
fd = open(devname, O_RDWR);
read(fd, &key, sizeof(key));
printf("the key = %d\n",key);
close(fd);
}