見出し画像

カーネルパッチ勉強会:関数のコメント誤り

こんにちは、ゆたかさんです。いつもはTwitterで #カーネルパッチ勉強会 を投稿しているのですが、今回は特別バージョンとしてnoteでやってみることにしました。

カーネルバグ修正の元ネタ

今回取り上げるのはLinuxカーネル5.2.2のstableブランチで修正されたもので、下記になります。

修正内容を見てみよう

Linuxカーネルのパッチというと、ソースコードの修正が主ですが、今回の修正内容は「ソースコードのコメント」のみが対象となっています。ちょっと一安心しますね。以下に修正内容をそのまま転記しました。


diff --git a/kernel/irq/manage.c b/kernel/irq/manage.c
index 54a41da..df8498d 100644
--- a/kernel/irq/manage.c
+++ b/kernel/irq/manage.c
@@ -96,7 +96,8 @@ EXPORT_SYMBOL(synchronize_hardirq);
 *	to complete before returning. If you use this function while
 *	holding a resource the IRQ handler may need you will deadlock.
 *
- *	This function may be called - with care - from IRQ context.
+ *	Can only be called from preemptible code as it might sleep when
+ *	an interrupt thread is associated to @irq.
 */
void synchronize_irq(unsigned int irq)
{

「This function may be called - with care - from IRQ context.」という一文が削除され、新しい一文が追加されています。この変更はどういった意味があるのでしょうか?

英文の意味について考える

「This function may be called - with care - from IRQ context.」は「この関数は割り込みコンテキストから呼び出されるかもしれないから気を付けましょう」という意味です。

新しい一文である「Can only be called from preemptible code as it might sleep when an interrupt thread is associated to @irq.」は「割り込みスレッドが@irq引数に紐付いている場合、関数はスリープすることので、ユーザコンテキストからしか呼び出せません」という意味になります。

 /**
 *	synchronize_irq - wait for pending IRQ handlers (on other CPUs)
 *	@irq: interrupt number to wait for
 *
 *	This function waits for any pending IRQ handlers for this interrupt
 *	to complete before returning. If you use this function while
 *	holding a resource the IRQ handler may need you will deadlock.
 *
- *	This function may be called - with care - from IRQ context.
+ *	Can only be called from preemptible code as it might sleep when
+ *	an interrupt thread is associated to @irq.
 */

関数のコメントの全文を読んでみることにしましょう。

synchronize_irq - wait for pending IRQ handlers (on other CPUs)
@irq: interrupt number to wait for
synchronize_irq - 他のCPUで動作している仕掛かり中のIRQハンドラの終了を待ちます。
@irq: 待ち合わせをする割り込み番号

This function waits for any pending IRQ handlers for this interrupt
to complete before returning. If you use this function while
holding a resource the IRQ handler may need you will deadlock.
この関数は、関数が戻る前に仕掛かり中のIRQハンドラの終了を待ち合わせます。
もし、IRQハンドラのリソースをつかんだまま、この関数を呼び出すと、デッドロックが
起こるかもしれません。

何やら難しい言葉が並んでいますが、synchronize_irq関数を呼び出すには条件があるので気を付けなさいね、と言っているようです。

ユーザコンテキストと割り込みコンテキスト

Linuxにはユーザ空間とカーネル空間という2つの空間があります。ユーザ空間では普段使っているユーザプロセス(アプリケーション)が動作しています。カーネル空間はLinuxカーネルだけが使える空間で、ユーザ空間からは直接干渉できないようになっています。

ユーザ空間とカーネル空間の違いは、例えるなら、この世とあの世。仏教用語で言うなら、浄土と穢土。ユーザ空間とカーネル空間の狭間では、門番から3つの選択を迫られるのです。おいきなさい、と。

そして、ユーザプロセスからカーネルの機能を呼び出す流れをユーザコンテキストといいます。ちょうど、水が上から下へ流れるさまです。そう、まるでウォーターフォールモデルのように。
反対に、ハードウェアから割り込みが発生して、カーネルの機能を呼び出す流れを割り込みコンテキストといいます。ちょうど、水道の蛇口が壊れて、噴水のごとく水が噴き出すさまです。そう、まるで仕様変更の入ったウォーターフォールモデルのように。

Linuxではユーザコンテキストと割り込みコンテキストの2種類しかありません。

割り込み処理は割り込みコンテキストで動くので、割り込み処理が完了したどうかはユーザコンテキスト側で監視する必要があります。ユーザコンテキストでは長い処理をさせることができますが、割り込みコンテキストでは処理をすぐに終わらせないといけません。

カーネル関数の処理内容

ここで関数の実装を見てみましょう。synchronize_irq()では__synchronize_hardirq()を呼び出しています。

void synchronize_irq(unsigned int irq)
{
   struct irq_desc *desc = irq_to_desc(irq);

   if (desc) {
       __synchronize_hardirq(desc, true);
       wait_event(desc->wait_for_threads,
              !atomic_read(&desc->threads_active));
   }
}

__synchronize_hardirq()では割り込み処理の状態が変化するまで、ループで延々と待つようになっています。whileループではcpu_relax()という関数を呼んでいますが、その実体はただのNOP(のっぷ)です。CPUに対してNOP命令を実行することで、ハイパースレッディングの切り替えタイミングにヒントを与えることができます。

static void __synchronize_hardirq(struct irq_desc *desc, bool sync_chip)
{
   struct irq_data *irqd = irq_desc_get_irq_data(desc);
   bool inprogress;

   do {
       unsigned long flags;

       while (irqd_irq_inprogress(&desc->irq_data))
           cpu_relax();

       raw_spin_lock_irqsave(&desc->lock, flags);
       inprogress = irqd_irq_inprogress(&desc->irq_data);

       if (!inprogress && sync_chip) {
           __irq_get_irqchip_state(irqd, IRQCHIP_STATE_ACTIVE,
                       &inprogress);
       }
       raw_spin_unlock_irqrestore(&desc->lock, flags);
   } while (inprogress);
}

このようにsynchronize_irq()は長いループ処理を行うため、ユーザコンテキストでしか使えないのです。

おわりに

Linuxはドキュメントに乏しい文化なので、カーネル関数の使い方を知るには関数のコメントを読むか、ソースコードを読解するしかありません。たかがコメントでも、間違いがあるならば、開発者に誤解を与える可能性があります。いかがコメント、されどコメント。Linuxカーネルの最先端ではコメントだけの修正もおこなわれています。

貴重な時間を使って最後まで読んでくださり、ありがとうございました。 よかったら、記事の拡散もお願いいたします。