内核随记(三)--同步(2)

内核随记(三)--同步(2)


2.2、睡眠与唤醒

在操作系统中,睡眠和唤醒原语实际上是操作系统的基本原语,也是实现同步的一种方式,而且它还是实现信号量的基础。当进程请求的资源(如内存、文件等)不能得到满足时,就会主动放弃CPU,进入等待状态(可中断等待或者不可中断等待)。当资源满足时,就会由别的进程唤醒,从而投入运行。

2.2.1、等待队列

等待队列表示一组睡眠的进程,这些进程正在等待特定的事件发生(或者说条件为真),比如,等待足够的内存。等待队列是一个双链表,每个队列都有一个队列头,其定义如下:

[html]  view plain copy print ?
  1. //include/linux/wait.h  
  2. //等待队列头  
  3. struct __wait_queue_head {  
  4.       // 自旋锁  
  5.     spinlock_t lock;  
  6.     struct list_head task_list;  
  7. };  
  8. typedef struct __wait_queue_head wait_queue_head_t;  

等待队列链表中的元素类型为:

[html]  view plain copy print ?
  1. typedef struct __wait_queue wait_queue_t;  
  2. //唤醒函数指针  
  3. typedef int (*wait_queue_func_t)(wait_queue_t *wait, unsigned mode, int sync, void *key);  
  4. //默认的唤醒函数  
  5. int default_wake_function(wait_queue_t *wait, unsigned mode, int sync, void *key);  
  6.   
  7. struct __wait_queue {  
  8.     /*取值为WQ_FLAG_EXCLUSIVE(=1)表示互斥进程,由内核有选择的唤醒.为0时表示非互斥进程,由内核在  
  9.     **事件发生时唤醒所有等待进程.  
  10.     **/  
  11.     unsigned int flags;  
  12. #define WQ_FLAG_EXCLUSIVE    0x01  
  13.     //等待的任务描述符  
  14.     struct task_struct * task;  
  15.   
  16.     //唤醒函数,默认为default_wake_function  
  17.     wait_queue_func_t func;  
  18.     struct list_head task_list;  
  19. };  

其典型的结构如下:


等待队列头的初始化:
DECLARE_WAIT_QUEUE_HEAD(name);
其定义如下:

[html]  view plain copy print ?
  1. //incude/linux/wait.h  
  2. #define __WAIT_QUEUE_HEAD_INITIALIZER(name) {                \  
  3.     .lock        = SPIN_LOCK_UNLOCKED,                \  
  4.     .task_list    = { &(name).task_list, &(name).task_list } }  
  5.   
  6. //初始化等待队列头  
  7. #define DECLARE_WAIT_QUEUE_HEAD(name) \  
  8.     wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name)  

或者如下:
wait_queue_head_t my_queue; 
init_waitqueue_head(&my_queue);

等待队列元素初始化:

// linux/wait.h
// wait_queue_t初始化
static  inline  void  init_waitqueue_entry(wait_queue_t  * q,  struct  task_struct  * p)
{
    q
-> flags  =   0 ;
    q
-> task  =  p;
    q
-> func  =  default_wake_function;
}

2.2.2、等待事件(Waiting on the Event)
内核提供的等待接口包括wait_event(), wait_event_ interruptible(), 和wait_event_interruptible_timeout()。此外sleep_on(), sleep_on_timeout(), 和interruptible_sleep_on()在2.6中仍然支持,但已经过时。这些接口的基本实现如下:

具体代码如下:

[html]  view plain copy print ?
  1. //linux/wait.h  
  2. #define wait_event(wq, condition)                     \  
  3. do {                                    \  
  4.     if (condition)    //条件发生                         \  
  5.         break;                            \  
  6.     __wait_event(wq, condition);                    \  
  7. } while (0)  
  8.   
  9. #define __wait_event(wq, condition)                     \  
  10. do {                                    \  
  11.     DEFINE_WAIT(__wait);                        \  
  12.                                     \  
  13.     for (;;) {                            \  
  14.         prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE);    \  
  15.         if (condition)                        \  
  16.             break;                        \  
  17.         schedule();//调度                        \  
  18.     }                                \  
  19.     finish_wait(&wq, &__wait);                    \  
  20. } while (0)  
  21.   
  22. //kernel/wait.c  
  23. void fastcall  
  24. prepare_to_wait(wait_queue_head_t *q, wait_queue_t *wait, int state)  
  25. {  
  26.     unsigned long flags;  
  27.     //非互斥进程  
  28.     wait->flags &= ~WQ_FLAG_EXCLUSIVE;  
  29.     //关中断,并请求自旋锁  
  30.     spin_lock_irqsave(&q->lock, flags);  
  31.     if (list_empty(&wait->task_list))  
  32.         __add_wait_queue(q, wait);  //将等待任务加入等待队列  
  33.     /*  
  34.      * don't alter the task state if this is just going to  
  35.      * queue an async wait queue callback  
  36.      */  
  37.     if (is_sync_wait(wait))  
  38.         set_current_state(state);  //设置任务当前的状态  
  39.     //释放自旋锁,并恢复处理器状态  
  40.     spin_unlock_irqrestore(&q->lock, flags);  
  41. }  
  42.   
  43. //等待完成之后,应该设置任务的状态为运行状态,并从等待队列中删除  
  44. void fastcall finish_wait(wait_queue_head_t *q, wait_queue_t *wait)  
  45. {  
  46.     unsigned long flags;  
  47.   
  48.     __set_current_state(TASK_RUNNING); //设置为运行状态  
  49.       
  50.     if (!list_empty_careful(&wait->task_list)) {  
  51.         spin_lock_irqsave(&q->lock, flags);  
  52.         list_del_init(&wait->task_list);    //从等待队列中删除  
  53.         spin_unlock_irqrestore(&q->lock, flags);  
  54.     }  
  55. }  

2.2.3、唤醒(Waking Up)
接口如下:

[html]  view plain copy print ?
  1. //include/inux/wait.h  
  2. #define wake_up(x)            __wake_up(x, TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE, 1, NULL)  
  3. #define wake_up_nr(x, nr)        __wake_up(x, TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE, nr, NULL)  
  4. #define wake_up_all(x)            __wake_up(x, TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE, 0, NULL)  
  5. #define wake_up_interruptible(x)    __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)  
  6. #define wake_up_interruptible_nr(x, nr)    __wake_up(x, TASK_INTERRUPTIBLE, nr, NULL)  
  7. #define wake_up_interruptible_all(x)    __wake_up(x, TASK_INTERRUPTIBLE, 0, NULL)  
  8. #define    wake_up_locked(x)        __wake_up_locked((x), TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE)  
  9. #define wake_up_interruptible_sync(x)   __wake_up_sync((x),TASK_INTERRUPTIBLE, 1)  

具体实现:

[html]  view plain copy print ?
  1. //kernel/sched.c  
  2. void fastcall __wake_up(wait_queue_head_t *q, unsigned int mode,  
  3.                 int nr_exclusive, void *key)  
  4. {  
  5.     unsigned long flags;  
  6.     //请求自旋锁,并关中断  
  7.     spin_lock_irqsave(&q->lock, flags);  
  8.     __wake_up_common(q, mode, nr_exclusive, 0, key);  
  9.     spin_unlock_irqrestore(&q->lock, flags);  
  10. }  
  11. static void __wake_up_common(wait_queue_head_t *q, unsigned int mode,  
  12.                  int nr_exclusive, int sync, void *key)  
  13. {  
  14.     struct list_head *tmp, *next;  
  15.   
  16.     list_for_each_safe(tmp, next, &q->task_list) {  
  17.         wait_queue_t *curr;  
  18.         unsigned flags;  
  19.         curr = list_entry(tmp, wait_queue_t, task_list);  
  20.         flags = curr->flags;  
  21.         //调用相应的唤醒函数, 唤醒第1个有WQ_FLAG_EXCLUSIVE标志的进程后停止  
  22.         if (curr->func(curr, mode, sync, key) &&  
  23.             (flags & WQ_FLAG_EXCLUSIVE) &&  
  24.             !--nr_exclusive)  
  25.             break;  
  26.     }  
  27. }  
  28. //默认的唤醒函数  
  29. int default_wake_function(wait_queue_t *curr, unsigned mode, int sync, void *key)  
  30. {  
  31.     task_t *p = curr->task;  
  32.     return try_to_wake_up(p, mode, sync);  
  33. }  

try_to_wake_up是唤醒原语中核心部分,其具体代码如下:

[html]  view plain copy print ?
  1. /*p被唤醒的进程描述符.  
  2. **state被唤醒的进程的状态掩码  
  3. **sync禁止被唤醒的进程抢占本地CPU正在运行的进程  
  4. */  
  5. static int try_to_wake_up(task_t * p, unsigned int state, int sync)  
  6. {  
  7.     int cpu, this_cpu, success = 0;  
  8.     unsigned long flags;  
  9.     long old_state;  
  10.     runqueue_t *rq;  
  11. #ifdef CONFIG_SMP  
  12.     unsigned long load, this_load;  
  13.     struct sched_domain *sd;  
  14.     int new_cpu;  
  15. #endif  
  16.     //关闭中断,并获取最后执行该进程的CPU(可能不同于本地CPU)的运行队列的锁  
  17.     rq = task_rq_lock(p, &flags);  
  18.     schedstat_inc(rq, ttwu_cnt);  
  19.     old_state = p->state;  
  20.     if (!(old_state & state))  
  21.         goto out;  
  22.   
  23.     if (p->array)  
  24.         goto out_running;  
  25.       
  26.     //最后执行该任务的CPU  
  27.     cpu = task_cpu(p);  
  28.     //本地CPU  
  29.     this_cpu = smp_processor_id();  
  30.       
  31. /*对于多CPU系统,检查要被唤醒的进程是否应该从最近执行该进程的CPU的运行队列,  
  32. **转移到另外一个CPU的运行队列.  
  33. */  
  34. #ifdef CONFIG_SMP  
  35.     if (unlikely(task_running(rq, p)))  
  36.         goto out_activate;  
  37.   
  38.     new_cpu = cpu;  
  39.   
  40.     if (cpu == this_cpu || unlikely(!cpu_isset(this_cpu, p->cpus_allowed)))  
  41.         goto out_set_cpu;  
  42.   
  43.     load = source_load(cpu);  
  44.     this_load = target_load(this_cpu);  
  45.   
  46.     /*  
  47.      * If sync wakeup then subtract the (maximum possible) effect of  
  48.      * the currently running task from the load of the current CPU:  
  49.      */  
  50.     if (sync)  
  51.         this_load -SCHED_LOAD_SCALE;  
  52.   
  53.     /* Don't pull the task off an idle CPU to a busy one */  
  54.     if (load < SCHED_LOAD_SCALE/2 && this_load > SCHED_LOAD_SCALE/2)  
  55.         goto out_set_cpu;  
  56.   
  57.     new_cpu = this_cpu; /* Wake to this CPU if we can */  
  58.   
  59.     /*  
  60.      * Scan domains for affine wakeup and passive balancing  
  61.      * possibilities.  
  62.      */  
  63.     for_each_domain(this_cpu, sd) {  
  64.         unsigned int imbalance;  
  65.         /*  
  66.          * Start passive balancing when half the imbalance_pct  
  67.          * limit is reached.  
  68.          */  
  69.         imbalance = sd->imbalance_pct + (sd->imbalance_pct - 100) / 2;  
  70.   
  71.         if ((sd->flags & SD_WAKE_AFFINE) &&  
  72.                 !task_hot(p, rq->timestamp_last_tick, sd)) {  
  73.             /*  
  74.              * This domain has SD_WAKE_AFFINE and p is cache cold  
  75.              * in this domain.  
  76.              */  
  77.             if (cpu_isset(cpu, sd->span)) {  
  78.                 schedstat_inc(sd, ttwu_wake_affine);  
  79.                 goto out_set_cpu;  
  80.             }  
  81.         } else if ((sd->flags & SD_WAKE_BALANCE) &&  
  82.                 imbalance*this_load <= 100*load) {  
  83.             /*  
  84.              * This domain has SD_WAKE_BALANCE and there is  
  85.              * an imbalance.  
  86.              */  
  87.             if (cpu_isset(cpu, sd->span)) {  
  88.                 schedstat_inc(sd, ttwu_wake_balance);  
  89.                 goto out_set_cpu;  
  90.             }  
  91.         }  
  92.     }  
  93.   
  94.     new_cpu = cpu; /* Could not wake to this_cpu. Wake to cpu instead */  
  95. out_set_cpu:  
  96.     schedstat_inc(rq, ttwu_attempts);  
  97.     new_cpu = wake_idle(new_cpu, p);  
  98.     if (new_cpu != cpu && cpu_isset(new_cpu, p->cpus_allowed)) {  
  99.         schedstat_inc(rq, ttwu_moved);  
  100.         set_task_cpu(p, new_cpu);  
  101.         task_rq_unlock(rq, &flags);  
  102.         /* might preempt at this point */  
  103.         rq = task_rq_lock(p, &flags);  
  104.         old_state = p->state;  
  105.         if (!(old_state & state))  
  106.             goto out;  
  107.         if (p->array)  
  108.             goto out_running;  
  109.   
  110.         this_cpu = smp_processor_id();  
  111.         cpu = task_cpu(p);  
  112.     }  
  113.   
  114. out_activate:  
  115. #endif /* CONFIG_SMP */  
  116.     if (old_state == TASK_UNINTERRUPTIBLE) {  
  117.         rq->nr_uninterruptible--;  
  118.         /*  
  119.          * Tasks on involuntary sleep don't earn  
  120.          * sleep_avg beyond just interactive state.  
  121.          */  
  122.         p->activated = -1;  
  123.     }  
  124.   
  125.     /*  
  126.      * Sync wakeups (i.e. those types of wakeups where the waker  
  127.      * has indicated that it will leave the CPU in short order)  
  128.      * don't trigger a preemption, if the woken up task will run on  
  129.      * this cpu. (in this case the 'I will reschedule' promise of  
  130.      * the waker guarantees that the freshly woken up task is going  
  131.      * to be considered on this CPU.)  
  132.      */  
  133.      //将进程p加入目标CPU的可运行队列  
  134.     activate_task(p, rq, cpu == this_cpu);  
  135.     /*如果没有设置sync标志(表示允许抢占),且目标CPU不是本地CPU,则检查p是否比rq运行队列中当前进程的动态优先级高.  
  136.     **即(p)->prio < (rq)->curr->prio,如果是,则调用resched_task()抢占rq->curr。  
  137.     */  
  138.     if (!sync || cpu != this_cpu) {  
  139.         if (TASK_PREEMPTS_CURR(p, rq))  
  140.             /*在单CPU中,仅仅设置TIF_NEED_RESCHED标志.多CPU系统中,则检查相应标志,并使目标CPU重新调度  
  141.             */  
  142.             resched_task(rq->curr);  
  143.     }  
  144.     success = 1;  
  145.   
  146. out_running:  
  147.     //设置进程的状态  
  148.     p->state = TASK_RUNNING;  
  149. out:  
  150.     //释放rq的锁,并打开本地中断  
  151.     task_rq_unlock(rq, &flags);  
  152.   
  153.     return success;  
  154. }  
  155.   
  156. #ifdef CONFIG_SMP  
  157. //多CPU系统  
  158. static void resched_task(task_t *p)  
  159. {  
  160.     int need_resched, nrpolling;  
  161.   
  162.     BUG_ON(!spin_is_locked(&task_rq(p)->lock));  
  163.   
  164.     /* minimise the chance of sending an interrupt to poll_idle() */  
  165.     nrpolling = test_tsk_thread_flag(p,TIF_POLLING_NRFLAG);  
  166.     need_resched = test_and_set_tsk_thread_flag(p,TIF_NEED_RESCHED);  
  167.     nrpolling |= test_tsk_thread_flag(p,TIF_POLLING_NRFLAG);  
  168.       
  169.     if (!need_resched && !nrpolling && (task_cpu(p) != smp_processor_id()))  
  170.         //产生IPI,强制目标CPU重新调度  
  171.         smp_send_reschedule(task_cpu(p));  
  172. }  
  173. #else  
  174. //单CPU系统  
  175. static inline void resched_task(task_t *p)  
  176. {  
  177.     set_tsk_need_resched(p);  
  178. }  
  179. #endif  

2.2.4、互斥等待
当调用wake_up唤醒等待队列时,等待队列上的所有进程都转置为可运行。在一些情况下,这种做法是正确的,比如等待某个特定的事件。但是在另外一些情况,可以提前知道只有一个被唤醒的进程能够成功的获取资源,比如等待临界区资源,其它的进程将再次睡眠。如果等待队列中的进程数量太大,将会严重影响系统性能,这就是所谓的thundering herd行为。为此,内核引入互斥等待,它与非互斥等待的区别如下:
(1) 当一个等待队列入口有 WQ_FLAG_EXCLUSEVE 标志置位, 它被添加到等待队列的尾部. 没有这个标志的入口项, 相反, 添加到开始。
(2) 当 wake_up 被在一个等待队列上调用, 它在唤醒第一个有 WQ_FLAG_EXCLUSIVE 标志的进程后停止。
这样,进行互斥等待的进程一次只唤醒一个。使一个进程进入互斥等待是调用prepare_to_wait_exclusive完成的。

[html]  view plain copy print ?
  1. //kernel/wait.c  
  2. void fastcall  
  3. prepare_to_wait_exclusive(wait_queue_head_t *q, wait_queue_t *wait, int state)  
  4. {  
  5.     unsigned long flags;  
  6.     //互斥标志  
  7.     wait->flags |= WQ_FLAG_EXCLUSIVE;  
  8.     spin_lock_irqsave(&q->lock, flags);  
  9.     if (list_empty(&wait->task_list))  
  10.         __add_wait_queue_tail(q, wait);  
  11.     /*  
  12.      * don't alter the task state if this is just going to  
  13.       * queue an async wait queue callback  
  14.      */  
  15.     if (is_sync_wait(wait))  
  16.         set_current_state(state);  
  17.     spin_unlock_irqrestore(&q->lock, flags);  
  18. }  

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值