Linux カーネルと FreeBSD カーネルの割り込み処理

はじめに

わけあって、 Linux カーネルと FreeBSD カーネルの双方で仕事をした結果、 二つのカーネルで割り込み処理の実装方法が大きく異なっていることに気づきました。 ここでは、それぞれの割り込み処理の仕組みについて、 調べたことを書いてみたいと思います。

割り込みとは

割り込みとは、主に入出力ハードウェアによって CPU に送られる処理要求のことです。 一般に、 CPU は入出力ハードウェアよりもずっと高速に動作するので、 入出力処理を行う際に、ハードウェアの動作を待つよりも、 CPU では別の処理を行なっておき、 必要になった時にハードウェア側からの通知を受けて対応する処理を行う方が、 CPU を有効に活用できます。この入出力ハードウェア側からの通知が、割り込みと呼ばれます。

割り込みは、 CPU で動作するカーネルによって処理されることとなります。 ここで、一つの割り込み処理に時間がかかると、他の割り込みの応答が遅れてしまうため、 割り込みの処理は出来るだけ短い時間で終了するようにし、 時間のかかる処理は後で実行するようにする、という実装が行なわれます。 例えば、ネットワークインターフェイスからのパケットの受信は、以下のような過程で行われます。

  1. ネットワークインターフェイスにパケットが到着すると、 ネットワークインターフェイスは CPU に向けて割り込み要求を出す。
  2. 割り込みを受け、カーネルの該当処理が呼び出される。
  3. カーネルの割り込み処理により、ネットワークインターフェイスからパケットを受けとる。 この時点でパケットの内容を処理すると時間がかかる可能性があるため、 それは後で処理するようスケジュールしておいて、割り込み処理を終了し、 ネットワークインターフェイスからの次の受信に備える。
  4. CPU が手空きの時に、先にスケジュールしたパケット受信処理の続きを起動する。 パケットの中身を処理し、最終的にユーザに渡す。

このように処理を分割することで、割り込みへの応答性を向上させることが出来ます。

FreeBSD カーネルと Linux カーネルの双方で、このような実装がされているのですが、 特に割り込み処理の後半について、その実装の内容は大きく異なっています。

FreeBSD の割り込み処理

FreeBSD では、割り込み処理の前半部分の、ハードウェアから来た割り込みを受け取る処理は、 interrupt filter と呼ばれます。この処理は、 割り込みが来た瞬間に実行されていたスレッドを一時停止し、 そのスレッドを間借りして実行されます。 (primary interrupt context)。 そうして、 interrupt filter では実行できない、 時間のかかる処理は、後で、独立した interrupt thread で処理するようスケジュールします。

interrupt thread は、高い優先度を持ったカーネルスレッドで、 これにより割り込み処理の後半が行われます。 CPU が手空きの時に interrupt thread の実行が始まり、割り込み処理が行われます。

Linux の割り込み処理とロック

Linux では、割り込み処理の前半部分の、ハードウェアから来た割り込みを受け取る処理は、 ハードウェア割り込み と呼ばれます。この部分は、 FreeBSD の interrupt filter と同様、 割り込みが来た瞬間に実行されていたスレッドを間借りして実行されます。 そして、時間のかかる処理は、次のソフト割り込みで処理するようスケジュールされます。

ソフト割り込み は、スケジューラにより起動されます。 スケジューラは、ソフト割り込みの有無を確認し、実行の必要があれば、 ハードウェア割り込みの処理と同じく、その時実行中だったスレッドのカーネルスタックを用いて、 ソフト割り込みを実行します。 FreeBSD とは違い、割り込み処理を行う専用スレッドは起こしません。 そして、ソフト割り込み中には、 新たなハードウェア割り込みの受け付けが可能となっていて、 ハードウェア割り込みに対する応答性が確保されています。 このように、ソフトウェア割り込みが実行中スレッドを間借りして実行されるため、 Linux では、全スレッドのメモリ空間に、 割り込み処理のためのカーネルスタック空間が予約されています。

両者の pros & cons

上で見たように、割り込みの後半部分を実行する箇所が、 FreeBSD カーネルと Linux カーネルとで大きく異なる部分です。FreeBSD カーネルの構造では、 それ自身のスレッドを持って割り込みの後半を実行するのに対して、 Linux カーネルでは割り込みの後半もスレッドを持たず、 他のスレッドのカーネルスタックを借りて割り込みを処理することになります。

一般的に、CPU 上で実行するスレッドを切り替える (コンテキストスイッチ) ことは、コストの高い処理です。 FreeBSD の方式では、割り込み処理のために、 どうしてもコンテキストスイッチが発生してしまいます。 この点で、Linux の方式は、メモリ空間が切り替わらないため、 コンテキストスイッチの必要がなく、効率上の利点があります。

一方で、 Linux の方式では、 全てのスレッドにカーネルスタック用の領域を予約する必要があります。 さらに、メモリの消費を避けるために、 カーネルスタックの大きさも制限されてしまいます。 このような事情により、 Linux カーネルでは、 スタックをあまり消費しないようなコーディングをしなければいけません。 また、割り込みの後半部分に自分自身のスレッドを持たないため、 割り込み処理と割り込まれた元の処理との間でデッドロックしないように、 慎重にロックを取る必要があります。 Linux の方式は、メモリ使用量や、 さらには開発者への負荷という点で欠点があるといえます。

まとめ

以上、二つのカーネルを比べて、割り込み処理という基盤的な箇所においても、 実装方針の違いが見られることを知りました。

なお、 FreeBSD のドキュメントには、 light weight context switch という、 interrupt thread へのコンテキストスイッチに、専用の方式を使用することで、 現状の構造を保ったままで高速化を目指す手法が述べられていました。 このような記述があるのを見ると、 一見 Linux に比べ非效率に見える FreeBSD の方にも、 まだまだ伸び代があるのだなあと思わされました。

References

Author: YOKOTA Yuki

Date: 2014-03-27

Generated by Org version 7.8.11 with Emacs version 24

© Mathematical Systems Inc. 2014