Skip to content

필사 모드: オペレーティングシステムの現代的理解 — io_uring、cgroups/namespaces、eBPF、NUMA、GPU UVM、EEVDF、Zero-Copy完全ガイド(2025)

日本語
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.

なぜ今OSを学び直すのか

「OSは学部で習ったものでは?」と聞かれたら、答えはこうです:

  • io_uring (2019) — epoll/kqueueの後継。Node.js、Rust tokio、PostgreSQL 17が導入中。
  • cgroups v2(2016+、2022年に主流) — Docker・K8sのリソース制御の基盤。
  • eBPF — 以前の記事(Observability、Network)で登場したあの技術。2020年代のカーネルの革命。
  • EEVDF (2024 Linux 6.6) — CFSスケジューラを置き換えます。
  • NUMA — 32 vCPU以上を使い始めた瞬間から、性能への影響が大きく出ます。
  • GPU UVM (Unified Virtual Memory) — LLM推論・訓練の基盤。
  • io_uringベースのネットワーキング — AF_XDP、DPDKと競合。

2025年のエンジニアがOSを知らなければ、なぜ自分のアプリが遅いのか、なぜコンテナがOOMを食らうのか、なぜCPU 100%なのにスループットが上がらないのかを説明できません。

Part 1 — プロセス、スレッド、コルーチン — 現代の比較

プロセス

  • 別々のアドレス空間。隔離が強力。
  • 生成コストが大きい(fork + exec)。
  • IPCが必要。
  • 例: UNIXシェルのパイプ、Chromeのタブ。

スレッド

  • 同じアドレス空間を共有。
  • 生成コストが小さい(pthread_create)。
  • 共有メモリで通信しやすい + 危険(データレース)。
  • カーネルスケジューリング → コンテキストスイッチのオーバーヘッド。

コルーチン / Fiber / Green Thread

  • ユーザー空間でのスケジューリング。
  • 生成コストがほぼゼロ(数千万個も可能)。
  • 協調的スケジューリング — await地点で譲ります。
  • 例: Go goroutine、Rust async、Python asyncio、Java Virtual Thread(2023)。

Java Virtual Threads — Project Loom (2023)

JDK 21 LTS。「既存のスレッドコードをほぼそのままにして、数百万の同時リクエストを処理する。」

従来のスレッド: スレッドごとにOSスタック1MB+ → 数万個が限界。 仮想スレッド: JVM内部でスケジューリングし、必要なときだけスタックを割り当てる → 数百万が可能。

ブロッキングI/Oを書きながらノンブロッキングの同時実行性を得られます。2024〜2025年のSpring Bootでの採用によって、Javaバックエンドの地形が変わりつつあります。

Go goroutine

  • M:Nスケジューリング(Mカーネルスレッド : Nゴルーチン)。
  • チャネルで通信。
  • スタックは初期2KBから動的に拡張。
  • GOMAXPROCSがP(Processor)の数を決めます。

いつ何を?

状況選択
CPUバウンドの並列スレッド + 共有メモリ
I/Oバウンドの大量同時実行コルーチン/async
強力なセキュリティ隔離が必要プロセス + IPC
JVMで数百万の同時実行Virtual Threads

Part 2 — io_uring — epollを越えて

I/O進化の歴史

  1. ブロッキングI/Oread()がデータが来るまでブロック。
  2. select/poll — FDセットを全部スキャン。O(n)。
  3. epoll (Linux, 2002) / kqueue (BSD) — イベントベース、O(1)。
  4. aio_read(POSIX AIO) — 限界が多い。ほとんど使われません。
  5. io_uring (2019, Jens Axboe) — 真の非同期。

io_uringの構造

Submission Queue (SQ) + Completion Queue (CQ)。どちらもmmapされた共有メモリです。syscallなしで作業を提出し、完了を確認します。

io_uring_prep_read(sqe, fd, buf, len, offset);
io_uring_submit(&ring);  // syscall 1回で複数のリクエストを提出
// ... 後で
io_uring_wait_cqe(&ring, &cqe);

利点:

  • Syscallオーバーヘッドの大幅な削減 — SQPOLLモードならゼロ。
  • Batching — 複数のI/Oを一度に提出。
  • ファイル + ネットワーク + タイマー + acceptの統合。
  • sendmsg、recvmsg、spliceなど大部分をサポート。

採用:

  • Rust tokio(一部)、glommiomonoio
  • Node.js — 実験的。
  • PostgreSQL 17(2024) — 非同期I/Oを基盤としてio_uringの導入を検討中。
  • QEMU、RocksDB、ScyllaDB

セキュリティの問題: 2023年にGoogleがChromeのサンドボックスでio_uringを遮断しました。カーネルの攻撃面を広げるためです。そこで信頼できる環境限定で使うようにという勧告が出ています。

Part 3 — 仮想メモリの現代

4段階ページテーブル (x86_64)

CR3 → PML4 → PDPT → PD → PT → Physical Page

48ビット仮想アドレス = 9+9+9+9+12ビット。

5段階 — 57ビットアドレス空間 (Ice Lake 2021+)

大型サーバーに必要です。2024年にはLinuxの標準設定へと移行中。

TLB (Translation Lookaside Buffer)

仮想→物理変換のキャッシュ。サイズは数百エントリ。TLBミスは性能の隠れた殺し屋です

解決策:

  • Huge Pages (2MB, 1GB) — 1つのTLBエントリで広い領域をカバー。
  • Transparent Huge Pages (THP) — Linuxが自動で統合します。ただし、遅延スパイクの原因になることもあります。

Memory Overcommit

Linuxのデフォルト: 「要求されたメモリを全部渡すふりをする」→ 実際に使うときにページを割り当てる。後でメモリが足りなくなる → OOM Killerが発動します。

echo 2 > /proc/sys/vm/overcommit_memory  # strict accounting

Part 4 — cgroups + namespaces = コンテナ

cgroups v2

リソースグループの階層。CPU、メモリ、I/O、PID数を制限します。

/sys/fs/cgroup/
  my-app/
    cpu.max         # "50000 100000"50% CPU
    memory.max      # "1G"
    io.max          # "8:0 rbps=10485760"  → 10MB/s read

namespaces

プロセスの「自分だけのビュー」。

  • mnt: ファイルシステム。
  • pid: プロセスID。
  • net: ネットワークインターフェース・ルーティング。
  • ipc: IPC。
  • uts: hostname。
  • user: UID/GIDのマッピング。
  • cgroup: cgroupのビュー。
  • time: 2020年に追加、ブート時刻の仮想化。

Dockerの本質

container = chroot + namespaces + cgroups + capabilities + seccomp + AppArmor/SELinux

仮想マシンではありません。 1つのカーネルを共有し、「見える領域」だけが違います。

rootlessコンテナ (2020+)

user namespaceでルートなしにコンテナを実行します。Podman、Buildahが主導しました。

Part 5 — eBPF — カーネルにコードを注入する

eBPFとは

Extended BPF. カーネル空間で実行される小さなVMです。サンドボックスと検証器で安全性を保証します。

なぜ革命なのか:

  • カーネルを修正せずに機能を追加できます。
  • 従来はモジュール開発が必要で、再起動も必要でした。
  • eBPFはリアルタイムにロード、アンロードできます。

使われる場所

  • 観測性: bpftrace、Parca、Pixie。
  • セキュリティ: Tetragon、Falco。
  • ネットワーキング: Cilium、Katran、XDPによる超高速L4 LB。
  • プロファイリング: 継続的プロファイリング(Parca、Polar Signals)。

XDP (eXpress Data Path)

ネットワークカードのNICドライバレベルでeBPFを実行します。パケットがカーネルスタックに入る前に処理する → DDoS防御などに使われます。毎秒数千万パケットの処理が可能です。

開発スタック

  • bpftrace — 一行スクリプト(awkスタイル)。
  • libbpf + CO-RE(Compile Once Run Everywhere) — C/Rust。
  • Aya(Rust) — 2024年に成熟度が高まりました。
// bpftraceの例: open() syscallの追跡
bpftrace -e 'tracepoint:syscalls:sys_enter_openat { printf("%s -> %s\n", comm, str(args->filename)); }'

Part 6 — NUMA — 32コア以上での隠れたコスト

NUMAとは

Non-Uniform Memory Access. 大きなサーバーは複数のソケット(物理CPU)を持ち、各ソケットが自分のメモリバンクを持ちます。別のソケットのメモリアクセスは1.5〜3倍遅くなります。

確認

numactl --hardware
# node 0 cpus: 0-23
# node 0 size: 96GB
# node 1 cpus: 24-47
# node 1 size: 96GB
# node distances: 10 (local), 21 (remote)

NUMAバインディング

# プロセスをNUMAノード0でのみ実行
numactl --cpunodebind=0 --membind=0 ./my-app

DBサーバー、LLM推論、高性能プロキシでは必須です。デフォルトに任せると、スケジューラは最適ではない判断をします。

Kubernetes + NUMA

  • Topology Manager(アルファ→ベータ) — PodをNUMA境界に合わせて配置します。
  • CPU Manager static policy — 専用コアを割り当てます。

LLM推論のK8sクラスタでは、この設定が処理量に30%以上の差を生みます。

Part 7 — Linuxスケジューラの進化

CFS (Completely Fair Scheduler, 2007-2024)

  • 仮想ランタイム(vruntime)で公平性を追求します。
  • nice値で優先度を決めます。
  • red-black treeで管理します。

EEVDF (Earliest Eligible Virtual Deadline First, 2024)

Linux 6.6から標準です。Peter Zijlstraが主導しました。

  • CFSの短所(遅延に敏感なタスクに不利)を解決します。
  • 遅延許容値(slice)パラメータを導入しました。
  • メディア・ゲーム・対話型ワークロードにより親和的です。

CPU隔離の技法

  • isolcpus — 指定したコアを通常のスケジューリングから除外します。
  • nohz_full — タイマー割り込みを除去します。
  • RCUコールバックのオフロード — 指定コアを妨げません。

低遅延トレーディング・HFTインフラでは標準です。

Part 8 — GPUドライバとLLMの関係

GPUコンテナの複雑さ

DockerでGPUを使うには:

  • NVIDIA Container Toolkit — ホストのドライバをコンテナにマウントします。
  • NVIDIA MIG(Multi-Instance GPU) — H100を7つのインスタンスに分割します。
  • MPS(Multi-Process Service) — GPUの共有。

UVM (Unified Virtual Memory, CUDA 6+)

CPUとGPUが同じアドレス空間を共有します。ページフォルト時に必要なページだけを転送します。LLM訓練でGPUメモリより大きいモデルを扱うときに必須です。

CUDA Graph

繰り返されるカーネル呼び出しのパターンをグラフとしてキャプチャし、オーバーヘッドを除去します。LLM推論のデコードループで20%以上の速度改善という事例があります。

2024-2025のGPU OS課題

  • Fractional GPU sharing(Run.ai、Aptakube) — プロダクションでの問題が頻発します。
  • NVIDIA DCGM + K8sメトリクス — GPU観測の標準。
  • AMD ROCm、Intel oneAPI — CUDA代替としての成熟度が徐々に上がっています。

Part 9 — I/O性能の最終形

Zero-Copy

従来: read() → buffer → write()と各段階でコピー。 Zero-copy: sendfile(), splice(), io_uring, MSG_ZEROCOPY — カーネルが直接DMAします。

実際の事例: Kafkaがconsumerにデータを送るときにsendfile()を使用 → CPU使用率が半分に。

DMA (Direct Memory Access)

CPUの関与なしにデバイスがメモリへ直接アクセスします。ネットワークカード、SSD、GPUはいずれもDMAエンジンを内蔵しています。

RDMA (Remote DMA)

別マシンのメモリにCPUの関与なく直接アクセスします。Infiniband、RoCE(RDMA over Converged Ethernet)。

  • レイテンシは1μs台。
  • HFT、HPC、大型データウェアハウス
  • 2024年のAI訓練クラスタの標準 — NVIDIA GPUDirect RDMA。

DPDK / AF_XDP

カーネルのネットワークスタックをバイパスして、ユーザー空間から直接NICを扱います。クラウドの仮想スイッチ、5G UPF、高性能プロキシ(Cloudflare、Fastly)。

Part 10 — WSL2、コンテナ、仮想化の交差点

WSL2 (Windows Subsystem for Linux 2)

  • 実際には軽量Hyper-V VMの中でLinuxカーネルを実行しています。
  • WindowsカーネルとLinuxカーネルが共存します。
  • ファイル性能: WSLネイティブFSは速く、Windowsの/mnt/cは遅いです。
  • 2024年にsystemdの標準サポートが追加されました。

Firecracker (AWS Lambdaの基盤)

  • 2018年にオープンソース化。microVM。
  • 起動は125ms以下。
  • KVMベース、120行未満のVMM。
  • Fly.io、Kata Containersも使用しています。

gVisor (Google)

  • ユーザー空間でのカーネル再実装。
  • syscallをユーザー空間のSentryが処理する → ホストカーネルの攻撃面を縮小します。
  • 性能オーバーヘッドはありますが、隔離は強力です。

Kata Containers

  • OCI互換のコンテナランタイム + microVM。
  • コンテナの手軽さ + VMの隔離。
  • マルチテナントのK8sで有用です。

Part 11 — 観測の道具たち

ツール用途
perfハードウェア + ソフトウェアのイベント
ftraceカーネル関数の追跡
bpftraceeBPFの一行スクリプト
bcceBPFツール集(execsnoop、opensnoopなど)
stracesyscallの追跡(遅い)
ltraceライブラリ呼び出しの追跡
pmapプロセスのメモリマップ
iotop / biolatencyI/O分析
perf topCPUサンプリング
flame graphスタックの可視化(Brendan Gregg)

Continuous Profiling

  • Parca、Pyroscope、Polar Signals
  • eBPFベースの常時プロファイリングはオーバーヘッド1%未満。
  • 「P99は遅いのにCPUは暇」といった謎を解きます。

Part 12 — OSチェックリスト(12項目)

  1. ulimitの確認 — プロダクションサーバーのFD制限、nproc制限。
  2. Swapポリシー — swappiness=1(DB)または0(レイテンシに敏感)。
  3. Transparent Huge Pages — DBはほとんどの場合オフにしたほうが良いです。
  4. NUMAバインディング — ソケットが2つ以上のシステムで。
  5. io_uringのサポート確認 — 最新カーネルでfdの上限を調整。
  6. cgroups v2の使用 — v1は機能が限られます。
  7. seccompプロファイル — コンテナのsyscallを制限。
  8. 基本TCPパラメータのチューニング — somaxconn、tcp_max_syn_backlog。
  9. カーネルバージョンの確認 — 最新LTS(6.6+)はEEVDF、io_uringが成熟。
  10. eBPF観測インフラ — プロファイリングツールを1つは常時稼働。
  11. OOM Killerログの監視 — dmesgの手がかり。
  12. CPU governorperformanceモードを設定(サーバー)。

Part 13 — 10大アンチパターン

  1. コンテナをVMとして扱う — 「カーネル共有」を忘れると、セキュリティ・性能の誤解が生じます。
  2. 1プロセスに数万スレッド — コンテキストスイッチ地獄。コルーチンが答えです。
  3. ブロッキングI/Oで大量の同時実行 — イベントループ / コルーチン / Virtual Thread。
  4. swappiness=60(デフォルト)を維持 — DBは下げるべきです。
  5. 大容量RAMでTHPを無視 — 遅延スパイクの原因になり得ます。
  6. NUMAを無視 — 大きなマシンだからとプロセス1つを回すだけでは性能が半分です。
  7. syscallの多いアプリでstraceを常時稼働 — 100倍遅くなります。eBPFを使ってください。
  8. コンテナをルートで実行 — rootlessまたはdrop capabilities。
  9. Firecrackerを「一般的なK8sコンテナ」と同じように扱う — ストレージ・ネットワークが違います。
  10. GPUをdocker runにそのまま接続 — Toolkit + ドライバのバージョンマトリクスを確認。

おわりに — OSは依然として「性能の境界」

2025年のアプリの性能上限は、しばしばOSが決めます。io_uringを知っているかどうかがネットワークサーバーの処理量に2倍の差を生み、cgroups v2の設定がコンテナのOOMを決め、NUMAバインディングがLLM推論のコストを左右します。

OSは「自分のアプリより下」ではなく「自分のアプリの一部」です。良いエンジニアは必要な瞬間にこの境界を越えられます。/proc/を探検し、perf topを回し、bpftrace一行で答えを得ます。

学部のOS授業で学んだこと(ページテーブル、スケジューラ、セマフォ)は今も生きています。ただ形が進化しました。2025年のOSは、単一カーネル + 数千のコンテナ + GPU + RDMAが共存する世界です。この世界を理解することがエンジニアの新しい基礎です。

次回予告 — 「コンパイラと現代の言語ランタイム」 — LLVM、JIT、GC、Inline Caching、Escape Analysis、WASMランタイムまで

OSの下はハードウェア、OSの上はランタイム。次は言語がどのように実行されるかです。

  • LLVMの支配 — Rust、Swift、Julia、Zig、Crystalが共有する骨格
  • JITコンパイラの内部 — V8のTurboFan、JVMのC2、LuaJIT
  • Hidden ClassとInline Caching — V8がJSを速くする秘密
  • Escape Analysis — スタックに残すかヒープに載せるか
  • Garbage Collectorの系譜 — Mark & SweepからZGC、Shenandoah、G1、Goの3色同時まで
  • Tiered Compilation — V8 Ignition/Sparkplug/Maglev/TurboFan
  • RustのMonomorphization — ジェネリクスはなぜ速いのか
  • Goのwork-stealing — goroutineスケジューラの内部
  • PythonのSpecializing Adaptive Interpreter — 3.13の飛躍
  • WASMランタイムたち — Wasmtime、wasmer、WasmEdgeの比較

「自分のコードが実行されるまでに何が起きているのか?」次回の記事で。

현재 단락 (1/200)

「OSは学部で習ったものでは?」と聞かれたら、答えはこうです:

작성 글자: 0원문 글자: 9,213작성 단락: 0/200