linux中的等待隊列

最近看epoll 和 select 都涉及到一個東西叫作設備等待隊列,等待隊列是如何工做的,內核是怎麼管理的?看這篇文章html

  1. 問題:進程是如何組織起來的?
    咱們知道,進程是有不少種狀態的:include/linux/sched.h
    #define TASK_RUNNING        0
    #define TASK_INTERRUPTIBLE    1
    #define TASK_UNINTERRUPTIBLE    2
    #define __TASK_STOPPED        4
    #define __TASK_TRACED        8
    /* in tsk->exit_state */
    #define EXIT_ZOMBIE        16
    #define EXIT_DEAD        32
    等等。
    那麼,對於不一樣狀態的進程,內核是如何來管理的呢?
    • 就緒隊列:狀態爲TASK_RUNNING的進程組成的列表;
    • 處於TASK_STOPPED、EXIT_ZOMBIE或者EXIT_DEAD狀態的進程是不須要鏈接進特定鏈表的。由於對於這些狀態的進程而言,父進程只會經過 PID或者子進程鏈表來進行訪問。
    • 而處於TASK_INTERRUPTIBLE、TASK_UNINTERRUPTIBLE狀態的進程分爲不少種類型,其每一個進程對應一 種特定事件。在這種狀況下,進程的狀態信息是不能提供足夠的信息去快速的檢索所需進程,所以有必要介紹一些其餘的鏈表組織結構。好比等待隊列。
  2. 等待隊列:
    在內核裏面,等待隊列是有不少用處的,尤爲是在中斷處理、進程同步、定時等場合。咱們主要討論其在進程同步中的應用。
    有時候,一個進程可能要等待一些事件的發生,如磁盤操做結束、一些系統資源的釋放等等。我的理理解:等待隊列就是暫時存放等待某些事件發生的進程的集合。若是一個進程要等待一個事件發生,那麼該進程便將自身放入相應的等待隊列中進入睡眠,而放棄控制權,直到等待事件發生後纔會被內核喚醒。

  3. 等待隊列的結構:


    • 等待隊列是以雙循環鏈表的形式實現的,並且隊列中的成分(等待隊列項)包含了指向進程描述符task的指針。
    • 等待隊列頭:include/linux/wait.h
      每個等待隊列是有一個等待隊列頭,等待隊列頭是一個wait_queue_head_t的數據結構:
      struct __wait_queue_head {
      spinlock_t lock;
          struct list_head task_list;
      };
      typedef struct __wait_queue_head wait_queue_head_t;
      成員說明:
      lock:由於等待隊列是不容許多個進程同時進行訪問的,以防產生不可預料的結果,所以在此結構中定義了"自旋鎖"以實現訪問間的同步。
      task_list:用於實現雙向鏈表形式。
      struct list_head {
          struct list_head *next, *prev;
      };
    • 等待隊列項:include/linux/wait.h
      struct __wait_queue {
          unsigned int flags;
      struct task_struct *task;(2.6.25.5中是void *private;)
          wait_queue_func_t func;
          struct list_head task_list;
      };
      typedef struct __wait_queue wait_queue_t;

      等待隊列中的每個成分:等待隊列項,表明着一個正在等待特定事件發生的睡眠進程。
      成員解釋:
      task:存放着睡眠進程狀態描述符的地址;
      task_list:用於將進程連接進等待相同事件發生的進程鏈表中(等待隊列)。
      flag:

      互斥進程(exclusive processes)和非互斥進程:

      咱們來考慮一下,若是等待的事件發生了、變爲真的了,那麼是否是要喚醒等待該事件的全部進程(某個等待隊列中)呢?
         老是喚醒全部等待該事件的進程並不必定是合適的。好比考慮這樣一種狀況:若是隊列中的多個進程等待的資源是要互斥訪問的,必定時間內只容許一個進程去訪問的話,這時候,只須要喚醒一個進程就能夠了,其餘進程繼續睡眠。若是喚醒全部的進程,最終也只有一個進程得到該資源,其餘進程讓需返回睡眠。

      所以,等待隊列中的睡眠進程可被劃分爲互斥、非互斥進程。
         互斥進程:等待的資源是互斥訪問的;互斥進程由內核有選擇的喚醒,等待隊列項的flag字段爲1;
         非互斥進程:等待的資源是可多進程同時訪問的。非互斥進程在事件發生時,老是被內核喚醒,等待隊列元素的flag字段爲0。

      func:
      指定等待隊列中的睡眠進程如何被喚醒。



  4. 等待隊列的建立:include/linux/wait.h
    DECLARE_WAITQUEUE()
    init_waitqueue_head()
    能夠用DECLARE_WAIT_QUEUE_HEAD(name)宏定義一個新的等待隊列,該宏靜態地聲明和初始化名爲name的等待隊列頭變量。 init_waitqueue_head()函數用於初始化已動態分配的wait queue head變量。
    等待隊列能夠經過 DECLARE_WAITQUEUE()靜態建立,也能夠用 init_waitqueue_head()動態建立。進程把本身放入等待隊列中並設置成不可執行狀態。 例:
    The init_waitqueue_entry(q, p) function initializes a wait_queue_t structure q as follows:

        q->flags = 0;
        q->task = p;
        q->func = default_wake_function;
    非互斥進程p(flags = 0)將被default_wake_function函數喚醒,而default_wake_function喚醒函數是try_to_wake_up( )的包裝而已。

    DEFINE_WAIT:
    能夠用宏DEFINE_WAIT聲明一個新的wait_queue_t變量(等待隊列項),而且對其進行初始化:
    #define DEFINE_WAIT(name)                        \
        wait_queue_t name = {                        \
            .private    = current,                \
            .func        = autoremove_wake_function,        \
            .task_list    = LIST_HEAD_INIT((name).task_list),    \
        }

  5. 等待隊列的添加和刪除:
    • add_wait_queue( ):kernel/wait.c
      add_wait_queue_exclusive( )
      add_wait_queue()函數把一個非互斥進程插入等待隊列鏈表的第一個位置;

      在wait.c中:
      void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)
      {
          unsigned long flags;

          wait->flags &= ~WQ_FLAG_EXCLUSIVE;
      spin_lock_irqsave(&q->lock, flags);
          __add_wait_queue(q, wait);
      spin_unlock_irqrestore(&q->lock, flags);
      }
      EXPORT_SYMBOL(add_wait_queue);
      內嵌內核函數__add_wait_queue(),而且使用了所機制對該操做進行互斥保護。

      add_wait_queue_exclusive( )函數把一個互斥進程插入等待隊列鏈表的最後一個位置;
    • remove_wait_queue( ):
      remove_wait_queue( )函數從等待隊列鏈表中刪除一個進程;
    • waitqueue_active( ):
      waitqueue_active( )函數檢查一個給定的等待隊列是否爲空。

  6. 等待隊列的使用:睡眠和喚醒:/kernel/sched.c
    該組函數使用任務管理中公用形式的等待隊列。
    但願等待一個特定事件的進程能調用下列函數中的任一個:
    • 睡眠操做:思想是更改當前進程(CURRENT)的任務狀態,並要求從新調度,由於這時這個進程的狀態已經改變,再也不在調度表的就緒隊列中,所以沒法再得到執行機會,進入"睡眠"狀態,直至被"喚醒"(wake_up()),即其任務狀態從新被修改回就緒態。
      經常使用的睡眠操做有interruptible_sleep_on和sleep_on,兩個函數相似,是把調用進程加入到特定的等待隊列中,只不過前者將進程的狀態從就緒態 (TASK_RUNNING)設置爲TASK_INTERRUPTIBLE,容許經過發送signal喚醒它(便可中斷的睡眠狀態);然後者將進程的狀態 設置爲TASK_UNINTERRUPTIBLE,在這種狀態下,不接收任何singal。

      在當前進程上操做的sleep_on()函數:
      void sleep_on(wait_queue_head_t *wq)
          {
              wait_queue_t wait;
              /* 構造當前進程對應的等待隊列項 */
      init_waitqueue_entry(&wait, current);  //wait.h中
              /* 將當前進程的狀態從TASK_RUNNING改成TASK_UNINTERRUPTIBLE */
              current->state = TASK_UNINTERRUPTIBLE;  
              /* 將等待隊列項添加到指定鏈表中 */  
              wq_write_lock_irqsave(&q->lock,flags);
              __add_wait_queue(q, &wait); 
              wq_write_unlock(&q->lock);

           /* 進程從新調度,放棄執行權 */
               schedule( );


              /* 本進程被喚醒,從新得到執行權,首要之事是將等待隊列項從鏈表中刪除 */
              wq_write_lock_irq(&q->lock);
               __remove_wait_queue(q, &wait);
              wq_write_unlock_irqrestore(&q->lock,flags);
          /* 至此,等待過程結束,本進程能夠正常執行下面的邏輯 */
          }
      該函數把當前進程的狀態設置爲TASK_UNINTERRUPTIBLE,並把它插入到特定的等待隊列。而後,它調用調度程序,而調度程序從新開始另外一個進程的執行。當睡眠進程被喚醒時,調度程序從新開始執行sleep_on()函數,把該進程隊列中刪除。

    • interruptible_sleep_on():
      interruptible_sleep_on()與sleep_on()函數基本上是同樣的,可是interruptible_sleep_on()把 當前進程的狀態設置爲TASK_INTERRUPTIBLE而不是TASK_UNINTERRUPTIBLE,所以,接受一個信號就能夠喚醒當前進程
    • sleep_on_timeout()interruptible_sleep_on_timeout()於上述兩個函數相似,只是他們還容許調用者定義一個時間間隔使得進程被內核喚醒;可是,在這兩個函數中調用的是schedule_timeout()來代替schedule()。
    • prepare_to_wait()、prepare_to_wait_exclusive()、finish_wait():在wait.c中:
      是在linux2.6中介紹的,將當前進程放入等待隊列的另外一種方式。做用同sleep_on()。


    • 對應的喚醒操做包括wake_up_interruptible和wake_up。wake_up函數不只能夠喚醒狀態爲 TASK_UNINTERRUPTIBLE的進程,並且能夠喚醒狀態爲TASK_INTERRUPTIBLE的進程。 wake_up_interruptible只負責喚醒狀態爲TASK_INTERRUPTIBLE的進程。這兩個宏的定義以下:
      #define wake_up(x)   __wake_up((x),TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE, 1)
      #define wake_up_interruptible(x) __wake_up((x),TASK_INTERRUPTIBLE, 1)
      __wake_up函數主要是獲取隊列操做的鎖,具體工做是調用__wake_up_common完成的。
      void __wake_up(wait_queue_head_t *q, unsigned int mode, int nr)
      {
          if (q) {
              unsigned long flags;
              wq_read_lock_irqsave(&q->lock, flags);
              __wake_up_common(q, mode, nr, 0);
              wq_read_unlock_irqrestore(&q->lock, flags);
          }
      }
      參數q表示要操做的等待隊列,mode表示要喚醒任務的狀態,如TASK_UNINTERRUPTIBLE或TASK_INTERRUPTIBLE等。nr_exclusive是要喚醒的互斥進程數目,在這以前遇到的非互斥進程將被無條件喚醒。sync表示???
      {
          struct list_head *tmp;
          struct task_struct *p;
          CHECK_MAGIC_WQHEAD(q);
          WQ_CHECK_LIST_HEAD(&q->task_list);
          /* 遍歷等待隊列 */
          list_for_each(tmp,&q->task_list) {
              unsigned int state;
              /* 得到當前等待隊列項 */
              wait_queue_t *curr = list_entry(tmp, wait_queue_t, task_list);
              CHECK_MAGIC(curr->__magic);
              /* 得到對應的進程 */
              p = curr->task;
              state = p->state;
              /* 若是咱們須要處理這種狀態的進程 */
              if (state & mode) {
                  WQ_NOTE_WAKER(curr);
                  if (try_to_wake_up(p, sync) && (curr->flags&WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)
                      break;
              }
          }
      }

      其餘的還有wake_up_nr, wake_up_all, wake_up_interruptible_nr, wake_up_interruptible_all, wake_up_interruptible_sync, wake_up_locked. 
          

參考文章:
http://hi.baidu.com/abigbigman/blog/item/a0a1fb54f2faa85cd009065f.html
http://linux.chinaunix.net/techdoc/system/2008/03/08/982296.shtmllinux

相關文章
相關標籤/搜索