Linux待ち行列wait_queue_head_tとwait_queue_t

12964 ワード

Linux待ち行列wait_queue_head_tとwait_queue_t
August 18, 2015 7:46 PM
【整理自】http://blog.csdn.net/luoqindong/article/details/17840095
  • Linux待ち行列wait_queue_head_tとwait_queue_t
  • 定義
  • 作用
  • フィールド詳細
  • 操作
  • を定義して初期化する.
  • 待機キューヘッダから削除待機キュー
  • を追加する.
  • 待機イベント
  • ウェイクアップキュー
  • 待ち行列でスリープ


  • 待機キューはLinuxカーネルで重要な役割を果たし、多くのLinuxドライバが待機キューに関連しています.したがって、Linuxカーネルドライバでは、待機キューを把握することが必須です.Linuxカーネルの待ち行列は,二重ループチェーンテーブルをベースとしたデータ構造である.プロセススケジューリングメカニズムと密接に結合し、コアの非同期イベント通知メカニズムを実現するために使用することができる.待ち行列ヘッダwait_queue_head_t)と待ち行列エントリwait_queue_tの2つのデータ構造があります.待機キューヘッダおよび待機キューエントリには、「コネクタ」としてlist_headタイプのドメインが含まれています.2つのチェーンテーブルを介してtaskを待つヘッダと待機するプロセスリストをリンクします.具体的にご紹介します.
    定義#テイギ#
    ヘッダファイル:/include/linux/wait.h
    struct __wait_queue_head {
        spinlock_t lock;
        struct list_head task_list;
    };
    typedef struct __wait_queue_head wait_queue_head_t;

    さぎょう
    カーネルでは、キューを待つのに多くの用途があります.特に、処理の中断、プロセスの同期、タイミングなどの場合です.待機キューを使用して、ブロック・プロセスの起動を実現できます.キューに基づいたデータ構造で、プロセススケジューリングメカニズムと緊密に結合され、カーネル内の非同期イベント通知メカニズム、システムリソースへのアクセスの同期などを実現することができます.
    フィールドの詳細
  • spinlock_t lock; task_listと動作中に、このロックを使用して待ち行列への反発アクセスが実現される.
  • struct list_head task_list; 双方向ループチェーンテーブルは、待機中のプロセスを格納します.

  • 操作
    1.定義と初期化
    (1)
    wait_queue_head_t my_queue;
    init_waitqueue_head(&my_queue);

    直接定義して初期化します.init_waitqueue_head()関数は、スピンロックを未ロックに初期化し、キューが空の双方向ループチェーンテーブルに初期化されるのを待つ.
    (2)
    DECLARE_WAIT_QUEUE_HEAD(my_queue);

    (1)に相当する定義と初期化.
    (3)待ち行列の定義:
    DECLARE_WAITQUEUE(name,tsk);

    ここではwait_を定義します.queue_tタイプの変数nameをprivateをtskに設定します.wait_queue_tタイプは以下のように定義される.
    struct __wait_queue {
        unsigned int flags;
    #define WQ_FLAG_EXCLUSIVE 0x01
        struct task_struct * task;
        wait_queue_func_t func;
        struct list_head task_list;
    };
    
    typedef struct __wait_queue wait_queue_t;

    ここで、flagsドメインは、待機中のプロセスが反発プロセスであるか非反発プロセスであるかを示す.ここで、0は非反発プロセスであり、WQ_FLAG_EXCLUSIVE(0×01)は反発プロセスである.待ち行列wait_queue_tと待ち対列ヘッダwait_queue_head_tの違いは、待ち行列が待ち行列ヘッダのメンバーであることである.すなわち、待ち行列ヘッダのtask_listドメインリンクのメンバは、待ち行列タイプのwait_queue_tである.
    Linux等待队列wait_queue_head_t和wait_queue_t_第1张图片
    2.待機キューの追加/削除:
    (1)add_wait_queue()関数
    void fastcall 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);

    待機プロセスを非反発プロセスとして設定し、待機キューヘッダ(q)のキューヘッダに追加します.
    void fastcall add_wait_queue_exclusive(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_tail(q, wait);
        spin_unlock_irqrestore(&q->lock, flags);
    }
    EXPORT_SYMBOL(add_wait_queue_exclusive);

    この関数もadd_とwait_Queue()関数の機能は基本的に同じですが、待機中のプロセス(wait)を反発プロセスに設定します.
    (2)remove_wait_queue()関数:
    void fastcall remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)
    {
        unsigned long flags;
    
        spin_lock_irqsave(&q->lock, flags);
        __remove_wait_queue(q, wait);
        spin_unlock_irqrestore(&q->lock, flags);
    }
    EXPORT_SYMBOL(remove_wait_queue);

    3.待機イベント
    (1) wait_event()マクロ:
    #define wait_event(wq, condition)                   \ do {                                    \     if (condition)                          \         break;                          \     __wait_event(wq, condition);                    \ } while (0)
    
    
    #define __wait_event(wq, condition)                     \ do {                                    \     DEFINE_WAIT(__wait);                        \                                     \     for (;;) {                          \         prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE);    \         if (condition)                      \             break;                      \         schedule();                     \     }                               \     finish_wait(&wq, &__wait);                  \ } while (0)

    (2)wait_event_interruptible()関数:wait_event()との違いは、マクロを呼び出す待機中に現在のプロセスがTASK_INTERRUPTIBLEの状態に設定されることである.起動されるたびに、まずconditionが真であるかどうかをチェックし、真であれば戻る、そうでなければプロセスが信号によって起動された場合、-ERESTARTSYSのエラーコードを返すことをチェックする.conditionが真の場合は0を返す.
    (3)wait_event_timeout()マクロ:wait_event()と同様である.ただし、与えられた睡眠時間が負の場合は直ちに戻る.睡眠中に起動する場合、conditionが真である場合、残りの睡眠時間を返し、そうでない場合、所定の睡眠時間に達するまで睡眠を続け、その後、0を返す.
    (4)wait_event_interruptible_timeout()マクロ:wait_event_timeout()と同様であるが、睡眠中に信号が遮断するとERESTARTSYSのエラーコードが戻る.
    (5)wait_event_interruptible_exclusive()マクロ
    同様にwait_event_interruptible()と同様であるが、この睡眠のプロセスは反発プロセスである.
    4.起動キュー:
    (1)wake_up()関数:
    #define wake_up(x)          __wake_up(x, TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE, 1, NULL)
    
    /** * __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 */
    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);
        spin_unlock_irqrestore(&q->lock, flags);
    }

    待機キューを起動します.TASK_INTERRUPTIBLEおよびTASK_UNINTERUPTIBLEの状態にあるプロセスを起動することができ、wait_event/wait_event_timeoutとペアで使用することができる.
    (2)wake_up_interruptible()関数:
    #define wake_up_interruptible(x) __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)

    とwake_up()唯一の違いはTASKを呼び覚ますしかないことです.INTERUPTIBLE状態のプロセスwait_event_interruptible/wait_event_interruptible_timeout/ wait_event_interruptible_exclusiveとペアで使用する.
    (3)
    #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)
    

    これらもほぼwake_up/wake_up_interruptibleと同様である.
    5.待機キューでのスリープ:
    (1) sleep_on()関数:
    void fastcall __sched sleep_on(wait_queue_head_t *q)
    {
        SLEEP_ON_VAR
    
        current->state = TASK_UNINTERRUPTIBLE;
    
        SLEEP_ON_HEAD
        schedule();
        SLEEP_ON_TAIL
    }
    
    EXPORT_SYMBOL(sleep_on);
    
    
    #define SLEEP_ON_VAR \
        unsigned long flags;                \
        wait_queue_t wait;              \
        init_waitqueue_entry(&wait, current);
    
    
    #define SLEEP_ON_HEAD \
        spin_lock_irqsave(&q->lock,flags);      \
        __add_wait_queue(q, &wait);         \
        spin_unlock(&q->lock);
    
    #define SLEEP_ON_TAIL \
        spin_lock_irq(&q->lock);            \
        __remove_wait_queue(q, &wait);          \
        spin_unlock_irqrestore(&q->lock, flags);

    この関数は、待ち行列waitを定義し、現在のプロセスを待ち行列waitに追加し、現在のプロセスの状態をTASK_UNINTERRUPTIBLEに設定し、待ち行列waitを待ち行列ヘッダqに追加する役割を果たす.その後、リソースが取得できるまで保留され、待機キューヘッダqから起動され、待機キューヘッダから移動される.保留中にリソースを待機している間、プロセスは信号で起動できません.
    (2)sleep_on_timeout()関数:
    long fastcall __sched sleep_on_timeout(wait_queue_head_t *q, long timeout)
    {
        SLEEP_ON_VAR
    
        current->state = TASK_UNINTERRUPTIBLE;
    
        SLEEP_ON_HEAD
        timeout = schedule_timeout(timeout);
        SLEEP_ON_TAIL
     return timeout;
    }

    (3)interruptible_sleep_on()関数:
    void fastcall __sched interruptible_sleep_on(wait_queue_head_t *q)
    {
        SLEEP_ON_VAR
    
        current->state = TASK_INTERRUPTIBLE;
    
        SLEEP_ON_HEAD
        schedule();
        SLEEP_ON_TAIL
    }

    この関数とsleep_on()関数の唯一の違いは、現在のプロセスの状態をTASK_INTERRUPTINLEに設定することであり、これは、睡眠中にプロセスが信号を受信すると起動することを意味する.
    (4)interruptible_sleep_on_timeout()関数:
    long fastcall __sched sleep_on_timeout(wait_queue_head_t *q, long timeout)
    {
        SLEEP_ON_VAR
    
        current->state = TASK_UNINTERRUPTIBLE;
    
        SLEEP_ON_HEAD
        timeout = schedule_timeout(timeout);
        SLEEP_ON_TAIL
     return timeout;
    }
    sleep_on_timeout()関数と同様です.プロセスは、睡眠中に待機時間が到着しないうちに信号によって中断されて起動される可能性があり、待機時間が到着して起動される可能性がある.
    以上の4つの関数は、プロセスを待ち行列で眠らせているが、少し驚いているだけだ.実際に使う過程で、必要に応じて適切な関数を選んで使えばいい.例えば、FDDデータの読み書きでは、デバイスが準備ができていない場合、sleep_on()関数を呼び出してデータが読める(書き込み可能)までスリープし、シリアルポートを開いたとき、シリアルポートが閉じている場合、interruptible_sleep_on()関数を呼び出して開くのを待つ.サウンドカード駆動では、音声データを読み取る際に、データが読み取り可能でない場合、読み取り可能になるまで十分な時間を待つ.