免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 847 | 回复: 0
打印 上一主题 下一主题

等待队列 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2008-05-22 09:29 |只看该作者 |倒序浏览

最近学习Linux Driver,学习到wait queue时,仔细查了一下,发现这篇文章比较好,所以转载过来。介绍的还是很清楚的。
wait queue很早就作为一个基本的功能单位出现在Linux内核里了,它以队列为基础数据结构,与进程调度机制紧密结合,能够用于实现核心的异步事件通知机制。我们从它的使用范例着手,看看等待队列是如何实现异步信号功能的。
在核心运行过程中,经常会因为某些条件不满足而需要挂起当前线程,直至条件满足了才继续执行。在2.4内核中提供了一组新接口来实现这样的功能,下面的代码节选自kernel/printk.c:
   unsigned long log_size;
1:  DECLARE_WAIT_QUEUE_HEAD(log_wait);...
4:  spinlock_t console_lock = SPIN_LOCK_UNLOCKED;...
    int do_syslog(int type,char *buf,int len){
        ...
2:      error=wait_event_interruptible(log_wait,log_size);
        if(error)
             goto out;
        ...
5:      spin_lock_irq(&console_lock);
        ...
        log_size--;
        ...
6:        spin_unlock_irq(&console_lock);
        ...
    }
    asmlinkage int printk(const char *fmt,...){
        ...
7:        spin_lock_irqsave(&console_lock,flags);
        ...
        log_size++;...
8:        spin_unlock_irqrestore(&console_lock,flags);
3:      wake_up_interruptible(&log_wait);
        ...
    }
这段代码实现了printk调用和syslog之间的同步,syslog需要等待printk送数据到缓冲区,因此,在2:处等待log_size非0;而printk一边传送数据,一边增加log_size的值,完成后唤醒在log_wait上等待的所有线程(这个线程不是用户空间的线程概念,而是核内的一个执行序列)。执行了3:的wake_up_interruptible()后,2:处的wait_event_interruptible()返回0,从而进入syslog的实际动作。
1:是定义log_wait全局变量的宏调用。
在实际操作log_size全局变量的时候,还使用了spin_lock自旋锁来实现互斥,关于自旋锁,这里暂不作解释,但从这段代码中已经可以清楚的知道它的使用方法了。
所有wait queue使用上的技巧体现在wait_event_interruptible()的实现上,代码位于include/linux/sched.h中,前置数字表示行号:
779 #define __wait_event_interruptible(wq, condition, ret)                  \
780 do {                                                  \
781         wait_queue_t __wait;                          \
782         init_waitqueue_entry(&__wait, current);       \
783                                                       \
784         add_wait_queue(&wq, &__wait);                 \
785         for (;;) {                                    \
786                 set_current_state(TASK_INTERRUPTIBLE);\
787                 if (condition)                        \
788                         break;                        \
789                 if (!signal_pending(current)) {       \
790                         schedule();                   \
791                         continue;                     \
792                 }                                     \
793                 ret = -ERESTARTSYS;                   \
794                 break;                                \
795         }                                             \
796      current->state =TASK_RUNNING;                    \
797         remove_wait_queue(&wq, wait);                 \
798 } while (0)
799         
800 #define wait_event_interruptible(wq, condition)       \
801 ({                                                    \
802         int __ret = 0;                                \
803         if (!(condition))                             \
804          __wait_event_interruptible(wq, condition, __ret);    \
805         __ret;                                        \
806 })
在wait_event_interruptible()中首先判断condition是不是已经满足,如果是则直接返回0,否则调用__wait_event_interruptible(),并用__ret来存放返回值。__wait_event_interruptible()首先定义并初始化一个wait_queue_t变量__wait,其中数据为当前进程结构current(struct task_struct),并把__wait入队。在无限循环中,__wait_event_interruptible()将本进程置为可中断的挂起状态,反复检查condition是否成立,如果成立则退出,如果不成立则继续休眠;条件满足后,即把本进程运行状态置为运行态,并将__wait从等待队列中清除掉,从而进程能够调度运行。如果进程当前有异步信号(POSIX的),则返回-ERESTARTSYS。
挂起的进程并不会自动转入运行的,因此,还需要一个唤醒动作,这个动作由wake_up_interruptible()完成,它将遍历作为参数传入的log_wait等待队列,将其中所有的元素(通常都是task_struct)置为运行态,从而可被调度到,执行__wait_event_interruptible()中的代码。
struct __wait_queue_head log_wait = {
        lock:        SPIN_LOCK_UNLOCKED,
        task_list:      { &log_wait.task_list, &log_wait.task_list }
}
其中task_list是struct list_head变量,包括两个list_head指针,一个next、一个prev,这里把它们初始化为自身,属于队列实现上的技巧,其细节可以参阅关于内核list数据结构的讨论,add_wait_queue()和remove_wait_queue()就等同于list_add()和list_del()。
wait_queue_t结构在include/linux/wait.h中定义,关键元素即为一个struct task_struct变量表征当前进程。
除了wait_event_interruptible()/wake_up_interruptible()以外,与此相对应的还有wait_event()和wake_up()接口,interruptible是更安全、更常用的选择,因为可中断的等待可以接收信号,从而挂起的进程允许被外界kill。


本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u2/67707/showart_698568.html
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP