《Linux设备驱动开发详解》-- 等待队列

基础知识:阻塞与非阻塞

        阻塞操作是指在执行设备操作时若不能获得资源则挂起进程,直到满足可操作的条件后再进行操作。被挂起的进程进入休眠状态,被从调度器的运行队列移走,直到等待的条件被满足。而非阻塞操作的进程在不能进行设备操作时并不挂起,它或者放弃,或者不停地查询,直至可以进行操作为止。
        驱动程序通常需要提供这样的能力: 当应用程序进行 read()、 write()等系统调用时,若设备的资源不能获取,而用户又希望以阻塞的方式访问设备,驱动程序应在设备驱动的 xxx_read()、xxx_write()等操作中将进程阻塞直到资源可以获取,此后,应用程序的 read()、write()等调用才返回,整个过程仍然进行了正确的设备访问,用户并没有感知到;若用户以非阻塞的方式访问设备文件,则当设备资源不可获取时,设备驱动的 xxx_read()、xxx_write()等操作应立即返回,read()、write()等系统调用也随即被返回。
        阻塞从字面上听起来似乎意味着低效率,实则不然,如果设备驱动不阻塞,则用户想获取设备资源只能不停地查询,这反而会无谓地耗费 CPU 资源。而阻塞访问时,不能获取资源的进程将进入休眠,它将 CPU 资源让给其他进程。

        因为阻塞的进程会进入休眠状态,因此,必须确保有一个地方能够唤醒休眠的进程。唤醒进程的地方最大可能发生在中断里面,因为硬件资源获得的同时往往伴随着一个中断。

8.1.1  等待队列
        在 Linux 驱动程序中,可以使用等待队列(waitqueue)来实现阻塞进程的唤醒。wait  queue 很早就作为一个基本的功能单位出现在 Linux 内核里了,它以队列为基础数据结构,与进程调度机制紧密结合,能够用于实现内核中的异步事件通知机制。等待队列可以用来同步对系统资源的访问,第 7 章中所讲述的信号量在内核中也依赖等待队列来实现。

Linux 2.6 提供如下关于等待队列的操作。
1.定义“等待队列头”。
wait_queue_head_t my_queue; 
2.初始化“等待队列头”。
init_waitqueue_head(&my_queue); 
而下面的 DECLARE_W AIT_QUEUE_HEAD()宏可以作为定义并初始化等待队列头的“快捷方式” 。
DECLARE_WAIT_QUEUE_HEAD (name) 
3.定义等待队列。
DECLARE_WAITQUEUE(name, tsk) 
该宏用于定义并初始化一个名为 name 的等待队列。
4.添加/移除等待队列。
void  fastcall  add_wait_queue(wait_queue_head_t  *q,  wait_queue_t *wait); 
void  fastcall  remove_wait_queue(wait_queue_head_t  *q,  wait_queue_t *wait); 

add_wait_queue()用于将等待队列 wait 添加到等待队列头 q 指向的等待队列链表中, 而 remove_wait_queue()用于将等待队列 wait 从附属的等待队列头 q 指向的等待队列链表中移除。
5.等待事件。
wait_event(queue, condition) 
wait_event_interruptible(queue, condition) 
wait_event_timeout(queue, condition, timeout) 
wait_event_interruptible_timeout(queue, condition, timeout) 

第一个参数 queue 作为等待队列头的等待队列被唤醒,而且第二个参数condition 必须满足,否则阻塞。wait_event()和 wait_event_interruptible()的区别在于后者可以被信号打断,而前者不能。加上_timeout 后的宏意味着阻塞等待的超时时间,以 jiffy 为单位,在第三个参数的 timeout 到达时,不论 condition 是否满足,均返回。
wait()的定义如代码清单 8.3 所示,从其源代码可以看出,当 condition 满足时,wait_event()会立即返回,否则,阻塞等待 condition 满足。

6.唤醒队列
void wake_up(wait_queue_head_t *queue);
void wake_up_interruptible(wait_queue_head_t *queue);
上述操作会唤醒以queue作为等待队列头的所有等待队列对应的进程。
wake_up() <---> wait_event()
 wait_event_timeout()
wake_up_interruptible() <---> wait_event_interruptible() 
 wait_event_interruptible_timeout()
 
 
wake_up()可以唤醒处于TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE的进程
wake_up_interruptble()只能唤醒处于TASK_INTERRUPTIBLE的进程。
 
 
 
 
7. 在等待队列上睡眠
sleep_on(wait_queue_head_t *q);
interruptible_sleep_on(wait_queue_head_t *q);
 
 
sleep_on()函数的作用就是将当前进程的状态置成TASK_UNINTERRUPTIBLE,定义一个等待队列,并把它添加到等待队列头q,直到支援获得,q引导的等待队列被唤醒。
interruptible_sleep_on()与sleep_on()函数类似,其作用是将目前进程的状态置成TASK_INTERRUPTIBLE,并定义一个等待队列,之后把它附属到等待队列头q,直到资源可获得,q引导的等待队列被唤醒或者进程收到信号。 
 
 
sleep_on() <---> wake_up() 
interruptible_sleep_on() <---> wake_up_interruptible()


另转:

例程1

  1. /*a simple wait_queue demo
     *task_1,task_2 added into the wait_queue, if condition is 0.
     *task_3 change condition to 1, and task_1 task_2 will be wake up
     */
    
    #include <linux/kernel.h>
    #include <linux/init.h>
    #include <linux/module.h>
    #include <linux/sched.h>
    #include <linux/kthread.h>
    #include <linux/delay.h>
    
    MODULE_LICENSE("GPL");
    MODULE_AUTHOR("[email protected]");
    
    static int condition;
    static struct task_struct *task_1;
    static struct task_struct *task_2;
    static struct task_struct *task_3;
    
    DECLARE_WAIT_QUEUE_HEAD(wq);
    
    
    static int thread_func_1(void *data)
    {
        int i = 0;
        while (i++ < 100) {
            wait_event(wq, condition == 1);
            msleep(1000);
            printk(">>>>>this task 1\n");
        }
        return 0;
    }
    
    static int thread_func_2(void *data)
    {
        int i = 0;
        while (i++ < 100) {
            wait_event(wq, condition == 1);
            msleep(1000);
            printk(">>>>>this task 2\n");
        }
        return 0;
    }
    static int thread_func_3(void *data)
    {
        int i = 0;
        while (i++ < 10) {
            condition = 0;
            msleep(2000);
            printk(">>>>>this task 3\n");
            condition = 1;
            wake_up(&wq);
            msleep(2000);
        }
        return 0;
    }
    
    
    
    static int __init mod_init(void)
    {
        printk("=====mod set up===\n");
        condition = 0;
    
        task_1 = kthread_run(thread_func_1, NULL, "thread%d", 1);
        if (IS_ERR(task_1))
            printk("**********create thread 1 failed\n");
        else
            printk("======success create thread 1\n");
    
        task_2 = kthread_run(thread_func_2, NULL, "thread%d", 2);
        if (IS_ERR(task_2))
            printk("**********create thread 2 failed\n");
        else
            printk("======success create thread 2\n");
    
        task_3 = kthread_run(thread_func_3, NULL, "thread%d", 3);
        if (IS_ERR(task_3))
            printk("**********create thread 3 failed\n");
        else
            printk("======success create thread 3\n");
        return 0;
    }
    
    static void __exit mod_exit(void)
    {
        int ret;
        if (!IS_ERR(task_1)) {
            ret = kthread_stop(task_1);
            if (ret > 0)
                printk("<<<<<<<<<thread 1 has run %ds\n", ret);
        }
            
        if (!IS_ERR(task_2)) {
            ret = kthread_stop(task_2);
            if (ret > 0)
                printk("<<<<<<<<<thread 2 has run %ds\n", ret);
        }
    
        if (!IS_ERR(task_3)) {
            ret = kthread_stop(task_3);
            if (ret > 0)
                printk("<<<<<<<<<thread 3 has run %ds\n", ret);
        }
    }
    module_init(mod_init);
    module_exit(mod_exit);

    Makefile:
  2. KERNEL_DIR:=/lib/modules/`uname -r`/build
    PWD:=`pwd`
    obj-m:= wq_mod.o
    default:
    make -C $(KERNEL_DIR) M=$(PWD) modules
    clean:
    make -C $(KERNEL_DIR) M=$(PWD) clean


  3. 例程2:
  4. #include <linux/init.h>
    #include <linux/module.h>
    #include <linux/sched.h>
    #include <linux/semaphore.h>
    #include <linux/interrupt.h>
    #include <linux/slab.h>
    #include <linux/types.h>
    #include <linux/unistd.h>
    #include <linux/kernel.h>
    
    static DECLARE_WAIT_QUEUE_HEAD(myqueue);
    
    int hello(void)
    {    
        DECLARE_WAITQUEUE(wait,current);
        daemonize("hello");    
        add_wait_queue(&myqueue,&wait);
        printk("hello\n");
        set_current_state(TASK_INTERRUPTIBLE);
        schedule();
        remove_wait_queue(&myqueue,&wait);
        return 0;
    
    }
    
    int H_init(void)
    {
        int res = 0;
        printk(KERN_ALERT "Hello ...\n");
        kernel_thread(hello,NULL,CLONE_FS | CLONE_FILES | CLONE_SIGHAND | SIGCHLD );
        return res;
    }
    
    void H_exit(void)
    {
        wake_up(&myqueue);
        printk(KERN_ALERT "Bye ...\n");
    }
    static int __init hdrv_init(void)
    {
        printk(KERN_ALERT "driver loading ...\n");
        return H_init();
        
    }
    
    
    static void __exit hdrv_exit(void)
    {
        printk(KERN_ALERT " driver unloaded.\n");
        H_exit();
    }
    MODULE_LICENSE("GPL");
    module_init(hdrv_init);
    module_exit(hdrv_exit);



你可能感兴趣的:(《Linux设备驱动开发详解》-- 等待队列)