Dev Study
← OSとカーネルの土台
レッスン 2 / 10·約 9 分·第10期 OSとカーネルの土台

割り込み — 頼んでいないのに止められる

アプリと DB の下に何が積まれているか(左が下、右が上)
  1. 物理機リージョン/AZ
  2. ハイパーバイザVM exit で捕まえる
  3. VMゲストOS
  4. コンテナnamespace / cgroup
  5. プロセスアプリ / DB今ここ

前回、カーネルへ入る道が「自分から呼ぶ」ものだけではないことを見ました。今回はそのうち外から来る1つ、ハードウェア割り込みを詳しく見ます。

まず結論

ハードウェア割り込みは走っている命令列と無関係に入ります。プログラム側が依頼していないのに、実行が止められてカーネルの入口へ移ります。

だから割り込みハンドラは短く保ち、重い処理は後回しの仕組みへ渡します。そして定期的に入るタイマー割り込みが、第12週で見た「OSが切り替える」の土台です。

? 読む前に予測

仕事を持っているCPUに、Linuxのタイマー割り込みは1秒あたり何回入るでしょうか。

当てることが目的ではありません。外した予測ほど記憶に残るので、直感で選んでください。

抽象原理

依頼していないのに、実行が止められる

前回、カーネルへ入る道が「自分から呼ぶ」ものだけではないことを見ました。今回はそのうち外から来る1つを詳しく見ます。

システムコールは、走っているプログラムが自分で出します。出した場所があり、戻る場所もそこです。ハードウェア割り込みには、その依頼がありません。

kernel.org の Unreliable Guide To Hacking The Linux Kernel は、割り込みの性質を1段落に詰めて書いています。

Timer ticks, network cards and keyboard are examples of real hardware which produce interrupts at any time. The kernel runs interrupt handlers, which services the hardware. The kernel guarantees that this handler is never re-entered: if the same interrupt arrives, it is queued (or dropped). Because it disables interrupts, this handler has to be fast: frequently it simply acknowledges the interrupt, marks a 'software interrupt' for execution and exits.

3つのことが書かれています。(a) いつでも来る(at any time)。(b) 入れ子にならない(never re-entered。同じ割り込みが来たら積まれるか捨てられる)。(c) 割り込みを禁止するから速くないといけない(has to be fast)。

(c) が設計を決めます。ハンドラが走っている間は次を受けられないので、ここで重い処理をすると、その間に来た信号が積まれるか捨てられます。だから多くのハンドラは、応答して「後でやる分」に印を付けて抜けます。同じ文書は続けて、"Much of the real interrupt handling work is done here" — 本当の処理の多くは後回し側で行われる、と書いています。

具体実物

実物のカウンタを見る

Linux は割り込みの回数を数えて /proc/interrupts に出します。次は実機の生ログの先頭部分です。

$ cat /proc/interrupts(Apple Silicon 上の Docker Desktop / linuxkit / kernel 6.10.14 / aarch64。2026-09-17 取得。右端のCPU5〜CPU9列と、全CPUが0の12行を省略)コマンド出力
CPU0 CPU1 CPU2 CPU3 CPU4
11: 35092735 30785551 29233019 28811702 28780102 GICv3 27 Level arch_timer
14: 18400904 0 0 0 0 GICv3 65 Level virtio0
15: 5983561 0 0 0 0 GICv3 69 Level virtio1
16: 4016708 0 0 0 0 GICv3 70 Level virtio2
17: 3530 0 0 0 0 GICv3 71 Level virtio3
24: 6429568 0 0 0 0 GICv3 78 Level virtio10
27: 0 0 0 0 0 GICv3 36 Level rtc-pl031
28: 0 0 0 0 0 20060000.pl061 7 Edge gpiomon

読み方は左から、割り込み番号 / CPUごとの累積回数 / コントローラ / ハードウェア番号 / トリガ種別(Level か Edge) / デバイス名です。

つかまえてほしい点は1つ。11番の arch_timer だけが桁違いに多く、全CPUに散らばっています。 一方 virtio デバイス(仮想的なディスクやネットワーク)は CPU0 に偏ります。

この環境は arm64 の仮想マシンなのでコントローラが GICv3、デバイスが virtio です。x86 の実機なら行の名前も数も変わります。出力の形は環境で変わり、読み方だけが共通。

✓ ここまでの確認

割り込みハンドラを短く保つ必要があるのは、カーネル文書によればなぜですか。

抽象原理

後回しにする側の、現在の呼び名

「短い前半と後回しの後半」に分ける話をすると、トップハーフ / ボトムハーフという対語を聞いたことがあるかもしれません。ここは注意が要ります。

カーネル公式文書はこの語を、過去形で、置き換えられたものとして書いています。

Much of the real interrupt handling work is done here. Early in the transition to SMP, there were only 'bottom halves' (BHs), which didn't take advantage of multiple CPUs. Shortly after we switched from wind-up computers made of match-sticks and snot, we abandoned this limitation and switched to 'softirqs'.

there **were** only 'bottom halves' / we **abandoned** this limitation and **switched to** 'softirqs'。節の題そのものも Software Interrupt Context: Softirqs and Tasklets であり、bottom half ではありません。

現行の語は softirq / tasklet / threaded IRQ handler です。分割設計を説明する逐語は、文書ではなくソース kernel/irq/manage.c の kernel-doc にあります。

If you want to set up a threaded irq handler for your device then you need to supply @handler and @thread_fn. @handler is still called in hard interrupt context and has to check whether the interrupt originates from the device. If yes it needs to disable the interrupt on the device and return IRQ_WAKE_THREAD which will wake up the handler thread and run @thread_fn. This split handler design is necessary to support shared interrupts.

"This split handler design" — 分割する設計そのものは現役で、現在形で説明されています。変わったのは呼び名です。前半が primary handler(hard interrupt context で走る)、後半が thread_fn(irq handler thread で走る)。

具体実物

タイマー割り込みは、時分割の土台

第12週『OSの役割 — 1つのCPUを、みんなで分け合う』で、OSが高速に切り替えることで同時に見せている、と学びました。ここで残っていた問いがあります。切り替える機会は、どうやって来るのか。

走っているプログラムは自分から譲るとは限りません。無限ループを書けば握ったままです。機会を作るのが、定期的に入るタイマー割り込みです。no_hz.rst は目的をこう書いています。

If a CPU is idle, there is little point in sending it a scheduling-clock interrupt. After all, the primary purpose of a scheduling-clock interrupt is to force a busy CPU to shift its attention among multiple duties, and an idle CPU has no duties to shift its attention among.

to force a busy CPU to shift its attention among multiple duties — 忙しいCPUに、複数の役目の間で注意を移させる。これは時分割の定義そのものです。

同時にこの文は、省く条件も述べています。省くのは idle なCPUに対してだけです。切り替える相手がいないCPUに合図を送る意味が無いからで、時分割をやめたわけではありません。

数字で確かめられます。先ほどと同じ機で、arch_timer の行を5秒あけて2回取り、差を出しました。

arch_timer の5秒間の増分(同じ実機。CPU0〜CPU3 を抜粋。以下略)表
条件 CPU0 CPU1 CPU2 CPU3
何もしない5秒 +388 +310 +366 +435
CPU1でビジーループ5秒 +367 +5014 +263 +218

ビジーループを回した CPU1 だけが5秒で 5014 回。1秒あたり約 1002.8 回。この機の設定は CONFIG_HZ=1000 で、一致します。同じ5秒間、他のCPUは134〜367回どまりでした。

設定はこうでした。CONFIG_NO_HZ_IDLE=y / # CONFIG_NO_HZ_FULL is not set / CONFIG_HZ=1000。既定は「idle なら省く」であって「常に省く」ではありません。 忙しくても省く CONFIG_NO_HZ_FULL は既定ではなく、no_hz.rst 自身がリアルタイム用途やある種のHPC以外では you will normally -not- want this option と書きます。

✓ ここまでの確認

「後半の処理はボトムハーフと呼ばれ、これが現在のLinuxの用語です」という説明をどう扱いますか。

⇄ 2つを見比べる
具体例 A

システムコール — 自分で出す

きっかけはプログラム自身の命令。依頼を書いた場所があり、終われば依頼した命令の次へ戻ります。結果は戻り値と errno で受け取れます。

具体例 B

ハードウェア割り込み — 外から来る

きっかけはデバイスからの信号。戻り先はたまたま止められた場所で、命令列には現れません。止められた側が受け取る結果もありません。

自分で呼ぶ道と外から来る道。どちらもカーネルへ入りますが、2つを分けている構造は何でしょうか。

「誰がきっかけを作ったか」と「戻り先がプログラムに書いてあるか」を並べてください。

同じ構図は仮想化編『ハイパーバイザ — 特権命令をどう捕まえるか』でも出てきました。ゲストOSが自分から呼んだのではなく、特権命令を実行したところで制御が下の層へ移る — 依頼していないのに止められて、下の層の入口へ移る構造です。

抽象原理

図で整理する

割り込みが入ってから、後回し分が片付くまでの流れです。

割り込みが入ってから、後回し分が走るまで
デバイスが信号を上げる実行中の命令列とは無関係("at any time")
実行中の処理が止められ、カーネルの入口へ
割り込みハンドラ(前半)割り込みを禁止した状態で走る。"has to be fast"
応答して、後でやる分に印を付けて抜ける
softirq / taskletハンドラを抜けるとき、またはシステムコールがユーザ空間へ戻るときに実行される
thread_fnIRQ_WAKE_THREAD で起こされた irq handler thread が実行する
止められていた処理へ戻る
元の続き止められた側は、止められたことを命令列のどこにも持たない
  • ・前半を短くするのは、この間は次の割り込みを受けられないため
  • ・分けること自体は現役の設計。kernel/irq/manage.c は "This split handler design" と現在形で説明している

この章のまとめ

  • ハードウェア割り込みは、実行中の命令列と無関係に入る。依頼が無いので、止められた側は止められたことを命令列のどこにも持たない。
  • 割り込みハンドラは割り込みを禁止した状態で走るので短く保つ。多くは応答して「後でやる分」に印を付けて抜ける。
  • 「トップハーフ/ボトムハーフ」は現行の用語ではない。 公式文書は bottom halves を過去形で書き、softirq へ置き換えたと述べる。現行は softirq / tasklet / threaded IRQ handler。
  • 分割する設計そのものは現役で、request_threaded_irq の primary handler と thread_fn がそれにあたる。
  • タイマー割り込みが時分割の土台。 省かれるのは idle なCPUに対してだけで、仕事のあるCPUには CONFIG_HZ どおり入る(実測した1台では 5秒で5014回、CONFIG_HZ=1000 と一致)。
  • /proc/interrupts が空でも、割り込みが無いとは限らない。ファイルの実体を確かめる。

自分の現場で確かめる

読んだだけでは身につきません。手元の環境で実際に見てみてください。

環境:

理解度チェック

読んだ直後は「分かった」と感じやすいものです。実際に解いて確かめてください。

問題 1 / 3正解: 0

現行のカーネルで「短い前半と、後回しの後半」に分ける設計は、どの語で説明されていますか。

用語集(5語)
ハードウェア割り込み (hard IRQ)
デバイスからの信号で、実行中の命令列と無関係にカーネルの入口へ移らせるもの。hacking.rst は例としてタイマー・ネットワークカード・キーボードを挙げ、"produce interrupts at any time" と書いている。
softirq / tasklet
割り込みハンドラが印を付けた「後回し分」を実行する仕組み。hacking.rst によれば SMP 初期の bottom halves を置き換えたもの。tasklet は同時に1つのCPUでしか走らない。
threaded IRQ handler
request_threaded_irq で登録する分割方式。primary handler は hard interrupt context、thread_fn は irq handler thread で走る。kernel/irq/manage.c はこれを "This split handler design" と呼ぶ。
scheduling-clock interrupt (tick)
定期的に入るタイマー割り込み。no_hz.rst は主目的を "to force a busy CPU to shift its attention among multiple duties" と述べる。1秒あたりの回数は CONFIG_HZ で決まる。
dyntick-idle
idle なCPUへの scheduling-clock interrupt を省いている状態。no_hz.rst は "dyntick-idle" "in nohz mode" "running tickless" を同義として挙げる。既定は CONFIG_NO_HZ_IDLE=y。