割り込みと入出力(I/O)
デバイスの「準備できたよ」をCPUに知らせる仕組みが割り込み。CPUは無駄に待たず、必要なときだけ反応してI/Oを効率よくさばく。
- 1.割り込み(interrupt)は、デバイスからCPUへの「処理が終わった/要求がある」という非同期の通知。
- 2.ずっと聞き続けるポーリングより、割り込みのほうがCPUを無駄なく使える(ただし用途次第)。
- 3.高速転送はDMAがCPUを介さずメモリへ直送し、デバイスドライバが差を吸収。アプリ側はブロッキング/ノンブロッキングで待ち方を選ぶ。
なぜ割り込みが要るのか
CPU はナノ秒単位で動きますが、ディスクやネットワーク、人間のキー入力は 桁違いに遅い です。データが届くのを CPU が待ち続けたら、その間は何もできず大損です。
そこで「用意ができたら向こうから知らせてもらう」方式にします。これが割り込みです。CPU は待っている間、別のプロセスを動かせます(この「待ちのあいだ別の仕事をする」発想は /os/scheduling/ や /os/process-thread/ とも深く関わります)。
ポーリング vs 割り込み
デバイスの状態を知る方法は大きく2つあります。
- ポーリング(polling):CPU が「もう終わった?」と定期的に 自分から確認 しに行く
- 割り込み(interrupt):デバイスが終わったら 向こうから通知 してくる
| 観点 | ポーリング | 割り込み |
|---|---|---|
| きっかけ | CPUが自分で繰り返し確認 | デバイスがCPUに通知 |
| CPU使用 | 待っている間も消費(空回り) | 通知が来るまで他の仕事ができる |
| 遅延(応答) | 次の確認まで待つことがある | 発生時にほぼ即応 |
| 実装 | 単純(ループで状態を読む) | ハンドラ・割り込みベクタが必要 |
| 向く場面 | 超高速・高頻度なデバイス | 低頻度・予測できないイベント |
「割り込みのほうが上位互換」ではありません。NIC のように 毎秒大量のパケット が来ると、1件ごとの割り込みで処理が割り込み嵐(interrupt storm)になり逆に遅くなります。そこで高速ネットワークでは、しばらく 割り込みを止めてポーリングに切り替える(Linux の NAPI など)混在方式が使われます。
I/O が完了するまでの流れ
割り込みを使った典型的な入出力は、ざっくりこう進みます。
1. アプリが read() などでカーネルにI/Oを依頼(システムコール)
2. デバイスドライバがデバイスへ「読んで」と指示
3. CPUは待たず、別のプロセスを実行(依頼元プロセスはブロック)
4. デバイスがデータを用意 → CPUへ「割り込み」発生
5. CPUは実行中の処理を中断し、割り込みハンドラへジャンプ
6. ハンドラがデータを回収し、待っていたプロセスを「実行可能」に戻す
7. 中断した処理に復帰
ステップ5で CPU は、いま使っているレジスタなどの状態を退避し、割り込みベクタテーブル を引いて対応する 割り込みハンドラ(ISR: Interrupt Service Routine) に飛びます。ここでの「状態を退避して別処理に移り、後で戻る」感覚は /os/system-call/ の制御移行とよく似ています。
ISR の実行中は他の割り込みが待たされやすいため、ハンドラ内で重い処理をしてはいけません。実務では「最低限だけハンドラで行い(前半/top half)、残りの重い処理は後回しにする(後半/bottom half・Linux の softirq/tasklet/threaded IRQ)」という二段構えが定石です。
DMA:CPUを介さずメモリへ直送
データ1バイトごとに CPU がコピーしていては、せっかく割り込みにしても CPU が忙しすぎます。そこで DMA(Direct Memory Access) を使います。
- DMA コントローラ がデバイスとメモリの間を CPU を経由せずに 転送する
- CPU は「ここからここへ転送して」と最初に指示するだけ
- 転送が 全部終わったとき にだけ、DMA が CPU へ 完了割り込み を上げる
[割り込み駆動コピー] デバイス → CPU → メモリ (バイトごとにCPUが介在)
[DMA転送] デバイス → メモリ (CPUは開始と完了通知だけ)
つまり DMA は「転送中の CPU 解放」、割り込みは「完了の通知」を担当し、両者は 組み合わせて 使われます。大きなディスク読み込みやネットワーク受信が CPU を食いつぶさないのは DMA のおかげです。
デバイスドライバ:差を吸収する層
世の中のデバイスは型番ごとにレジスタも作法もバラバラです。それを デバイスドライバ が吸収し、OS には統一された使い方(read/write など)を見せます。
- 上から見ると:OS の共通インターフェース(「ファイルのように」読み書き)
- 下を見ると:その機器固有のレジスタ操作・割り込み処理
割り込みが来たとき実際に動く ISR も、多くはドライバの一部です。ドライバのおかげで、アプリは相手が SSD でも USB メモリでも 同じコード で扱えます。
ブロッキング vs ノンブロッキング I/O
ここまではカーネル内の話。アプリから見た「待ち方」 も2通りあります。
- ブロッキング I/O:
read()を呼ぶと、データが揃うまで その場で停止(プロセス/スレッドは眠る) - ノンブロッキング I/O:データがまだなら すぐ「まだ無い」と返る。後で再確認したり、まとめて多数の接続を監視(
select/epoll/kqueue等)する
| 観点 | ブロッキング | ノンブロッキング |
|---|---|---|
| 呼び出し | 揃うまで返らない(待つ) | 未完了でも即返る |
| 書きやすさ | 直線的で簡単 | 状態管理やループが必要 |
| 多数の接続 | 1接続=1スレッドで重くなりがち | 1スレッドで多数を多重化しやすい |
| 相性 | 順序処理・素朴なツール | 高並行サーバ・イベント駆動 |
割り込みは ハードウェア↔カーネル の通知の仕組み、ブロッキング/ノンブロッキングは アプリ↔カーネル のAPIの振る舞いです。混同しがちですが層が違います。なお「待っている間ほかを進める」というノンブロッキングの考え方は、言語レベルの /programming/sync-async/ と地続きです。
まとめ
- 割り込み:遅いデバイスからの非同期通知。CPU を見張りから解放する
- ポーリングとの使い分け:低頻度なら割り込み、超高頻度なら(部分的に)ポーリング
- DMA:転送そのものを CPU から肩代わりし、完了時だけ割り込む
- ドライバ:機器ごとの差を隠し、統一インターフェースを提供
- ブロッキング/ノンブロッキング:アプリ側の「待ち方」。割り込みとは別レイヤ
OS Article
割り込みと入出力(I/O)を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
割り込み
比較で見る軸
難易度: intermediate / カテゴリ: OS / タグ数: 4
導入後に効く点
ずっと聞き続けるポーリングより、割り込みのほうがCPUを無駄なく使える(ただし用途次第)。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- intermediate
- カテゴリ
- OS
- タグ数
- 4
判断チェックリスト
- 自社の用途が「割り込み / I/O」に近いか確認する。
- 強みである「割り込み(interrupt)は、デバイスからCPUへの「処理が終わった/要求がある」という非同期の通知。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。