等待队列(阻塞IO)wait_queue_head_t

等待队列(阻塞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);
}

猜你喜欢

转载自blog.csdn.net/wyy626562203/article/details/81286793