Skip to content
Published on

[オペレーティングシステム] 12. I/Oシステム

Authors

I/Oシステム

オペレーティングシステムの核心的な役割の一つは、様々なI/Oデバイスを管理し、 アプリケーションに一貫したインターフェースを提供することです。 この記事では、I/Oハードウェア構造からカーネルI/Oサブシステムまで解説します。


1. I/Oハードウェア

ポート、バス、コントローラ

┌─────────────────────────────────────────────┐
CPU│              ┌──────────┐                    │
│              │ メモリ   │                    │
│              └────┬─────┘                    │
│                   │ システムバス              │
│         ┌─────────┼─────────┐                │
│         │         │         │                │
│    ┌────┴───┐ ┌───┴────┐ ┌─┴──────┐         │
│    │ SATA   │ │ USB    │ │ PCIe   │  ← コントローラ│
│    │Controller│ │Controller│ │Controller│     │
│    └────┬───┘ └───┬────┘ └─┬──────┘         │
│         │         │         │                │
│     ┌───┴──┐  ┌───┴──┐  ┌──┴───┐            │
│     │ HDD  │  │マウス │  │ GPU  │  ← デバイス│
│     └──────┘  └──────┘  └──────┘            │
└─────────────────────────────────────────────┘
  • ポート(Port): デバイスとの接続点(例:USBポート、SATAポート)
  • バス(Bus): 信号を伝達する共有通信経路(例:PCIeバス)
  • コントローラ(Controller): ポート、バス、デバイスを管理する電子回路

デバイスコントローラのレジスタ

各デバイスコントローラは一般的に以下のレジスタを持ちます。

レジスタ役割
data-inホストが読むデータ
data-outホストが書くデータ
statusデバイス状態(busy, ready, error)
commandホストが発行するコマンド

2. ポーリング(Polling)

CPUがデバイスのステータスレジスタを繰り返し確認してI/O完了を検知する方式です。

// ポーリング基盤I/O例(擬似コード)
void polling_write(char data) {
    // 1. 장치가 준비될 때까지 busy-wait
    while (read_status_register() & BUSY_BIT)
        ; // 바쁜 대기

    // 2. 데이터 레지스터에 데이터 쓰기
    write_data_register(data);

    // 3. 명령 레지스터에 쓰기 명령 설정
    write_command_register(WRITE_COMMAND);

    // 4. 완료될 때까지 다시 대기
    while (read_status_register() & BUSY_BIT)
        ;
}

利点: 実装が簡単で、短いI/Oには効率的 欠点: CPUサイクルの浪費(ビジーウェイト)、長いI/Oには非効率的


3. 割り込み(Interrupt)

デバイスがI/O完了時にCPUに信号を送る方式です。CPUは他の作業を行いながら、割り込みを受けると処理します。

割り込み処理フロー

┌──────┐      1. I/O要求          ┌──────────┐
CPU  │ ──────────────────────→ │ デバイス  │
│      │                         │コントローラ│
│      │  他の作業を実行中        │          │
│      │                         │ I/O実行  │
│      │                         │          │
│      │  ←── 2. 割り込み信号 ── │ 完了!   │
│      │                         └──────────┘
│      │  3. 現在の状態を保存
│      │  4. 割り込みハンドラ実行
│      │  5. 状態復元、作業再開
└──────┘

割り込みベクタテーブル

// 割り込みベクタテーブルの概念(擬似コード)
typedef void (*interrupt_handler_t)(void);

interrupt_handler_t interrupt_vector[256];

// 初期化時にハンドラを登録
void init_interrupts() {
    interrupt_vector[0]  = divide_error_handler;
    interrupt_vector[1]  = debug_handler;
    interrupt_vector[14] = page_fault_handler;
    interrupt_vector[32] = timer_handler;
    interrupt_vector[33] = keyboard_handler;
    interrupt_vector[46] = disk_handler;
    // ...
}

// 割り込み発生時のディスパッチ
void dispatch_interrupt(int vector_num) {
    interrupt_vector[vector_num]();
}

割り込み優先順位

オペレーティングシステムは割り込みに優先順位を付与し、重要な割り込みが先に処理されるようにします。

高い優先順位
  │  ┌────────────────────────┐
  │  │ NMI (Non-Maskable)     │ ← ハードウェアエラー
  │  │ タイマー割り込み        │ ← スケジューリング
  │  │ ディスク割り込み        │ ← I/O完了
  │  │ ネットワーク割り込み    │ ← パケット到着
  │  │ キーボード/マウス       │ ← ユーザー入力
  ▼  └────────────────────────┘
低い優先順位

4. DMA(Direct Memory Access)

大量データ転送時にCPUの介入なしでデバイスとメモリ間で直接データを転送する方式です。

DMA動作過程

┌──────┐                    ┌──────────┐
CPU1. DMA要求設定     │ DMA│      │ ─────────────────→ │ Controller│
│      │                    │          │
│      │  自由に他の          │ 2. バス制御権│
│      │  作業を実行          │    獲得   │
│      │                    │          │
│      │                    │ 3. デバイス ↔│
│      │                    │  メモリ直接 │
│      │                    │  転送     │
│      │                    │          │
│      │  ←── 4. 完了       │ 転送完了  │
│      │      割り込み       │          │
└──────┘                    └──────────┘
// DMA転送設定(擬似コード)
void setup_dma_transfer(
    void *buffer,        // 메모리 버퍼 주소
    int   device_id,     // 대상 장치
    int   byte_count,    // 전송 바이트 수
    int   direction      // READ 또는 WRITE
) {
    dma_controller.address  = buffer;
    dma_controller.count    = byte_count;
    dma_controller.device   = device_id;
    dma_controller.command  = direction;

    // DMA 전송 시작 - CPU는 다른 작업 수행 가능
    dma_controller.start    = 1;
}

5. アプリケーションI/Oインターフェース

オペレーティングシステムは様々なデバイスをいくつかのタイプに抽象化して一貫したインターフェースを提供します。

デバイスタイプ別インターフェース

┌───────────────────────────────────────┐
│        アプリケーション               │
read()  write()  ioctl()├───────────────────────────────────────┤
│        カーネルI/Oサブシステム         │
├──────┬──────┬──────┬──────┬──────────┤
│ブロック│文字  │ネット │クロック│ その他  │
│デバイス│デバイス│ワーク │タイマー│        │
│      │      │ソケット│      │          │
├──────┼──────┼──────┼──────┼──────────┤
│ディスク│キーボ│ NICRTC  │          │
SSD  │ード  │      │ PIT  │          │
│      │マウス│      │      │          │
└──────┴──────┴──────┴──────┴──────────┘
デバイスタイプ特性主要操作
ブロックデバイス固定サイズブロック単位read, write, seekディスク、SSD
文字デバイスバイトストリームget, putキーボード、シリアル
ネットワークソケットインターフェースsend, receiveNIC
クロック/タイマー時間測定、通知get_time, set_timerRTC, HPET

ブロッキング vs ノンブロッキングI/O

// 블로킹 I/O - 완료될 때까지 프로세스 대기
ssize_t bytes = read(fd, buffer, size);
// 이 줄은 read가 완료된 후에 실행됨

// 논블로킹 I/O - 즉시 반환
fcntl(fd, F_SETFL, O_NONBLOCK);
ssize_t bytes = read(fd, buffer, size);
if (bytes == -1 && errno == EAGAIN) {
    // 데이터가 아직 준비되지 않음
}

// 비동기 I/O - 요청 후 완료 시 통지
struct aiocb cb;
cb.aio_fildes = fd;
cb.aio_buf    = buffer;
cb.aio_nbytes = size;
aio_read(&cb);  // 즉시 반환
// 나중에 완료 확인
while (aio_error(&cb) == EINPROGRESS)
    do_other_work();

6. カーネルI/Oサブシステム

カーネルはI/Oを効率的に管理するための複数のサービスを提供します。

I/Oスケジューリング

複数のI/O要求の実行順序を最適化します。

要求キュー:  [ディスク読み取りA] [ディスク書き込みB] [ディスク読み取りC]
I/Oスケジューラ(再配置)
実行順序:    [ディスク読み取りA] [ディスク読み取りC] [ディスク書き込みB]
             → ディスクヘッド移動最小化

バッファリング(Buffering)

データ転送時に一時的な保存領域を使用して速度差を緩和します。

プロデューサ(デバイス)           コンシューマ(プロセス)
    │                                  │
    │  ┌──────────┐                    │
    ├→ │ Buffer 1 │ (充填中)          │
    │  └──────────┘                    │
    │  ┌──────────┐                    │
    │  │ Buffer 2 │ ────────────────→  ├→ 処理
    │  └──────────┘  (排出中)         │
    │                                  │
    │  ダブルバッファリング:    │  充填と排出を同時に実行           │

キャッシング(Caching)

頻繁にアクセスされるデータのコピーを高速なストレージに保管します。

アプリケーション → キャッシュ確認 → ヒット? → キャッシュから返却
                      └→ ミス → ディスクから読み取り → キャッシュに保存 → 返却

スプーリング(Spooling)

一度に一つの作業しか処理できないデバイス(例:プリンタ)のための出力キューイングメカニズムです。

プロセスA ─→ ┌────────────┐
プロセスB ─→ │ Spoolキュー │ ─→ プリンタ(一度に一つずつ)
プロセスC ─→ │ (ディスク) │
              └────────────┘

7. I/Oパフォーマンス

I/Oはシステム全体のパフォーマンスの主要なボトルネックです。

パフォーマンス改善の原則

┌────────────────────────────────────────┐
I/Oパフォーマンス最適化戦略      │
│                                        │
1. コンテキストスイッチ回数の削減      │
2. データコピー回数の削減              │
│     (Zero-copy手法)                  │
3. 割り込み頻度の削減                 │
│     (割り込みの統合)                  │
4. DMA活用によるCPU負担の削減         │
5. ポーリングと割り込みの適切な組合せ  │
6. 機能をハードウェアに移行           │
│     (Hardware Offloading)└────────────────────────────────────────┘

Zero-copy転送の例

// 전통적 방식: 4번의 데이터 복사
// 디스크 → 커널 버퍼 → 사용자 버퍼 → 소켓 버퍼 → NIC

// sendfile()을 이용한 Zero-copy (Linux)
#include <sys/sendfile.h>

// 파일에서 소켓으로 직접 전송 (커널 내에서만 복사)
ssize_t sent = sendfile(socket_fd, file_fd, &offset, count);
// 디스크 → 커널 버퍼 → NIC (사용자 공간 복사 없음)

8. まとめ

  • ポーリング: 単純だがCPU浪費。短いI/Oに適合
  • 割り込み: CPU効率的だがオーバーヘッドあり。ほとんどのI/Oに使用
  • DMA: 大量データ転送に必須。CPU負担最小化
  • カーネルI/Oサブシステム: スケジューリング、バッファリング、キャッシング、スプーリングでパフォーマンスと互換性を確保
  • パフォーマンス最適化: Zero-copy、割り込み統合、ハードウェアオフローディングなど多様な手法を活用
クイズ:I/Oシステム

Q1. ポーリングと割り込み方式の違いは何であり、それぞれどのような状況に適していますか?

A1. ポーリングはCPUがデバイスの状態を繰り返し確認する方式で、非常に短いI/Oでは割り込みオーバーヘッドよりも効率的です。 割り込みはデバイスが完了時にCPUに通知する方式で、CPUが待機中に他の作業を行えるため ほとんどのI/Oにより適しています。

Q2. DMAはCPUパフォーマンスにどのような利点をもたらしますか?

A2. DMAコントローラがメモリとデバイス間のデータ転送を直接処理するため、 CPUは転送完了を待たずに他の演算を実行できます。 特に大容量ディスクI/Oやネットワーク転送でCPU使用率を大幅に削減します。

Q3. バッファリングとキャッシングの違いは何ですか?

A3. バッファリングはプロデューサとコンシューマ間の速度差を緩和するための一時的な保存であり、 データが一度使用されるとバッファから削除されます。 キャッシングは頻繁にアクセスされるデータのコピーを高速なストレージに保持して再利用することです。