🎓 レベル:標準 | 重要度:B(推奨)
📎 前提:CPUと命令実行 | 関連:OSの役割とカーネル・ディスクとストレージ階層
要点(BLUF)
- CPUと外部機器(ディスク・キーボード・NIC)は バスという共有配線でつながり、機器ごとのレジスタを読み書きして通信する。
- 「準備できた?」をCPUが何度も聞きに行くのがポーリング、「できたら機器から知らせる」のが割り込み。割り込みはCPUを別の仕事に回せる。
- 大量データは1バイトずつCPUが運ぶと無駄。DMAが「CPU抜き」でメモリ⇔機器を直接転送し、終わったら割り込みで知らせる。
概念 ── CPUはどうやって外の世界と話すか
CPUが直接いじれるのは、レジスタと(バス越しの)メモリだけです。では、ディスクやネットワークカードとはどう通信するのか。答えは「機器にもレジスタ(制御レジスタ・データレジスタ・状態レジスタ)があり、CPUはそれを読み書きする」です。
これらの機器レジスタには2通りのアクセス方法があります。
- メモリマップドI/O:機器レジスタを「特別なメモリアドレス」に割り当て、通常のロード/ストア命令でアクセスする(現代の主流)。
- ポートマップドI/O:専用の
in/out命令で別空間にアクセスする(x86の伝統)。
要するに、外部機器との通信も「決められた番地を読み書きする」というメモリ操作(2進数・データ表現)に帰着します。
仕組み① ── ポーリング vs 割り込み
機器に仕事を頼んだあと、「終わったかどうか」をどう知るか。これが本質的な分かれ目です。
flowchart TB
subgraph P["ポーリング(CPUが聞き続ける)"]
p1["状態レジスタを読む"] --> p2{"準備できた?"}
p2 -->|"まだ"| p1
p2 -->|"できた"| p3["データを処理"]
end
subgraph I["割り込み(機器から知らせる)"]
i1["仕事を頼んで別作業へ"] --> i2["…別の処理を進める…"]
i2 --> i3["機器が割り込み信号"]
i3 --> i4["ハンドラがデータ処理"]
end
ポーリングは単純ですが、待っている間ずっとCPUが状態確認に張り付き、他の仕事ができません(ビジーウェイト)。低速な機器ほど無駄が大きい。
割り込みでは、CPUは仕事を頼んだら別の処理に移り、機器が「終わったよ」と割り込み信号を上げた瞬間に、いま実行中の命令を中断して割り込みハンドラへジャンプします。これは CPUと命令実行 で見た「PCを書き換える」操作そのもの。ハンドラが終わると元の処理へ戻ります。
sequenceDiagram
participant C as CPU
participant D as デバイス
C->>D: 読み取り要求(頼むだけ)
Note over C: 別の処理を続ける
D-->>C: 割り込み信号「準備完了」
Note over C: 実行中命令を中断 → ハンドラへ
C->>D: データを受け取る
Note over C: 元の処理へ復帰
なぜ割り込みが基本か。待ち時間をCPUの遊休にしないためです。ディスクの数ミリ秒はCPUにとって数百万サイクル。その間に別プロセスを走らせれば、計算資源を無駄にしません。これがOSのスケジューリング(CPUスケジューリング)が成立する前提でもあります。
仕組み② ── DMA(CPUを介さない転送)
割り込みでも、データ本体を1バイトずつCPUがメモリへ運ぶ(プログラムI/O)と、大量転送ではCPUが運び屋に占有されます。
そこで DMA(Direct Memory Access)。DMAコントローラに「この機器からメモリのこの範囲へNバイト運べ」と指示すれば、CPU抜きで機器⇔メモリを直接転送し、完了時に1回だけ割り込みで知らせます。
flowchart LR
CPU["CPU(頼むだけ → 他の仕事へ)"] -->|"転送を指示"| DMAC["DMAコントローラ"]
DMAC <-->|"直接転送"| MEM["主記憶"]
DMAC <-->|"直接転送"| DEV["デバイス(ディスク等)"]
DMAC -.->|"完了したら割り込み"| CPU
これでCPUは「指示」と「完了の受け取り」だけに関与し、転送本体から解放されます。ディスクI/Oやネットワーク(コンピュータネットワーク へ繋ぐ)の高スループットはDMAが支えています。
具体例 ── 実機で割り込みを覗く
Linuxでは /proc/interrupts に、各割り込み番号がどのCPUで何回発生したかが見えます(対応ラボ)。キーボードを叩く・ネットワークを使うと、対応する行のカウントが増えるのを観察できます。「割り込みは抽象概念ではなく、実機で数えられる現象」だと体感できます。
仕組みの直観 ── なぜこの設計か
- 割り込みを使う理由:I/Oは桁違いに遅い。同期的に待てばCPUが遊ぶ。非同期に「終わったら呼んで」にすることで、待ち時間を他プロセスの計算に充てられる。
- DMAを使う理由:転送の「運搬」はCPUの計算能力を要しない単純作業。専用ハードに任せ、CPUを本来の計算に集中させる(分業)。
- バスという共有配線:全機器を個別配線するのは非現実的。共有バスにアドレスで宛先を指定する方式で、配線を節約しつつ拡張性を得る(代償はバス帯域の奪い合い)。
⚠️ よくある誤解・落とし穴
- 「割り込みは常にポーリングより速い」→ 超高頻度・低遅延が要る場面(高速NIC)では、割り込みのオーバーヘッド(コンテキスト退避)が重く、あえてポーリング(busy-poll)する設計もある。
- 「DMA中はCPUが何もできない」→ 逆。CPUは解放されて別作業ができる。バス帯域は一部取り合う。
- 「割り込みハンドラは普通の関数」→ いつ来るか不明な非同期処理。共有データの保護(競合状態とクリティカルセクション)が必要で、ハンドラ内で重い処理は避ける。
- 「I/Oは特別な通信路」→ 多くはメモリマップドで、ロード/ストアに帰着する。
対応ラボ
cs-foundations-study/labs/01-04_observe_interrupts.md(Linux: cat /proc/interrupts を2回取り、キーボード/ネットワーク使用で特定行のカウントが増えるのを観察する手順)。
関連
- I/O完了を待つ間に別プロセスを走らせる仕組みが CPUスケジューリング
- 割り込みはOSがハードを束ねる入口 → OSの役割とカーネル
- ストレージのI/O特性は ディスクとストレージ階層