Chinaunix首页 | 论坛 | 博客
  • 博客访问: 491869
  • 博文数量: 72
  • 博客积分: 1851
  • 博客等级: 上尉
  • 技术积分: 1464
  • 用 户 组: 普通用户
  • 注册时间: 2010-09-16 17:50
文章分类

全部博文(72)

文章存档

2013年(1)

2012年(17)

2011年(51)

2010年(3)

分类: LINUX

2011-11-20 22:56:20

linux同步方法中,有一种非常有效的同步数据结构:wait_queue_head_t,这里就具体分析其数据结构。

wait_head_t定义如下:

  1. struct __wait_queue_head {
  2.     spinlock_t lock;    //保护该链表的自旋锁
  3.     struct list_head task_list;//双向链表
  4. };
  5. typedef struct __wait_queue_head wait_queue_head_t;

双向链表中保存的节点定义如下:

  1. typedef struct __wait_queue wait_queue_t;
  2. struct __wait_queue {
  3.     unsigned int flags;
  4. #define WQ_FLAG_EXCLUSIVE    0x01
  5.     void *private;//存放睡眠的进程指针
  6.     wait_queue_func_t func;
  7.     struct list_head task_list;
  8. };

声明一个等待队列函数定义如下:

  1. #define DEFINE_WAIT(name)
  2.     wait_queue_t name = {    \
  3.         .private     = current,    \
  4.         .func        = autoremove_wake_function,    \
  5.         .task_list     = LIST_HEAD_INIT((name).task_list),    \
  6.     }

或采用另外一种初始化的方法:

  1. #define init_wait(wait)        \
  2.     do {
  3.         (wait)->private = current;    \
  4.         (wait)->func = autoremove_wake_function;    \//默认唤醒的回调函数
  5.         INIT_LIST_HEAD(&(wait)->task_list);        \
  6.     }while(0)

在添加一个元素进入等待队列时,可以指明唤醒进程的方式:唤醒所有的睡眠进程,只唤醒所有的睡眠进程,等待队列入口可以设置WQ_FLAG_EXCLUSIVE标志来进行区分。

等待队列入口设置了WQ_FLAG_EXCLUSIVE标志时,则会被添加到等待队列的尾部,而没有这个标志的入口就会被添加到头部。在某个等待队列上调用wake_up时,它会在唤醒第一个具有WQ_FLAG_EXCLUSIVE标志的进程之后停止唤醒其他进程。

添加一个元素进入等待队列:

  1. void fastcall add_wait_queue(wait_queue_head_t *q,wait_queue_t *wait)
  2. {
  3.     unsigned long flags;
  4.     
  5.     wait->flags &= ~WQ_FLAG_EXLUSIVE;
  6.     spin_lock_irqsave(&q->lock,flags);
  7.     __add_wait_queue(q,wait);//添加到等待队列的头部
  8.     spin_unlock_irqrestore(&q->lock,flags);
  9. }
  10. void fastcall add_wait_queue_exclusive(wait_queue_head_t *q,
  11.         wait_queue_t *wait)
  12. {
  13.     unsigned long flags;
  14.     wait->flags |= WQ_FLAG_EXCLUSIVE;
  15.     spin_lock_irqrestore(&q->lock,flags);
  16.     //添加元素到尾部的WQ_FLAG_EXCLUSIVE标志
  17.     __add_wait_queue_tail(q,wait);
  18.     spin_unlock_irqrestore(&q->lock,flags);
  19. }

上层对应的函数接口为:

  1. void fastcall prepare_to_wait(wait_queue_head_t *q,wait_queue_t *wait,int state)
  2. {
  3. unsigned long flags;
  4. wait->flags &= ~WQ_FLAG_EXCLUSIVE;
  5. spin_lock_irqsave(&q->lock,flags);
  6. if(list_empty(&wait->task_list))
  7. __add_wait_queue(q,wait);
  8. if(is_sync_wait(wait))//等待队列元素不为空,同步调用回调
  9. set_current_state(state);//等待下一次调度为睡眠状态
  10. spin_unlock_irqrestore(&q->lock,flags);
  11. }
  12. void fastcall prepare_to_wait_exclusive(wait_queue_head_t *q,wait_queue_t *wait,int state)
  13. {
  14. unsigned long flags;
  15. wait->flags |= WQ_FLAG_EXCLUSIVE;
  16. spin_lock_irqsave(&q->lock,flags);
  17. if( list_empty(&wait->task_list))
  18. __add_wait_queue_tail(q,wait);//加入队列尾部
  19. if(is_sync_wait(wait))
  20. set_current_state(state);
  21. spin_unlock_irqrestore(&q->lock,flags);
  22. }

上面两个函数直接设置进程的状态,在调用该函数之后,进程即可调用schedule,当然在这之前,进程应确保仍有必要等待,常见的处理流程如下:

  1. set_current_state(TASK_INTERUPTIBLE);
  2. if(need_to_sleep) schedule();

一旦schedule返回,就到了清理时间了。这就需要下面的函数:

  1. void fastcall finish_wait(wait_queue_head_t *q,wait_queue_t *wait)
  2. {
  3.     unsigned long flags;
  4.     __set_current_state(TASK_RUNNING);//设置进程状态
  5.     if(!list_empty_careful(&wait->task_list)) {//从等待队列中删除该元素
  6.         spin_lock_irqsave(&q->lock,flags);
  7.         list_del_init(&wait->task_list);
  8.         spin_unlock_irqrestore(&q->lock,flags);
  9.     }
  10. }

当满足条件后,系统开始唤醒睡眠在等待队列上的进程,唤醒函数如下:

  1. wake_up(wait_queue_head_t *queue);
  2. wake_up_interruptible(wait_queue_head_t *queue);
  3. wake_up_nr(wait_queue_head_t *queue,int nr);
  4. wake_up_interruptible_nr(wait_queue_head_t *queue,int nr);
  5. wake_up_all(wait_queue_head_t *queue);
  6. wake_up_intrruptible_all(wait_queue_head_t *queue);
  7. wake_up_intrruptible_sync(wait_queue_head_t *queue);

函数定义如下:

  1. //唤醒所有非独占进程且一个独占进程
  2. #define wake_up(x)    __wake_up(x,TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE,1,NULL)
  3. //唤醒所有非独占进程且nr个独占进程
  4. #define wake_up_nr(x,nr)    __wake_up(x,TASK_UNINTERRUPTIBLE|TASK_INTERRUPTIBLE,nr,NULL)
  5. //唤醒所有进程(不管是否独占或非独占)
  6. #define wake_up_all(x,TASK_UNINTERRUPTIBLE|TASK_INTERRUPTIBLE,0,NULL)
  7. //唤醒所有非独占,可中断进程以及独占可中断进程
  8. #define wake_up_interruptible(x) __wake_up(x,TASK_INTERRUPTIBLE,1,NULL)
  9. //唤醒所有非独占可中断进程以及nr个独占可中断进程
  10. #define wake_up_interruptible_nr(x,nr)    __wake_up(x,TASK_INTERRUPTIBLE,nr,NULL)
  11. //唤醒所有可中断进程
  12. #define wake_up_interruptible_all(x) __wake_up(x,TASK_INTERRUPTIBLE,0,NULL)
  13. //互斥唤醒所有可中断非独占进程以及一个可独占可中断进程
  14. #define wake_up_interruptible_sync(x)    __wake_up_sync((x),TASK_INTERRUPTILBE,1)

LDD介绍,被唤醒的进程可能会抢占当前的进程,并在wake_up返回前被调度到处理器上。换句话说,对wake_up的调用可能不是原子的。如果调用wake_up的进程运行在原子上下文(如拥有自旋锁或者是一个中断处理例程)中,则调度就不会发生。如果不希望在这时被调度出处理器,则可使用该函数,即如果包含有syncwake_up进程就不会抢占当前正在执行唤醒的进程,结果是唤醒的高优先级进程(与当前进程同一cpu)的执行出现延迟。

  1. //如果已经有wait_queue_head_t的自旋锁就调用该函数,其它的与wake_up一致
  2. #define wake_up_locked(x)    __wake_up_locked((x),TAKS_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE)

上面的函数都调用了__wake_up该函数如下:

  1. void fastcall __wake_up(wait_queue_t *q,unsigned int mode,int nr_exclusive,void *key)
  2. {
  3. unsigned long flags;
  4. spin_lock_irqsave(&q->lock,flags);
  5. __wake_up_common(q,mode,nr_exclusive,0,key);
  6. spin_unlock_irqrestore(&q->lock,flags);
  7. }
  8. void fastcall __wake_up_sync(wait_queue_head_t *q,unsigned int mode,int nr_exclusive)
  9. {
  10. unsigned long flags;
  11. int sync = 1;
  12. if(unlikely(!q)) return;
  13. if(unlikely(!nr_exclusive)) sync = 0;
  14. spin_lock_irqsave(&q->lock,flags);
  15. __wake_up_common(q,mode,nr_exclusive,sync,NULL);
  16. spin_unlock_irqrestore(&q->lock,flags);
  17. }
  18. void fastcall __wake_up_locked(wait_queue_head_t *q,unsigned int mode)
  19. {
  20. __wake_up_common(q,mode,1,0,NULL);
  21. }

__wake_up_common定义如下:

  1. static void __wake_up_common(wait_queue_head_t *q,unsigned int mode,
  2.     int nr_exclusive,int sync,void *key)
  3. {
  4.     wait_queue_t *curr,*next;
  5.     list_for_each_entry_safe(curr,next,&q->task_list,task_list) {
  6.         unsigned flags = curr->flags;
  7.         //调用默认的注册回调函数autoremove_wake_function
  8.         if(curr->func(curr,mode,sync,key) && (flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)
  9.             break;
  10.     }
  11. }

autoremove_wake_function则进行唤醒在队列上的进程(void*prviate):

  1. int autoremove_wake_function(wait_queue_t *wait,unsigned mode,int sync,
  2.         void *key)
  3. {
  4.     int ret = default_wake_function(wait,mode,sync,key);//唤醒该进程
  5.     
  6.     if(ret)
  7.         list_del_init(&wait->task_list);//从等待队列中删除该进程
  8.     return ret;
  9. }

  10. int default_wake_function(wait_queue_t *curr,unsigned mode,int sync,void *key)
  11. {
  12.     return try_to_wake_up(curr->private,mode,sync);
  13. }

参考资料

1.linux-2.6.24.3内核源码(linux/wait.c sched.c,include/wait.h)

2.linux设备驱动程序

3.深入理解linux内核(第三版)

阅读(3045) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~