Linux内核里的等待队列机制在做驱动开发时用的非常多,多用来实现阻塞式访问,下面简单总结了等待队列的四种用法,希望对读者有所帮助。
1. 睡眠等待某个条件发生(条件为假时睡眠):
睡眠方式:wait_event, wait_event_interruptible
唤醒方式:wake_up (唤醒时要检测条件是否为真,如果还为假则继续睡眠,唤醒前一定要把条件变为真)
2. 手工休眠方式一:
1)建立并初始化一个等待队列项
DEFINE_WAIT(my_wait) <==> wait_queue_t my_wait; init_wait(&my_wait);
2)将等待队列项添加到等待队列头中,并设置进程的状态
prepare_to_wait(wait_queue_head_t *queue, wait_queue_t *wait, int state)
3)调用schedule(),告诉内核调度别的进程运行
4)schedule返回,完成后续清理工作
finish_wait()
3. 手工休眠方式二:
1)建立并初始化一个等待队列项:
DEFINE_WAIT(my_wait) <==> wait_queue_t my_wait; init_wait(&my_wait);
2)将等待队列项添加到等待队列头中:
add_wait_queue
3)设置进程状态
__set_current_status(TASK_INTERRUPTIBLE);
4)schedule()
5)将等待队列项从等待队列中移除
remove_wait_queue()
其实,这种休眠方式相当于把手工休眠方式一中的第二步prepare_to_wait拆成两步做了,即prepare_to_wait <====>add_wait_queue + __set_current_status,其他都是一样的。
4. 老版本的睡眠函数sleep_on(wait_queue_head_t *queue):
将当前进程无条件休眠在给定的等待队列上,极不赞成使用这个函数,因为它对竞态没有任何保护机制。
一般情况下等待队列使用是有两个进程工作的,比如A进程待,B进程唤醒。
DECLARE_WAIT_QUEUE_HEAD(buffer);
A:while(1){
sleep_on(&buffer);
}
B:while(1){
schedule_timeout_uninterruptible(100);
wake_up(&buffer);
}
其实在内核中有种比较常用的是等待队列 wait_quit_t。
DECLARE_WAITQUEUE(wait,current);
add_wait_queue(&buffer,&wait);
这几天又看下这篇文章以及百度文档,发现其实上面这位仁兄已经把等待队列的用法说的很清楚了,还有就是百度的这篇文档把其中的原理也讲了。大家可以结合这两个一起看。
上次一使用add_wait_queue(&buffer,&wait)时,死机是因为没有使用schedule()这个函数,所以这个函数一直占用cpu所以就死机了。
http://wenku.baidu.com/view/129cbb235901020207409c64.html百度的这个文档好像不错,
以下是做的一个小实验,这种其实是手动配置的方式。
#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还提供了有关等待队列的操作,这些就是包装过的:
1) wait_queue_head_t my_queue; //定义等待队列头
2) init_waitqueue_head(&my_queue); //初始化队列头
如果觉得上边两步来的麻烦,可以直接使用DECLARE_WAIT_QUEUE_HEAD(name)
3) DECLARE_WAITQUEUE(name,tsk); //定义等待队列
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);
用于将等待队列wait添加到等待队列头指向的等待队列链表中 。
5) wait_event(queue, conditon);
wait_event_interruptible(queue, condition);//可以被信号打断
wait_event_timeout(queue, condition, timeout);
wait_event_interruptible_timeout(queue, condition, timeout);//不能被信号打断
queue:作为等待队列头的等待队列被唤醒
conditon:必须满足,否则阻塞
timeout和conditon相比,有更高优先级
6) void wake_up(wait_queue_head_t *queue);
void wake_up_interruptible(wait_queue_head_t *queue);
上述操作会唤醒以queue作为等待队列头的所有等待队列中所有属于该等待队列头的等待队列对应的进程。
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作用是一样的, 只不过它把进程状态置为TASK_INTERRUPTIBLE.