解析Linux核心的同步與互斥機制(六)

來源:互聯網
上載者:User

源出處: http://www.startos.com/linux/tips/2011011921499_6.html

4.2 wake_up 的實現細節

  \kernel \sched.c

  /*

  * The core wakeup function. Non-exclusive wakeups (nr_exclusive == 0) just

  * wake everything up. If it's an exclusive wakeup (nr_exclusive == small +ve

  * number) then we wake all the non-exclusive tasks and one exclusive task.

  *

  * There are circumstances in which we can try to wake a task which has already

  * started to run but is not in state TASK_RUNNING. try_to_wake_up() returns

  * zero in this (rare) case, and we handle it by continuing to scan the queue.

  */

  static void __wake_up_common(wait_queue_head_t *q, unsigned int mode,

  int nr_exclusive, int sync, void *key)

  {

  struct list_head *tmp, *next;

  list_for_each_safe(tmp, next, &q->task_list) {

  wait_queue_t *curr = list_entry(tmp, wait_queue_t, task_list);

  unsigned flags = curr->flags;

  if (curr->func(curr, mode, sync, key) &&

  (flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)

  break;

  }

  }

  // 迴圈結束的條件包括list_for_each_safe本身、排他性(flags & WQ_FLAG_EXCLUSIVE)以及喚醒非0個進程,!--nr_exclusive非常巧妙,當傳入0時,!--nr_exclusive的結果總是0,if條件不可能成立,就無法break,即表示喚醒所有的進程。對於非WQ_FLAG_EXCLUSIVE進程,由於(flags & WQ_FLAG_EXCLUSIVE)為0後,就不計算!--nr_exclusive,因此這個過程可以喚醒所有的非 WQ_FLAG_EXCLUSIVE進程。但遇到WQ_FLAG_EXCLUSEVE之後的任意進程無法喚醒。

  最終哪個進程運行是由 schedule決定的。

  /**

  * __wake_up - wake up threads blocked on a waitqueue.

  * @q: the waitqueue

  * @mode: which threads

  * @nr_exclusive: how many wake-one or wake-many threads to wake up

  * @key: is directly passed to the wakeup function

  */

  void fastcall __wake_up(wait_queue_head_t *q, unsigned int mode, int nr_exclusive, void *key)

  {

  unsigned long flags;

  spin_lock_irqsave(&q->lock, flags);

  __wake_up_common(q, mode, nr_exclusive, 0, key);

  //通用的wakeup,不可重新進入的,需要__wake_up對之進行封裝

  spin_unlock_irqrestore(&q->lock, flags);

  }

  EXPORT_SYMBOL(__wake_up);

  根據int nr_exclusive值喚醒對應的進程,只是更改了進程的狀態,具體何時運行由schedule決定。

  並沒有將喚醒的進程從等待隊列中刪除,只有當schedule獲得cpu時才從等待隊列中刪除。

  \include\linux\wait.h

  void FASTCALL(__wake_up(wait_queue_head_t *q, unsigned int mode, int nr, void *key));

  extern void FASTCALL(__wake_up_locked(wait_queue_head_t *q, unsigned int mode));

  extern void FASTCALL(__wake_up_sync(wait_queue_head_t *q, unsigned int mode, int nr));

  #define wake_up(x) __wake_up(x, TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE, 1, NULL)

  #define wake_up_nr(x, nr) __wake_up(x, TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE, nr, NULL)

  #define wake_up_all(x) __wake_up(x, TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE, 0, NULL)

  #define wake_up_interruptible(x) __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)

  #define wake_up_interruptible_nr(x, nr) __wake_up(x, TASK_INTERRUPTIBLE, nr, NULL)

  #define wake_up_interruptible_all(x) __wake_up(x, TASK_INTERRUPTIBLE, 0, NULL)

  各種wakeup通過宏定義的形式本質上就是一個函數__wake_up,但對外的介面不一樣,這樣對外的意義明確,相當於採用了預設參數,而不是在各個wakeup內部調用函數,省掉了函數調用的開銷。在實現代碼複用的同時保證了對外的明確介面,值得借鑒。

  #define wake_up_locked(x) __wake_up_locked((x), TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE)

  #define wake_up_interruptible_sync(x) __wake_up_sync((x),TASK_INTERRUPTIBLE, 1)

  5 獨佔等待和進階休眠

  5.1 獨佔等待

  當一個進程調用 wake_up 在等待隊列上,所有的在這個隊列上等待的進程被置為可啟動並執行。 這在許多情況下是正確的做法。但有時,可能只有一個被喚醒的進程將成功獲得需要的資源,而其餘的將再次休眠。這時如果等待隊列中的進程數目大,這可能嚴重降低系統效能。為此,核心開發人員增加了一個“獨佔等待”選項。它與一個正常的睡眠有 2 個重要的不同:

  2 當等待隊列入口設定了 WQ_FLAG_EXCLUSEVE 標誌,它被添加到等待隊列的尾部;否則,添加到頭部。因為喚醒一個WQ_FLAG_EXCLUSEVE標誌的進程後就不再喚醒其他任意類型的進程。添加在尾部可以保證FIFO。

  2 當 wake_up 被在一個等待隊列上調用, 它在喚醒第一個有 WQ_FLAG_EXCLUSIVE 標誌的進程後停止喚醒。但核心仍然會喚醒所有的非獨佔等待進程,因為所有的非WQ_FLAG_EXCLUSIVE進程在隊頭。

  採用獨佔等待要滿足以下條件:

  2 希望對資源進行有效競爭;

  2 當資源可用時,喚醒一個進程就足夠來完全消耗資源;

  2 所有使用該資源的進程都應採用統一的獨佔等待規則。

  使一個進程進入獨佔等待,可調用:

  void prepare_to_wait_exclusive(wait_queue_head_t *queue, wait_queue_t *wait, int state);

  注意:無法使用通用的wait_event 和它的變體宏函數來進行獨佔等待。

  此時需要使用休眠的進階特性,利用等待隊列的prepare_to_wait_exclusive和finish_wait介面函數手動編寫相關代碼,

  5.2 進階休眠的基本步驟:

  (1)分配和初始化一個 wait_queue_t 結構, 隨後將其添加到正確的等待隊列。

  (2)設定進程狀態,標記為休眠。TASK_RUNNING 意思是進程能夠運行。有 2 個狀態指示一個進程是在睡眠: TASK_INTERRUPTIBLE 和 TASK_UNTINTERRUPTIBLE。2.6 核心的驅動代碼通常不需要直接操作進程狀態。但如果需要這樣做使用的代碼是:

  void set_current_state(int new_state);

  在老的代碼中, 你常常見到如此的東西:current->state = TASK_INTERRUPTIBLE; 但是象這樣直接改變 current 是不推薦的,當資料結構改變時這樣的代碼將會失效。通過改變 current 狀態,只改變了調度器對待進程的方式,但進程還未讓出處理器。

  (3)最後一步是放棄處理器。 但必須先檢查進入休眠的條件。如果不做檢查會引入競態: 如果決定休眠後,在做上述準備工作到真正調用schedule時,若等待的條件變為真,不對條件重新進行判斷,則你可能錯過喚醒且長時間休眠。因此典型的代碼下:

  if (!condition) schedule();

  在調用schedule前,應對條件再次進行檢查。

  (4)更改進程狀態並將進程從等待隊列中刪除。

  如果代碼是從 schedule 返回,則進程肯定處於TASK_RUNNING 狀態。 如果不需睡眠而跳過對 schedule 的調用,必須將任務狀態重設為 TASK_RUNNING。無論是否調用過schedule,都需要從等待隊列中去除這個進程,否則它可能被多次喚醒。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.