標準カーネルがボトルネックになるとき
数年前、Linuxサーバー上で低レイテンシの音声処理パイプラインを構築する作業を手伝っていたことがあります。CPUが高負荷なわけでもないのに、アプリケーションが断続的にフレームをドロップし続けていました。原因はカーネルスケジューラがタスクを数ミリ秒遅延させていたことでした。そのときに初めてPREEMPT_RTと出会いました。
標準のLinuxカーネルはスループット優先で設計されており、決定性(determinism)は重視されていません。カーネルタスクは長時間ロックを保持することがあります。割り込みハンドラはアプリケーションを予測不可能な形で遅延させることもあります。Webサーバー、データベース、コンテナアプリなど一般的なワークロードではそれで問題ありません。しかし、以下のようなリアルタイムアプリケーションでは話が変わります:
- 産業用制御システム(PLC代替、モーション制御)
- 低レイテンシ音声・映像制作
- マイクロ秒精度が求められる金融トレーディングシステム
- ロボティクスおよび組み込みLinuxプラットフォーム
- 通信インフラ(5G RAN、DPDKベースのパケット処理)
…標準カーネルでは到底対応できません。チューニングされていないシステムでは、スケジューリングの最悪レイテンシが5〜20ミリ秒にまで跳ね上がることがあります。リアルタイムの世界では、それは永遠に等しい遅延です。
PREEMPT_RTが実際に変えること
PREEMPT_RTはカーネルのロックモデルを根本的に再設計します。ほとんどのスピンロックはプリエンプト可能なミューテックスに変わります。割り込みハンドラはハード割り込みコンテキストではなく、プリエンプト可能なカーネルスレッドとして動作します。ほぼすべてのカーネルコードパスが途中で割り込まれる可能性があります。結果として、高優先度タスクはカーネル内部のコードさえもプリエンプトできるようになり、適切にチューニングされたハードウェアでは最悪レイテンシがミリ秒からひと桁マイクロ秒まで劇的に改善されます。
PREEMPT_RTカーネルのインストール
オプション1:ビルド済みパッケージ(Ubuntu/Debian — まずはこちらから)
UbuntuはUbuntu Pro経由でリアルタイムカーネルパッケージを提供しています。無料プランでも、低レイテンシカーネルで十分な改善が見込めます:
# 利用可能なRTカーネルを確認する
apt search linux-image-rt
# 低レイテンシカーネル(完全なRTではなくPREEMPT — 最初の足がかりに最適)
sudo apt install linux-image-lowlatency linux-headers-lowlatency
# Ubuntu ProでフルPREEMPT_RT(最大5台まで無料)
sudo pro attach <your-token>
sudo pro enable realtime-kernel
Debianシステムでは、RTカーネルは標準リポジトリに含まれています:
sudo apt install linux-image-rt-amd64 linux-headers-rt-amd64
オプション2:ソースからビルド
特定のカーネルバージョンが必要な場合、カスタムドライバのサポートが必要な場合、またはビルド設定を完全にコントロールしたい場合は、ソースからのコンパイルが適切な選択です。所要時間の目安は、最新のデスクトップで30〜60分、2コアのVPSなら2時間前後です。経験上、何度も助かったルールがあります:必ずステージング環境でテストしてから本番に適用すること。RTカーネルのビルドは、このステップを省略すると本番環境で思わぬ問題を引き起こすタイプの変更です。
# ビルド依存パッケージをインストール
sudo apt install build-essential libncurses-dev bison flex \
libssl-dev libelf-dev bc dwarves
# カーネルソースと対応するRTパッチをダウンロード
KERNEL_VERSION=6.6.21
RT_PATCH=patch-6.6.21-rt26
wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-${KERNEL_VERSION}.tar.xz
wget https://cdn.kernel.org/pub/linux/kernel/projects/rt/6.6/${RT_PATCH}.patch.xz
tar xf linux-${KERNEL_VERSION}.tar.xz
cd linux-${KERNEL_VERSION}
xzcat ../${RT_PATCH}.patch.xz | patch -p1
# 現在稼働中のカーネル設定をベースにする
cp /boot/config-$(uname -r) .config
make olddefconfig
# フルPREEMPT_RTを有効化
scripts/config --enable PREEMPT_RT
scripts/config --disable PREEMPT_VOLUNTARY
scripts/config --disable PREEMPT
# .debパッケージとしてビルド(インストール・削除が容易)
make -j$(nproc) deb-pkg LOCALVERSION=-rt
# インストール
sudo dpkg -i ../linux-image-*.deb ../linux-headers-*.deb
# 再起動してGRUBでRTカーネルを選択
sudo reboot
再起動後、RTカーネルで起動していることを確認します:
uname -a
# 出力にPREEMPT_RTが含まれていることを確認
# 例:Linux myhost 6.6.21-rt26 #1 SMP PREEMPT_RT Sat Apr 5 ...
リアルタイムパフォーマンスのためのシステム設定
RTカーネルのインストールは簡単な部分です。本当の作業は、OSの残りの部分にRTタスクを邪魔させないよう設定することです。
CPUアイソレーション
RTワークロード専用にCPUコアを予約します。アイソレーションが設定されると、カーネルスケジューラはそのコアに他の処理を割り当てなくなります:
sudo nano /etc/default/grub
# GRUB_CMDLINE_LINUX_DEFAULTに追加 — ここではコア2と3をアイソレーション:
GRUB_CMDLINE_LINUX_DEFAULT="quiet splash isolcpus=2,3 nohz_full=2,3 rcu_nocbs=2,3"
sudo update-grub
sudo reboot
nohz_fullはそれらのコアのタイマーティックを無効化し(割り込み起因のジッターを削減)、rcu_nocbsはRCUコールバックをそれらのコアから退避させます。この3つのパラメータは連携して機能します。必ず3つセットで使用してください。
IRQアフィニティ
ハードウェア割り込みをアイソレートされたコアから遠ざけます。最もシンプルな方法は、irqbalanceがそれらのコアを避けるよう設定することです:
sudo apt install irqbalance
sudo nano /etc/default/irqbalance
# ビットマスク — コア2と3 = 2進数1100 = 16進数0xC
IRQBALANCE_BANNED_CPUS="0x0c"
sudo systemctl restart irqbalance
irqbalanceが処理しない特定のIRQ(NICのMSI割り込みなど)は手動でピン留めします:
# アクティブなIRQをすべて確認
cat /proc/interrupts
# IRQ 30をCPU 0のみにピン留め(smp_affinityはCPUビットマスク)
echo 1 | sudo tee /proc/irq/30/smp_affinity
RTプロセスのリソース制限
RTプロセスはメモリのロックと高いスケジューリング優先度を設定する権限が必要です。/etc/security/limits.confに追加します:
sudo nano /etc/security/limits.conf
# 'rtuser'を実際のユーザー名に置き換えてください。audioグループには@audioを使用できます
rtuser - rtprio 99
rtuser - memlock unlimited
rtuser - nice -20
カーネルチューニングパラメータ
sudo nano /etc/sysctl.d/99-realtime.conf
# RTコアのカーネルウォッチドッグを無効化(コア0と1のみ — 16進数0x3 = 2進数0011)
kernel.watchdog_cpumask = 0x3
# 必要に応じてRTタスクがCPUを100%使用できるようにする
# 警告:RTタスクが予期せずスピンした場合は950000に戻すこと
kernel.sched_rt_runtime_us = -1
# スワップ使用を最小化
vm.swappiness = 0
vm.dirty_ratio = 10
vm.dirty_background_ratio = 5
sudo sysctl -p /etc/sysctl.d/99-realtime.conf
CPU周波数ガバナー
周波数スケーリングはレイテンシのばらつきを引き起こします。RTコアを最大周波数に固定します:
sudo apt install cpufrequtils
# アイソレートされたコアにperformanceガバナーを設定
sudo cpufreq-set -g performance -c 2
sudo cpufreq-set -g performance -c 3
# 確認
cat /sys/devices/system/cpu/cpu2/cpufreq/scaling_governor
# performance
RTスケジューリングでアプリケーションを起動
アプリケーションをアイソレートされたコアにピン留めし、SCHED_FIFOスケジューリングを設定します:
# SCHED_FIFO優先度80、コア2にピン留め
sudo chrt -f 80 taskset -c 2 ./your-rt-application
# 実行中のプロセスのスケジューリングポリシーを確認
chrt -p <PID>
検証とモニタリング
チューニングが効いているかどうかを思い込みで判断してはいけません。必ず計測してください。標準的なRTレイテンシベンチマークはcyclictestです。タイマーが発火すべきタイミングと実際に発火したタイミングの差を計測します。
cyclictestの実行
sudo apt install rt-tests
# アイソレートされたコア2でRT優先度80、60秒間実行
sudo cyclictest \
--mlockall \
--priority=80 \
--interval=200 \
--distance=0 \
--affinity=2 \
--duration=60s \
--histogram=400 \
--histfile=latency.hist
注目すべき3つの数値:
- Min:ベースラインのハードウェアレイテンシ — 最新のx86では通常1〜10 µs
- Avg:定常負荷時の通常動作レイテンシ
- Max:最悪ケースのジッター — アプリケーションが実際に耐えなければならない値
適切にチューニングされたPREEMPT_RTシステムでは、Maxは100 µs未満であるべきです。産業用制御やプロフェッショナル音声制作など本格的な用途では、良質なサーバーハードウェアで20〜50 µsを達成することも珍しくありません。同じテストを標準カーネルで負荷をかけながら実行すれば、5〜20 msのスパイクは当然のように発生します。
実際の負荷でテスト
アイドル状態のレイテンシ数値はほとんど意味がありません。システムが実際に負荷を受けたときに何が起きるかを把握する必要があります:
# ターミナル1:レイテンシを計測
sudo cyclictest --mlockall --priority=80 --interval=200 --duration=120s --affinity=2
# ターミナル2:CPUとメモリに負荷をかける
sudo apt install stress-ng
stress-ng --cpu 2 --vm 1 --vm-bytes 1G --io 2
# ターミナル3:ネットワーク負荷
iperf3 -s &
iperf3 -c localhost -t 120 -P 4
3つを同時に実行し、Maxの値を監視します。すべてのストレスが掛かった状態でアプリケーションのデッドライン内に収まっていれば、そのチューニングは本番環境での検証に十分耐えられるものです。
ftraceでレイテンシスパイクをトレースする
cyclictestで説明できない散発的な外れ値が出る場合、ftraceで原因となるカーネルコードパスを特定できます:
# プリエンプト/IRQオフレイテンシトレーサーを有効化
echo preemptirqsoff | sudo tee /sys/kernel/debug/tracing/current_tracer
echo 1 | sudo tee /sys/kernel/debug/tracing/tracing_on
# 数秒間ワークロードを実行してからトレースを取得
sudo cat /sys/kernel/debug/tracing/trace | head -80
# トレースを無効化
echo 0 | sudo tee /sys/kernel/debug/tracing/tracing_on
RTヘルスチェックスクリプト
再起動後、すべてのRT設定が復元されるとは限りません。IRQアフィニティは最も問題になりやすく、カーネル更新後にサイレントでリセットされることがあります。このスクリプトは、RTワークロードが問題に気づく前に異常を検知します:
#!/bin/bash
# rt-check.sh — 再起動後のRT設定を検証する
echo "=== カーネル ==="
uname -r | grep -q PREEMPT_RT && echo "OK: PREEMPT_RT 有効" || echo "警告: 標準カーネル"
echo ""
echo "=== CPU ガバナー ==="
for cpu in /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor; do
echo " $(basename $(dirname $(dirname $cpu))): $(cat $cpu)"
done
echo ""
echo "=== アイソレートされたCPU ==="
echo " $(cat /sys/devices/system/cpu/isolated 2>/dev/null || echo 'なし')"
echo ""
echo "=== スワップ設定 ==="
echo " $(sysctl vm.swappiness)"
chmod +x rt-check.sh
./rt-check.sh
カーネル更新や再起動のたびに、RTワークロードを再稼働させる前に実行してください。特に無人アップグレード後のサイレントな設定ミスを何度も検知してきたスクリプトです。

