io-uring について
Linux io_uring のキューモデル、liburing API、非同期操作、Rust 連携を Agent 向けに解説するガイドです。高スループットのネットワークやディスクサービスの I/O 設計を検討する際に、性能上の選択肢やセキュリティ上の注意点を把握できます。
主な機能
- 01SQE と CQE のキューモデル
- 02liburing のファイル・ネットワーク操作
- 03マルチショット accept/recv とバッファリング
- 04固定ファイル、登録バッファー、ゼロコピー送信
- 05tokio-uring の Rust 連携
活用シーン
- 01高スループットの Linux ネットワーク・ディスクサービス構築
- 02io_uring と epoll の比較検討
- 03多数接続向けマルチショット accept/recv の実装
- 04Rust での非同期 I/O 統合
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ mohitmishra786/ low-level-dev-skills --skill io-uringインストール後、Claude Code で/io-uringを入力して読み込まれたか確認します。
詳細
io_uring
目的
Linux io_uringについて、エージェント向けに説明します。サブミッション/コンプリーションキューモデル(SQE/CQE)、liburing API、マルチショットのaccept/recv、提供バッファリング、固定ファイルと登録済みバッファ、ゼロコピー送信、tokio-uringとのRust統合、epollとのパフォーマンス比較、セキュリティ上の考慮事項を扱います。
使用する場面
- Linux 5.1以降で高スループットのネットワークサーバーやディスクサーバーを構築する
- システムコールを減らすため、epoll + スレッドプールを置き換える
- 接続数の多いサービス向けに、マルチショットのaccept/recvを実装する
IORING_OP_SEND_ZCでゼロコピーのネットワーク通信を使用するtokio-uring経由でRustの非同期I/Oを統合する- ワークロードに対するio_uringとepollの性能を評価する
ワークフロー
1. SQ/CQモデル
Application Kernel
│ │
├── mmap SQ ring ───────────►│ submission queue (SQE)
├── mmap CQ ring ◄──────────│ completion queue (CQE)
├── io_uring_submit() ──────►│ processes SQEs
└── io_uring_wait_cqe() ◄──│ posts CQEs
各SQEは1つの操作を記述し、各CQEは結果とuser_dataクッキーを報告します。
2. liburingの最小限の例
#include <liburing.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int main(void) {
struct io_uring ring;
io_uring_queue_init(32, &ring, 0);
int fd = open("test.txt", O_RDONLY);
char buf[4096];
struct io_uring_sqe *sqe = io_uring_get_sqe(&ring);
io_uring_prep_read(sqe, fd, buf, sizeof(buf), 0);
io_uring_sqe_set_data(sqe, (void *)1);
io_uring_submit(&ring);
struct io_uring_cqe *cqe;
io_uring_wait_cqe(&ring, &cqe);
if (cqe->res >= 0)
printf("read %d bytes\n", cqe->res);
else
perror("read");
io_uring_cqe_seen(&ring, cqe);
io_uring_queue_exit(&ring);
close(fd);
return 0;
}
gcc -o uring_read uring_read.c -luring
./uring_read
3. よく使われるprep操作
| 関数 | 操作 |
|---|---|
io_uring_prep_read | ファイルの読み取り |
io_uring_prep_write | ファイルの書き込み |
io_uring_prep_recv | ソケットの受信 |
io_uring_prep_send | ソケットの送信 |
io_uring_prep_accept | 接続の受け入れ |
io_uring_prep_connect | アウトバウンド接続 |
io_uring_prep_poll_add | fdのポーリング |
io_uring_prep_timeout | タイムアウト/リンクタイムアウト |
// Batch multiple SQEs before single submit
for (int i = 0; i < n; i++) {
sqe = io_uring_get_sqe(&ring);
io_uring_prep_read(sqe, fds[i], bufs[i], sizes[i], 0);
io_uring_sqe_set_data(sqe, (void *)(intptr_t)i);
}
io_uring_submit(&ring);
4. マルチショット操作
// Multi-shot accept — one SQE handles many connections
sqe = io_uring_get_sqe(&ring);
io_uring_prep_multishot_accept(sqe, listen_fd, NULL, NULL, 0);
io_uring_sqe_set_data(sqe, (void *)ACCEPT_COOKIE);
// Loop on CQEs until CQE_FLAG_MORE is clear
while (1) {
io_uring_wait_cqe(&ring, &cqe);
if (cqe->user_data == ACCEPT_COOKIE) {
int client_fd = cqe->res;
if (client_fd >= 0)
handle_client(client_fd);
if (!(cqe->flags & IORING_CQE_F_MORE))
break; // re-arm accept SQE
}
io_uring_cqe_seen(&ring, cqe);
}
5. 提供バッファリング
#include <liburing.h>
#define BUF_GROUP 0
#define BUF_SIZE 4096
#define BUF_COUNT 64
struct io_uring_buf_ring *buf_ring;
char bufs[BUF_COUNT][BUF_SIZE];
// Setup buffer ring
buf_ring = io_uring_setup_buf_ring(&ring, BUF_COUNT, BUF_GROUP, 0, &ret);
for (int i = 0; i < BUF_COUNT; i++)
io_uring_buf_ring_add(buf_ring, bufs[i], BUF_SIZE, i, BUF_COUNT - 1 - i, 0);
io_uring_buf_ring_advance(buf_ring, BUF_COUNT);
// Recv with provided buffers
sqe = io_uring_get_sqe(&ring);
io_uring_prep_recv_multishot(sqe, sock_fd, NULL, 0, 0);
sqe->buf_group = BUF_GROUP;
カーネルがリングからバッファを選択するため、受信ごとのバッファ割り当てが不要になります。
6. 固定ファイルと登録済みバッファ
// Register files — avoids per-op fd table lookup
int fds[64];
io_uring_register_files(&ring, fds, 64);
io_uring_prep_read(sqe, 0, buf, size, offset); // index 0, not fd
// Registered buffers — pinned, DMA-friendly
struct iovec iov = { .iov_base = buf, .iov_len = size };
io_uring_register_buffers(&ring, &iov, 1);
io_uring_prep_read_fixed(sqe, fd, buf, size, offset, 0);
7. ゼロコピー送信
sqe = io_uring_get_sqe(&ring);
io_uring_prep_send_zc(sqe, sock_fd, data, len, 0, 0);
// May complete with IORING_CQE_F_MORE — wait for notification CQE
カーネル6.0以降と、NIC/ドライバーのサポートが必要です。利用できない場合はコピー方式にフォールバックします。
8. tokio-uring(Rust)
use tokio_uring::fs::File;
#[tokio_uring::main]
async fn main() -> std::io::Result<()> {
let file = File::open("test.txt").await?;
let buf = vec![0u8; 4096];
let (res, buf) = file.read_at(buf, 0).await;
println!("read {} bytes", res?);
Ok(())
}
cargo add tokio-uring
9. io_uringとepollの比較
| 項目 | epoll | io_uring |
|---|---|---|
| システムコール | 操作ごとにepoll_wait + read/write | SQEをまとめて単一のsubmit |
| バッファー管理 | アプリが割り当てる | 提供バッファー |
| ゼロコピー | sendfile、手動で実装 | SEND_ZCを標準搭載 |
| 複雑さ | 低い | 高い |
| カーネルバージョン | 2.6以降 | 5.1以降(機能によって異なる) |
Choose io_uring when
├── Syscall overhead is measurable bottleneck
├── High connection count with multi-shot
└── Kernel 6.x+ with needed op support
Stay on epoll when
├── Simple app, portability matters
└── Team familiarity outweighs gains
10. セキュリティ上の考慮事項
# Restrict unprivileged io_uring (some distros disable by default)
sysctl kernel.io_uring_disabled # 0=enabled, 1=disabled, 2=allow registered
sysctl kernel.io_uring_group # gid allowed when disabled=2
io_uringは攻撃対象領域となってきました。カーネルにパッチを適用し、信頼できないコードはサンドボックス内で実行してください。
よくある問題
| 症状 | 原因 | 対処法 |
|---|---|---|
prepで-EINVALが発生する | カーネルがその操作をサポートしていない | io_uring_get_probe()を確認する |
| wait_cqeでハングする | submitが不足している | SQEの後にio_uring_submitを呼び出す |
| バッファーが返却されない | 提供バッファーが再追加されていない | 消費後にbuf_ring_addを呼び出す |
| SEND_ZCが完了しない | 2つ目のCQEが必要 | IORING_CQE_F_MOREがクリアされるまで待機する |
| epollより遅い | バッチサイズが小さい | SQEをまとめて処理し、SQPOLLを使用する |
| 権限が拒否される | io_uringが無効になっている | sysctlを設定するか、必要なケイパビリティを付与して実行する |
関連スキル
skills/async-io/dpdk— ネットワーク向けのカーネルバイパス方式skills/async-io/af-xdp— パケットI/O向けのXDPソケット経路skills/observability/ebpf— io_uringイベントのトレースskills/rust/rust-async-internals— tokio-uringの基盤となる非同期モデルskills/profilers/strace-ltrace— epollとのシステムコール比較skills/kernel/kernel-internals— カーネルI/Oサブシステムの概要
コメント
Markdown 対応