基础知识:阻塞与非阻塞
阻塞操作是指在执行设备操作时若不能获得资源则挂起进程,直到满足可操作的条件后再进行操作。被挂起的进程进入休眠状态,被从调度器的运行队列移走,直到等待的条件被满足。而非阻塞操作的进程在不能进行设备操作时并不挂起,它或者放弃,或者不停地查询,直至可以进行操作为止。
驱动程序通常需要提供这样的能力: 当应用程序进行 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
/*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);
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
#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);