---
title: 'io_uring: 革命的な非同期I/O'
url: https://doc.liz6.com/ja/systems-programming/05-io-and-file-systems/02-io_uring
locale: ja
area: systems-programming
tags:
- systems-programming
- io-and-file-systems
date: 2026-06-30
modified: 2026-07-16
description: 'カバー範囲: SQ/CQリング → 固定バッファ → ポーリングIO → libaio/posix aioとの比較 → パフォーマンスの考慮事項 対象: Linux 5.1+'
---

# io_uring: 革命的な非同期I/O

> カバー範囲: SQ/CQリング → 固定バッファ → ポーリングIO → libaio/posix aioとの比較 → パフォーマンスの考慮事項
> 対象: Linux 5.1+

## 概要

io_uringはLinux 5.1で導入された非同期I/Oフレームワークであり、LinuxにおけるI/Oの扱い方を根本から変えました。ユーザー空間とカーネルの間で、2つの共有メモリリングバッファ（SQ/CQ）を使用してI/Oリクエストと完了通知をやり取りします。システムコールを使用せずにI/Oの提出と回収が可能です。最適な条件下では、io_uringは単一コアで約6M IOPSを達成できますが、従来のAIOでは約1Mです。

## リングバッファモデル

```mermaid
flowchart LR
    subgraph USER["ユーザー空間"]
        APP["App<br/>SQへの書き込み / CQの読み取り"]
    end

    subgraph SHARED["共有メモリ (mmap、ゼロコピー)"]
        SQ["Submission Queue<br/>I/Oリクエストキュー"]
        CQ["Completion Queue<br/>I/O完了キュー"]
    end

    subgraph KERNEL["カーネル空間"]
        K["Kernel<br/>SQの読み取り / CQへの書き込み"]
    end

    APP -->|"① SQEへの書き込み"| SQ
    SQ -->|"② カーネルが読み取り"| K
    K -->|"③ CQEへの書き込み"| CQ
    CQ -->|"④ Appが回収"| APP

    classDef user fill:#e3f2fd,stroke:#1565c0
    classDef shared fill:#fff3e0,stroke:#ef6c00
    classDef kernel fill:#f3e5f5,stroke:#7b1fa2
    class APP user
    class SQ,CQ shared
    class K kernel
```

## 基本的な使い方

```c
#include <liburing.h>  // liburingヘルパーライブラリ

struct io_uring ring;
io_uring_queue_init(256, &ring, 0);  // SQに256エントリ

// readリクエストの準備:
struct io_uring_sqe *sqe = io_uring_get_sqe(&ring);
io_uring_prep_read(sqe, fd, buf, size, offset);  // offset: 固定位置
sqe->user_data = 42;  // ユーザータグ (CQEで返される)

// リクエストの提出:
io_uring_submit(&ring);

// 完了の回収:
struct io_uring_cqe *cqe;
io_uring_wait_cqe(&ring, &cqe);  // 完了1つを待つ（ブロッキング）
printf("res=%d, user_data=%lu\n", cqe->res, cqe->user_data);
io_uring_cqe_seen(&ring, cqe);
```

## 高度な機能

### SQポーリング（カーネル側でのビジーウェイト）

```c
// io_uring_setup(..., IORING_SETUP_SQPOLL)
// カーネルがkthreadを起動し、SQを継続的にポーリングする → リクエストがあれば即座に処理
// → io_uring_submit()が不要 → システムコールゼロ！
// → io_uring_enter(ring_fd, 0, 0, IORING_ENTER_GETEVENTS, NULL)
//    完了のみ回収し、提出は行わない（提出はSQポラーターが自動で行う）

// 代償: SQポラータースレッドがCPUを継続的に占有する（1コアの5-10%）
// 適した用途: I/Oが継続的に発生するシナリオ（データベース、ファイルサーバー）
```

### 固定バッファ / 固定ファイル

```c
// バッファとfdを事前に登録:
io_uring_register_buffers(&ring, iovecs, nr_iovs);  // 固定バッファ
io_uring_register_files(&ring, fds, nr_fds);        // 固定ファイル

// その後、登録済みバッファを使用 → 毎回のI/Oでget_user_pagesを回避
// その後、固定fdを使用 → 毎回のI/Oでfget/fputを回避
// → さらなるレイテンシの低減（カーネルオーバーヘッドを約30-50%削減）
```

### IORING_SETUP_DEFER_TASKRUN

```c
// デフォルト: CQE → カーネルが割り込みコンテキストで完了をマーク
// DEFER_TASKRUN: カーネルがI/O完了処理を遅延させ → 次のio_uring_enter呼び出し時にバッチ処理
// → 割り込みの削減 + バッチ処理 → より高いスループット
```

## aioとの比較

| | io_uring | libaio | POSIX aio |
|---|---|---|---|
| 完了通知 | CQのポーリング | io_getevents() | シグナル / コールバック |
| バッファ | 事前登録または毎回の指定 | 毎回の指定 | 毎回の指定 |
| バッファリングI/O | サポートあり (5.6以降) | サポートなし (O_DIRECTのみ) | サポートあり |
| システムコール | 理論上0（SQポーリング時） | 提出ごと + 回収ごと | スレッドプールベース |
| パフォーマンス (4Kランダムリード) | ~6M IOPS | ~1M IOPS | ~0.1M IOPS |

## パフォーマンスの落とし穴

```
1. CQEの回収: io_uring_wait_cqeは1回で1つしか回収しない → 複数の回収にはio_uring_wait_cqesを使用
2. SQが満杯: io_uring_get_sqeがNULLを返す → まずio_uring_submitを実行してから再試行
3. 大きなSQ深度: 256-512で十分 → より大きな深度はメモリを無駄にするだけ
4. リンクタイムアウト: 各リクエストにタイムアウトを設定 → IORING_OP_LINK_TIMEOUT
   → 低速I/Oがリング全体をブロックするのを防止
```

## 参考

- **liburing**: https://github.com/axboe/liburing (examples + tests)
- **カーネルソース**: `fs/io_uring.c` (約20000行)
- **LWN**: "The io_uring API", "io_uring performance improvements"

*キーワード: io_uring, SQ, CQ, SQE, CQE, fixed buffers, SQPOLL, DEFER_TASKRUN, libaio*
