リアルタイムカーネルパッチセット
このページでは Linux カーネルのリアルタイムパッチセットや、スケジューリングの遅延を解決するために役立つユーティリティについて説明しています。
目次
リアルタイムとは
リアルタイムアプリケーションにはイベントが発生してからアプリケーションがイベントに対応するまでにかかる時間のデッドラインが存在します。許容できるデッドラインを守るために、特定のアプリケーションや環境がレスポンスを返す最大応答時間を正確に計算できるリアルタイムオペレーティングシステム (RTOS) が使われます。一般的な RTOS は優先順位を使用します。一番高い優先度が割り当てられたタスクについては、イベントが発生してから CPU は必ず決められた時間内に処理を行います。そのような RTOS ではタスクのレイテンシは同等以上の優先度が設定されているタスクによって決まり、優先度が低いタスクは全て無視できます。通常の OS (普通の Linux など) ではレイテンシはシステム上で動作している全てのタスクによって変わってくるため、ある程度複雑な環境ではデッドラインを守ることを必ずしも保証できません。プリエンプションがオフになると時間が予測できなくなるためです。プリエンプションが切られている場合、高優先度のタスクも低優先度のタスクによって遅延が発生する可能性があります。
リアルタイムパッチの仕組み
RT-Preempt パッチは Linux を完全にプリエンティブルなカーネルに書き換えます。主に以下のようなことが行われます:
- rtmutex を再実装することにより (スピンロックを使用する) カーネル内のロック機構をプリエンティブにします。
- spinlock_t と rwlock_t によって保護されているクリティカルセクションがプリエンティブになります。raw_spinlock_t を使うことで (カーネル内に) ノンプリエンプティブなセクションを作成できます (API は spinlock_t と同じです)。
- カーネル内のスピンロックとセマフォについて優先度継承を実装します。
- 割り込みハンドラをプリエンティブなカーネルスレッドに変換します: RT-Preempt はソフト割り込みハンドラをカーネルスレッドのコンテキストで扱います。通常のユーザー空間プロセスのように task_struct が使われます。ただしカーネルコンテキストに IRQ を登録することが可能になります。
- Linux の旧式なタイマー API を高精度なカーネルタイマーとタイムアウト用のタイマーからなる仕組みに変換します。ユーザー空間の POSIX タイマーが高精度になります。
インストール
AUR には -rt パッチが適用されたカーネルが多数存在します。主要なカーネルは linux-rtAUR と linux-rt-ltsAUR です。どちらも標準の linux カーネルパッケージのコンフィグレーションを基にしています。linux-rt は -rt パッチの開発ブランチに追従しており、linux-rt-lts は rt パッチセットの安定ブランチに追従しています。
スケジューリングレイテンシ
スケジューラにとって、レイテンシとはイベントが発生してからイベントを処理するまでにかかった時間のことです。大抵の場合、割り込みハンドラが起動する前に割り込みが起こることで遅延が生まれますが、タイマーの期限切れなどによって発生することもあります。
レイテンシ自体は自然なことで、いつだって多少の遅延は存在しています。問題となるのは遅延がアプリケーションを正しく動作するのに必要なデッドラインを超過する場合です。デッドライン内に処理が終われば成功で、デッドラインを超過した場合、失敗です。
スケジューリングレイテンシが発生する要因は様々です。一部を挙げると (順番に意味はありません): システムの設定がおかしい、ハードウェアが要件を満たしていない、カーネルモジュールの実装に問題がある、CPU の電源管理、ハードウェアタイマーが不正確、SMI、SMT など。
システムの最大スケジューリングレイテンシを計測する場合、システムを負担状態にする必要があります。大抵はアイドル状態のときよりもビジー状態のときに大きな遅延が発生します。自然的・人為的を問わず様々な条件でテストを実行してみることを推奨します。また、ディスクやネット IO、USB、グラフィックサブシステムなど本番環境で使用する全てのサブシステムに対して負担をかけてみると良いでしょう。
遅延をテストするユーティリティ
カーネルのスケジューリングレイテンシをチェックして、遅延が発生した原因を特定することができるツールは複数存在します。そのようなツールは rt-tests パッケージにまとめて含まれています。
cyclictest
リアルタイムテストで使われるプログラムである cyclictest は最大スケジューリングレイテンシを確認して、レイテンシのスパイクを追跡します。cyclictest はスレッドが設定したタイマーの有効期限とスレッドが実行されるまでの間の時間を計測することによって動作します。
テストの実行例:
# cyclictest --smp -p98 -m
# /dev/cpu_dma_latency set to 0us policy: fifo: loadavg: 239.09 220.49 134.53 142/1304 23799 T: 0 (23124) P:98 I:1000 C: 645663 Min: 2 Act: 4 Avg: 4 Max: 23 T: 1 (23125) P:98 I:1500 C: 430429 Min: 2 Act: 5 Avg: 3 Max: 23 T: 2 (23126) P:98 I:2000 C: 322819 Min: 2 Act: 4 Avg: 3 Max: 15 T: 3 (23127) P:98 I:2500 C: 258247 Min: 2 Act: 5 Avg: 4 Max: 32 ^C
上記の例ではクアッドコア CPU 環境で優先度98のひとつのスレッド (SCHED_FIFO) が動作しており、別のターミナルで hackbench を実行しているためにシステムは負担状態になっています。一番重要なのはコア3で32マイクロ秒の遅延が検出されていることです。
詳しくは cyclictest(8) の man ページを参照。
hackbench
待機状態のカーネルは低いレイテンシを表示しがちなので、本当の結果を得るにはある程度負担をかけることが重要です。rt-tests パッケージに含まれている hackbench というユーティリティを使うと良いでしょう。複数のスレッドまたはプロセスを生成して、ソケットやパイプでデータを交換します。長時間動作させるには -l パラメータを追加してください: hackbench -l 1000000
。
詳しくは hackbench(8) の man ページを参照。
hwlatdetect
hwlatdetect を使うことで、通常以上の時間が費やされて、通常のカーネル実行をブロックして遅延を生み出している SMI を検知できます。カーネルモジュール (linux-rt と linux-rt-lts の両方に存在) とプロセスを起動してユーザーに結果を報告する python スクリプトから構成されます。システムが NMI を使用しているかどうか確認するには以下のコマンドを実行:
$ grep NMI /proc/interrupts
NMI: 3335 3336 3335 3335 Non-maskable interrupts
hwlatdetect カーネルモジュールは stop_machine() コールによって CPU で動作しているものを全てオフにすることで機能します。それから TSC (Time Stamp Counter) をポーリングして生成されたデータストリームに間隙が存在しないかチェックします。間隙の存在は NMI によって割り込みが発生したことを意味します。NMI 以外に間隙が生まれることはありません (TSC の実装が壊れている場合は除く)。検知する閾値を15マイクロ秒に設定してプログラムを120秒間実行するには:
# hwlatdetect --duration=120 --threshold=15
hwlatdetect: test duration 120 seconds parameters: Latency threshold: 15us Sample window: 1000000us Sample width: 500000us Non-sampling period: 500000us Output File: None Starting test test finished Max Latency: 21us Samples recorded: 16 Samples exceeding threshold: 16 1408928107.0286324723 18 17 . . 1408928180.0296881126 15 21 . . 1408928212.0300332889 18 18
上記の結果では指定した15マイクロ秒の閾値を超えた16個の NMI が検知されており、検出された最大遅延は21マイクロ秒となっています。
詳しくは hwlatdetect(8) の man ページを参照。