2 ポイント 投稿者 GN⁺ 2023-11-17 | 1件のコメント | WhatsAppで共有
  • Linux の リアルタイムプリエンプション 対応は、20年近くメインライン入りを待ち続けてきた取り組みであり、Thomas Gleixner は 2023 Linux Plumbers Conference で最後の大きな障害が printk() だと明らかにした
  • 目標は、最高優先度のプロセスが 予測可能な短いレイテンシ で実行されるようにすることであり、そのためにカーネルの多くの中核部分が長年にわたって書き直されてきた
  • printk() はどのようなコンテキストからでも呼び出され得るため、単純なログ出力よりはるかに厄介であり、現在の 同期式出力 はリアルタイムのレイテンシ目標と衝突する
  • 2018年以降、およそ 300個のパッチ が upstream に入るか linux-next で待機しており、残る課題は緊急メッセージの handover とコンソールドライバの安全性処理である
  • printk() の整理が終わり、残りのリアルタイムコードが linux-next で準備できれば、同じ merge window での統合も可能だが、Gleixner は完了時期の予測はもうしないと述べた

20年近く続くリアルタイムプリエンプション作業

  • Linux のリアルタイム対応は LWN で 2004年に初登場 しており、長いあいだ「あと少しで終わる」ように見えていた
  • LWN は 2009年にも the realtime preemption endgame という見出しを付けたが、2023 Linux Plumbers Conference で Gleixner は、今度こそ本当に終わりが近いと見ている
  • Gleixner 個人にとっては、ほぼ 25年がかりの仕事 である
    • 彼は 1999年に Linux リアルタイム対応の作業を始めた
    • プロジェクト自体も20年近く続いてきた
  • 作業が終われば「a big party」があるだろうとしたが、最後の大きな障害として printk() が残っている

リアルタイムプリエンプションが減らそうとしているレイテンシ

  • リアルタイムプリエンプションの目的は、最高優先度のプロセスが常に 最小かつ予測可能なレイテンシ で実行できるようにすることにある
  • そのために、カーネルは可能な限り多くの状況でプリエンプト可能でなければならず、例外は狭く明確な範囲に限定されるべきである
  • 基本的な動作方式はかなり前に固まっていたが、細かな問題を解決するのに長い時間がかかった
  • この過程でカーネル中核部の多くが書き直され、その恩恵はリアルタイム用途を超えてカーネル全体に広がっている

printk() が最後の障害である理由

  • カーネルコードがコンソールやログにメッセージを送る必要があるときは、printk() またはそれを基盤にした関数を呼び出す
  • 単純な出力に見えるが、printk()ほぼあらゆるコンテキスト で動作しなければならない
    • non-maskable interrupt ハンドラから呼ばれることもある
    • 別の printk() 呼び出しの内部から再度呼ばれることもある
    • システムクラッシュ時には出力情報が重要になり得るため、呼び出しコンテキストを簡単に制限できない
  • こうした要件のため、printk() には並行性、ロック、ドライバ処理の問題が複雑に絡み合っている
  • 現在のカーネルの printk() は完全に 同期式の構造 である
    • 呼び出しは、メッセージが設定されたすべての宛先に送られるまで戻らない
    • Gleixner はこの構造を「stupid」と表現した
    • 特にブート中は、出力の大半が単なるノイズにすぎないこともあるのに、すべて送信し終えるまで待たされる
  • この待ち時間は、リアルタイム作業が減らそうとしているレイテンシと真っ向から衝突する
  • リアルタイム開発者たちは以前から printk() 出力を別スレッドに移して非同期化していたが、そのコードは根本的な解決策というより、いくつものハックに近かった

2018年以降の printk() 再設計

  • printk() 問題は 2018年から本格的に扱われており、約 300個のパッチ が upstream に入るか linux-next で待機している
  • 現在、作業を終えるための最後の 3 つのパッチセットが進行中である
  • 最も厄介な細部課題の1つが handover メカニズム である
    • カーネルがクラッシュのような緊急メッセージを出力しなければならないとき、低優先度メッセージを出力中のコンソールから制御を奪う必要があるかもしれない
    • これをどのようなコンテキストでも安全に行うのは容易ではない
  • もう1つの課題は、特定のコンテキストで安全に使えないコンソールドライバに印を付けることである
    • たとえば non-maskable interrupt 中にメッセージを出力しようとしても、ビデオモード設定が必要なら動作できない
  • Gleixner は、この1年ほどで根本的な概念の変化はないと答えている
    • カーネルには修正が必要なコンソールドライバが 76個 ある
    • handover コードは、すべてのドライバを一度に直さなくても、ドライバを1つずつ更新できるように変更された
    • 最近の printk() 作業に関する追加の議論は この記事 にある

非同期出力とメインライン統合の条件

  • Masami Hiramatsu が、どのカーネルメッセージを同期式で出力すべきか尋ねると、Gleixner はほぼすべてを 非同期式 にすべきだと答えた
  • 非同期出力は printk() 呼び出しによって生じるレイテンシを減らし、コンソールごとに個別のカーネルスレッドを持てるようにする
    • 高速なコンソールは最も遅いコンソールを待たず、自分の速度で動作できる
  • 重要なメッセージは、最初の行が出力される前にメッセージバッファへ完全にコピーされるよう、コードが変更された
    • これは不具合のあるコンソールドライバがシステム全体を壊す場合に備えるための措置である
  • より安全な出力順序のため、既知の安全なコンソールへ先に書き込む
    • たとえば persistent-memory store があれば、物理デバイスへ送る前に先にメッセージを保存する
    • 不具合のあるドライバがシステムを停止させても出力内容を保持するための方法である
  • Gleixner は、作業は終盤に近づいているが printk() は予測しづらいとして、完了時期はもう語らないと述べた
  • それでも、残りのリアルタイムプリエンプションコードが 2024年末の20周年前にメインラインへ入ることを期待していると明かした
  • Clark Williams が、printk() パッチの upstream 化後に残りのリアルタイムコードを同じ merge window に入れるのかと尋ねると、Gleixner は条件付きで「yes」と答えた
    • すべてのコードが linux-next に staged され、準備できているように見えれば試みることができる

1件のコメント

 
GN⁺ 2023-11-17
Hacker Newsの意見
  • QNXは数十年前からこの点をきちんと実現していた。マイクロカーネルは自分が行うすべての処理に上限があり、コードも数万行しかない
    マイクロカーネルが行うのは、メモリ割り当て、CPUディスパッチ、プロセス間メッセージ受け渡しだけ。ドライバやロガーを含む残りはすべてユーザー空間にあり、より高い優先度のスレッドによってプリエンプトできる
    QNXカーネルは文字列を扱わない。パースも、フォーマットも、メッセージもない。Linuxはリアルタイム用途には肥大化しすぎており、数百万行のカーネルコードすべてをプリエンプト可能にしなければならないため、構造自体がリアルタイムに向いていない。だから修正に20年かかったのだ

    • 現代的な例としては seL4 がある。動的メモリ割り当てを行わないと理解しており、複数の性質について形式検証もされている
      カーネル設計への最大の貢献はおそらく、capabilityを全体的に使うことで、制御権をユーザー空間へ安全かつ柔軟に渡す方式にある
    • QNXは車載 インフォテインメントシステム で使われるものではないのか? 他にはどこで使われているのか気になる
      カーネルの肥大化そのものはあまり気にならない。Linuxには多くの開発時間が投入されているし、デスクトップがサーバーほど優先されていないとしても、携帯機器のような分野で高性能なカーネルを作るための取り組みはデスクトップユーザーにも利益になるだろう
    • 現在の SDP 8カーネル はコメントとMakefileまで含めて15,331行だ
    • CやC系言語で、よく構造化された main 関数のように見える。main は他の関数呼び出しを調整するだけで、ここではQNXカーネルの初期化が少ないという違いはあるものの、大きな考え方は似ている
      カーネル開発者ではないが、このように単純さを保つやり方はよさそうに見える
    • 「数百万行のカーネル」のうち約 90%はデバイスドライバ だ。任意のハードウェアで動かすなら、マイクロカーネルでも結局は必要になる
  • システムが死にかけている状況でも、カーネルが何とかログメッセージを出そうとしており、実際の本番環境でそれがどう使われるかを示す例がある
    https://netflixtechblog.com/kubernetes-and-kernel-panics-ed6...

  • この問題が修正されれば、リアルタイム向けに作られたハードウェア/ソフトウェアの組み合わせの一部をかなり置き換えられるのか気になる。今では安価で低消費電力かつ高クロックのARMおよびx86チップの選択肢が多い
    クロックが非常に高いため、取りこぼしがあっても余剰サイクルが多く、完全なリアルタイム性の重要度が下がることも多いかもしれない。エレガントでも効率的でもないのは分かるが、ときには 汎用品 が正確さに勝ることもある

    • ハードリアルタイム が必要な仕事は、「取りこぼしても余剰サイクルが多い」で満足するわけにはいかない。CPUサイクルだけの問題でもない
      作りの悪いタスクひとつでカーネルを占有し、有用な仕事ができなくなる可能性がある。ハードリアルタイムの本質は、「この重要なタスクの実行を何ものにも妨げられない」ことだ。自動車や航空宇宙の分野では、制御システムがどんな状況でも動作しなければならない
    • 本当にリアルタイム要件があるアプリケーションは、通常、ごく小さな失敗の可能性すら許容できないほど要件が厳しい。アビオニクス、医療機器、自動車、軍事アプリケーションを思い浮かべればよい
      本当にリアルタイムが必要なら、本当に必要なのであって、「十分に近い」は存在しない。ただし部外者としての印象だ
    • 低消費電力・高クロックのARMチップでリアルタイムアプリケーションを作るときは、OSをまったく使わない。そういう用途ではx86も考慮しない
      OSはRTOSであっても邪魔が多すぎる。この変更が何を変えるのかは分からない。ただ、アプリケーション次第であり、「ほぼリアルタイム」程度が必要なケースは多いので、そうした用途には有用かもしれない
    • その通りだが、これによって 専用コア の必要性が魔法のようになくなるわけではない。おそらくスケジューラに、プリエンプト不可のリアルタイムタスクをLITTLEコア1つだけに配置するよう指示する形になる可能性が高い
  • ここでの議論は、「ハード」リアルタイムアプリケーションと「ソフト」リアルタイムアプリケーションの区別に焦点が当たっている。ハードリアルタイムでは、Linuxのような汎用OSをそもそも使いたくない可能性が高く、ビデオ会議やオーディオ再生のようなソフトリアルタイムでは、ときどき途切れたり数フレーム落ちたりしても大事にはならない
    RT Linuxはそのようなソフトリアルタイム用途に対する強力な解決策になる、という理屈だ。しかし提案されているソフト用途は、今でも組み込みLinuxで可能だ。低遅延のソフトウェア映像再生やオーディオ再生が不可能だったわけではなく、20年前でも可能だった
    問題は、負荷の高いシステムでプリエンプト不可のI/Oが頻繁に割り込んでくるときに生じるが、組み込み環境ではそうしたケースはまれだ。カーネルを完全にプリエンプト可能にし、スケジューリングの制御権をより多く与えることには説得力のある理由があるが、それはLinuxが最小構成のリアルタイムOSやベアメタルコードを置き換えるべき理由とはあまり関係がない
    単に良い衛生管理に近く、リアルタイムでないアプリケーションでも負荷時によりよく動作するOSを作ることにつながる

  • 良い知らせではあるが、Linuxカーネルがリアルタイムになっても、ハードウェアはキャッシュやCPU内部の複雑な魔法のせいでリアルタイムではない可能性が高い
    大きく複雑なハードウェアは真のリアルタイムには向いていない。だからAbsIntや最悪実行時間(WCET)ツールは主に単純なCPUアーキテクチャを扱う。8051は本当に永遠に生き残るだろう。ちなみにZephyr RTOSもある

    • 現代CPUの機能がリアルタイム用途を妨げるわけではないと理解している。何であれ上限があり推論可能なら、リアルタイムシステムを作るのに使える
      キャッシュヒットがまったくなく最大負荷の状況などを仮定すればよい。かかる時間に上限を置けるなら問題ない
    • Raspberry Piのような「大きい」マイクロコントローラクラスのボードではかなり有用だと思う。そこには一種のリアルタイム文化があり、CPUでビットバンギングを直接しなくても外から見ればすべてが時間どおりに合う
      タイマーが直交エンコーダ入力を受けてラップしたときだけ割り込みを送ったり、GPIOシステムをDMAにつないでCPU介入なしにメモリを出力ピンへストリーミングしたりできる。DACへストリーミングしたりADCからメモリへDMA転送したりも可能だ。こうしたものは予測可能な遅延のためにキャッシュを回避することが多い
    • SpaceXはロケットに x86プロセッサ を使っている。NASAが火星に送った小型ドローンヘリコプターも、昔のAndroidを動かせる程度には「かなり大きい」ARMコアを使っている
    • 大きく複雑なハードウェアが真のリアルタイムに向かないという話は、必ずしも正しくない。Arm Cortex-R82 のような高性能リアルタイムコアがある
      実際、多くのリアルタイムシステムは増え続けるセンサーデータを処理して集約しなければならず、ますます高性能になっている
    • 68000 こそリアルタイムの真の王だ
  • 若手のころ、面接官が リアルタイム が実際に何なのか分かっていない面接を、うんざりするほど多く経験した。記事にあった「そして予測可能な遅延」という概念を見落とす人が多く、リアルタイムをただ「速い」という意味だと考えているようだった

    • 「最小」という部分も完全に外したい。リアルタイムの核心は、作業に予測可能な 上限 があることだ。これは非リアルタイムシステムより平均的には遅い場合もあることを意味する
      自動車のブレーキシステムを制御するなら、「平均遅延は50msだが最大80ms」は許容できるかもしれないが、「平均遅延は1msだが任意に長くなり得て、数秒かかることもある」は許容されない
    • 昔から言われるように、「real time」は「real fast」ではない。ハードリアルタイムとソフトリアルタイムの区別が少し曖昧にしてはいるが、多くのソフトウェア開発者もリアルタイムが実際に何なのかをきちんと理解していないと思う
  • 同期ログ がまた問題を起こした。会社でGLOG(Googleのロギングライブラリ)が原因で似たようなことを経験したが、たとえばstdoutがファイルならディスクI/Oでブロックされることがある
    うちのサービスが100ms超停止したとき、原因の90〜99%はGLOGだった

    • ロギングをめぐって同僚とこんな会話をよくする。「ベストエフォートAPIと保証配信APIがあります」「保証配信が欲しいです!」「保証配信ロギングインターフェースがオフラインだったり遅かったりしたらサービス停止になりますが大丈夫ですか?」「だめです、停止は困ります!」
      「必ずログに残さなければならないのに、ログを残せないならどうするのですか?」 最近はただ CAP定理 を指して、ロギングもほかの分散システムと同じだと言っている。三角形の図と「定理」という単語のあるWikipedia文書があるせいか、人は受け入れやすいようだ
    • syslogサーバーが止まって、本番環境全体が一度停止したことがある。ログを TCP で押し込んでいて、そのブロッキングが本番環境全体に伝播した
      その後はUDP送信に変えた。本番全体を失うより、ログの一部を失うほうがましだからだ
    • $MSFT のロギングライブラリでも、「ロギングライブラリが全部壊す」タイプの問題があった。スレッド100本がそれぞれ300MBのロギングバッファを持つところを想像してほしい
      当然メモリを食い潰し、Azure App Serviceの最も高価なSKUでもサーバーがクラッシュした
    • 製品の可用性が±100msにかかっているなら、深刻に設計が間違っているのであって、それはロギングライブラリのせいではないと思う。ユーザーはボタンを押してから完了まで100ms余計にかかるかどうかは気にしないだろう
  • 昔を強く思い出す。17〜18年前、より厳密なタイミングが必要な科学機器で使うために、Debian向けカーネルを RT_PREEMPT でコンパイルした
    遅延とジッターは非常に印象的だった。その後はほとんど考えたことがなかったが、Raspberry Piで組み込みアプリケーションを作る際、RTOSを使うマイコンへ移行したくない場合には使いどころが多そうだ

    • Raspberry Piに言及しているのが興味深い。1日か2日前に、RpiOSがRTOS上で起動して実行されるという投稿を見た
      以前にも、LinuxをRTOSのタスクとして実行できるという提案を見たことがあり、特に興味深かった。ハードリアルタイムの締め切りが必要なものはRTOS上で動かし、仮想メモリシステムが引き起こし得る遅延の影響を受けないようにする方式だ。これが単なるアイデアだったのか実装されたものだったのかは覚えておらず、RpiOSがRTOS上にあるという言及も一度見ただけなので気になっている
  • 一般ユーザーにとってこれが何を意味するのか気になる。ごく特定の状況でだけ有効にする機能なのか、それとも一般大衆にもより応答性の高いシステムをもたらせるのか気になる

    • 私の理解では、リアルタイム化はシステムをより遅くする。リアルタイムであるためには、すべてに時間割り当てを設けなければならない
      各タスクは X という予算を与えられ、それを超えてはならない。最良の場合は速くても最悪の場合が遅いなら、システムは常に最悪の場合を前提にしなければならないという意味
    • RT は必ずしもレイテンシを改善するわけではなく、一部のタスクに固定された上限を与えるもの。ただし、RT を可能にするために必要な作業は、一般的なケースでのレイテンシを確実に改善しうる
      同期的な printk() 呼び出しを避ける例がまさにそれで、RT を有効にしなくても負荷時のレイテンシは改善されるべき。完全にアップストリーム化された RT カーネルは、実際に RT プロセスを実行しない限り通常のカーネルと異なる動作はしないだろうと思う。アップストリーム化に長くかかった理由は、RT を可能にするためのトレードオフが必要だったからであり、記事によればそのようなトレードオフはもうあまり残っていない
    • 「一般」ユーザーがデスクトップユーザーを指すなら大きな変化はない。しかし、産業制御や通信機器のような組み込み機器にとっては大きな出来事
      リアルタイムスケジューリングが必要な場合でも最新のメインラインカーネルを使えるようになるため
    • 私の理解では、Linux が RTOS を必要とする状況での選択肢になるということ。航空、医療機器のような重要システム向けで、一般ユーザーにはほとんど影響がない
    • 恩恵を受ける最も一般的なデスクトップのエンドユーザーはオーディオ作業をする人たち。そこではレイテンシ、特にジッタがかなり厄介になりうる
  • Xenomai[1] についてどう思うか気になる。何年も問題なく使っている
    BeagleBone Black では通常は数百ナノ秒レベルのジッタが出ており、「ハード」リアルタイムだと見なしている。数十マイクロ秒単位で周期タスクをスケジュールでき、一度も取りこぼさない
    Real-Time Linux が Linux 自体をプリエンプト可能にしようとするのに対し、Xenomai は本質的に独自のカーネルであり、その上のタスクとして Linux を実行する。ABI を提供して、ユーザー作成のタスクを Linux と並行して、あるいはそれより高い優先度で実行できる。たとえば printk() 問題を回避できるが、Xenomai はそれを気にせず、ユーザーのタスクを実行するために printk から喜んでコンテキストスイッチする
    欠点は、Xenomai コンテキスト内では通常のシステムコールを使えないこと。使うことはできるが、当然リアルタイムモデルを壊す。たとえば Xenomai タスク内で printf()malloc() を呼ぶとプリエンプト可能ではない。Xenomai ABI はシステムコールの面で必要になりそうなものをできるだけ複製しており、ヒープ割り当てを自前で処理することに満足できるなら非常によく動作する
    [1]: https://xenomai.org/