Linuxカーネルマルチタスク:割り込みとタスクレット

コテイカと弟 前の記事で、マルチスレッドのトピックに触れました。 マルチタスクの種類、スケジューラ、スケジューリング戦略、スレッドステートマシンなどの基本概念を扱いました。



今回は、異なる視点から計画の問題にアプローチしたいと思います。 つまり、フローではなく、彼らの「弟」の計画についてお話します。 この記事は非常に膨大であることが判明したため、最後にいくつかの部分に分けることにしました。

  1. Linuxカーネルマルチタスク:割り込みとタスクレット
  2. Linuxカーネルマルチタスク:ワークキュー
  3. プロトスレッドと協調マルチタスク


3番目のパートでは、これらすべての異なるエンティティを一見して比較し、いくつかの有用なアイデアを抽出しようとします。 しばらくして、これらのアイデアをEmboxプロジェクトでどのように実践しか、そして小さなスカーフでほぼ完全なマルチタスクを使用してOSを起動した方法について説明します。



主なAPIについて説明し、実装機能を掘り下げて、特に計画作業に焦点を当てて、詳細に説明しようとします。



割り込みとその処理



ハードウェア割り込み( IRQ )は、ハードウェアからの外部非同期イベントであり、プログラムを一時停止し、このイベントを処理するためにプロセッサに制御を渡します。 ハードウェア割り込み処理は次のとおりです。

  1. 現在の制御フローは一時停止され、コンテキスト情報はフローに戻るために保存されます。
  2. ハンドラー関数( ISR )は、無効化されたハードウェア割り込みのコンテキストで実行されます。 ハンドラーは、この割り込みに必要なアクションを実行する必要があります。
  3. 機器には、割り込みが処理されたことが通知されます。 これで、新しい割り込みを生成できるようになります。
  4. コンテキストを復元して、割り込みを終了します。


ハンドラー関数は十分に大きくすることができますが、無効化されたハードウェア割り込みのコンテキストで実行される場合は許可されません。 したがって、割り込み処理を2つの部分に分割するというアイデアを思いつきました(Linuxでは上半分と下半分と呼ばれます)。



したがって、遅延割り込み処理に行き着きます。 Linuxは、この目的のためにタスクレットとワークキューを使用します。



タスクレット



つまり、 タスクレットは、独自のスタックもコンテキストも持たない、非常に小さなスレッドです。 このような「フロー」は、迅速かつ完全に機能します。 タスクレットの主な機能:



「フードの下」を見て、それらがどのように機能するかを見てみましょう。 まず、タスクレット構造自体( <linux / interrupt.h>で定義):

struct tasklet_struct { struct tasklet_struct *next; /*  tasklet     */ unsigned long state; /* TASKLET_STATE_SCHED  TASKLET_STATE_RUN */ atomic_t count; /*   ,  tasklet   */ void (*func)(unsigned long); /*   tasklet' */ unsigned long data; /* ,    func */ };
      
      





タスクレットを使用する前に、まず初期化する必要があります。

 /*   tasklet  */ void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data); DECLARE_TASKLET(name, func, data); DECLARE_TASKLET_DISABLED(name, func, data); /*  tasklet */
      
      





タスクレットのスケジュール設定は簡単です。タスクレットは、優先度に応じて2つのキューのいずれかに配置されます。 キューは、単一のリンクリストとして整理されます。 さらに、各CPUには独自のキューがあります。 これは、関数を使用して行われます。

 void tasklet_schedule(struct tasklet_struct *t); /*    */ void tasklet_hi_schedule(struct tasklet_struct *t); /*    */ void tasklet_hi_schedule_first(struct tasklet_struct *t); /*   */
      
      





タスクレットがスケジュールされると、 TASKLET_STATE_SCHEDに設定され、キューに追加されます。 彼がこの状態にある間、それを再び計画することはうまくいきません-この場合、何も起こりません。 taskletは、tasklet_struct構造体の次のフィールドで編成される計画キューの複数の場所に同時に配置することはできません。 ただし、これは、 <linux / list.h>などのオブジェクトフィールドを介してリンクされたリストには当てはまります。

実行のために、タスクレットには状態TASKLET_STATE_RUNが割り当てられます。 ちなみに、タスクレットは実行前にキューから出て、 TASKLET_STATE_SCHED状態削除されます。つまり、実行中に再びスケジュールできます。 これは、自分で行うことも、たとえば別のコアで中断することもできます。 ただし、後者の場合、彼は最初のコアで実行を完了した後にのみ呼び出されます。



興味深いことに、タスクレットはさらに再帰的にアクティブ化および非アクティブ化できます。 次の関数がこれを担当します。

 void tasklet_disable_nosync(struct tasklet_struct *t); /*  */ void tasklet_disable(struct tasklet_struct *t); /*     tasklet' */ void tasklet_enable(struct tasklet_struct *t); /*  */
      
      





タスクレットが非アクティブ化された場合、タスクレットは計画キューに追加できますが、再度アクティブ化されるまでプロセッサ上で実行されません。 さらに、タスクレットが複数回非アクティブ化された場合、それはまったく同じ回数アクティブ化される必要があります。構造体のカウントフィールドは、このためだけのものです。



タスクレットを強制終了することもできます。 このように:

 void tasklet_kill(struct tasklet_struct *t);
      
      





さらに、すでに計画されている場合、彼はタスクレットが実行された後にのみ殺されます。 突然タスクレットがそれ自体を計画する場合、この関数を呼び出す前に、彼がこれを行うことを禁止することを忘れてはなりません-これはプログラマーの良心によるものです。



最も興味深いのは、スケジューラーの役割を果たす関数です。

 static void tasklet_action(struct softirq_action *a); static void tasklet_hi_action(struct softirq_action *a);
      
      





それらはほとんど同一なので、両方の関数のコードを与えることは意味がありません。 しかし、ここでそれらを詳細に理解する価値があります:

 static void tasklet_action(struct softirq_action *a) { struct tasklet_struct *list; local_irq_disable(); list = __this_cpu_read(tasklet_vec.head); __this_cpu_write(tasklet_vec.head, NULL); __this_cpu_write(tasklet_vec.tail, &__get_cpu_var(tasklet_vec).head); local_irq_enable(); while (list) { struct tasklet_struct *t = list; list = list->next; if (tasklet_trylock(t)) { if (!atomic_read(&t->count)) { if (!test_and_clear_bit(TASKLET_STATE_SCHED, &t->state)) BUG(); t->func(t->data); tasklet_unlock(t); continue; } tasklet_unlock(t); } local_irq_disable(); t->next = NULL; *__this_cpu_read(tasklet_vec.tail) = t; __this_cpu_write(tasklet_vec.tail, &(t->next)); __raise_softirq_irqoff(TASKLET_SOFTIRQ); local_irq_enable(); } }
      
      





tasklet_trylock()およびtasklet_lock()関数の呼び出しに注意してください。 tasklet_trylock()は、タスクレットをTASKLET_STATE_RUNに設定し、タスクレットをブロックします。これにより、異なるCPUで同じタスクレットが実行されるのを防ぎます。



実際、これらのスケジューラー機能は協調マルチタスクを実装しており、これについては私の記事で詳しく調べました 。 関数は、タスクレットをスケジュールするときにトリガーされるsoftirqハンドラーとして登録されます。



上記のすべての機能の実装は、/ linux / interrupt.hおよびkernel / softirq.cを 含むファイルあります



続く



次の部分では、はるかに強力なメカニズムについて説明します。ワークキューは、割り込みの遅延処理にもよく使用されます。



PS広告用。 また、私たちのプロジェクトに興味のあるすべての人をcodefreeze.ruが主催する会議に招待したいと思います( ハブでの発表 )。 ライブでチャットしたり、主な悪役アボンダレフに興味深い質問をしたり、最後に直接批判することができます:)



All Articles