LWN加入者のメリット LWN への 購読 |
2023 年 1 月 20 日
この記事は以前、ウィリー・タローによって寄稿されました
カーネル ベンチャーは、強力な特定の個人位置情報コードをホストしていません。リポジトリにありますが、例外があります。 そのうちの 1 つは、現在 instruments 内にレベルアップしています/ include/nolibc ディレクトリは、5.1 の配信以降、最高のレベルに達しています。 nolibc ベンチャーの目的は、わずかな低レベルのワークロード用に最小限の C ライブラリ エミュレーションを作成することです。 nolibc の概要、過去の歴史、および主要な貢献者によって書かれた将来の方向性について学びましょう。
nolibc 要因は、実を言うと、RCU 拷問の割り当てとして 5.0 カーネルに目立たないエントリを作成しました-スイートを見てください(“rcutorture”)、commit 66b6f755advert45 (“rcutorture: Import a copy nolibc」)。 これは、ポール・マッケニー が 尋ねた後に発生しました: “
例として、Linux カーネルを持ち、ユーザー空間がまったくない組み込み機器を組織する、カーネル最高の展開を機能させる人はいますか?
”
彼は続けた:
mkinitramfs は initrd の約 40MB で結果にアプローチし、約 10MB をドラカットします。 ここにあるほとんどのことは、ファイルシステムのマウント、デバイスのオープン、および mkinitramfs と dracut が可能にするほぼすべての注意を引くものに引き寄せられない rcutorture にはまったく無意味です。
私が条件を満たしていない可能性が高いことを知っている人々は、私が船外に出て、次の initrd をあなたが想像する準備ができているように見えるほど小さくしていることを知りました。 まず、エスケープ バイナリとスリープ バイナリによって完全に不要になったすべてのものを捨てることから始めました。これにより、合計で約 2.5 MB まで削減されました。そのうちの 1.8 MB は libc でした。
私は長い間同じ問題を解決するのが好きだったので、この説明は私にとって馴染み深いものでした。 実際の結果 (このレベルまで) は nolibc です。これは、マシンが 1 つの窮屈なプログラムを高速化するために最高の状態で起動されたときのイベント用の最小限の C ライブラリです。
25年間、私はシングルを構築するのが好きです-フロッピー – 主に主にベースの緊急ルーター、ファイアウォール、およびすべてのカーネルとルート ファイルシステムを含む Web サイト訪問者ミル。 私は後に信用格付けカードサイズのCDを常駐させ、時々、すべてのカーネルに窮屈なインタラクティブシェルを埋め込んで、ブートファクターからさらに良くすることに移りました。
これらのすべてには、 と呼ばれる比類のないわずかなプログラムがありました。 preinit の増加に責任がある /dev エントリ、マウント / proc、オプションで RAM ベースの主にベースのファイルシステムをマウントし、実行よりも早くいくつかの追加モジュールをロードします 初期化。 私のカーネルが比類のないほど好きなすべてのボディの属性は、モジュール内のすべてのボディが、カーネルの組み込みの予備的な ramfs の間に正しくパッケージ化され、ルートファイルシステム用に予備的な RAMdisk (initrd) を予約することです。 カーネルが起動すると、ルートおよびブート マウント機能をピボットして、事前にパッケージ化されたモジュールをアセンブルし、最終的なアサートで表示されるようにして、ルート ファイルシステムをカーネル モデル ライトの自己依存にします。 これは、フラッシュまたはネットワーク ブートから作業する場合に非常に便利です。ファイル システムに触れることなく、気にせずにカーネルを交換する準備ができているようです。
800KB (またはそれ以下) のカーネルに適合させる必要があったため、配信する preinit コードは最小限にする必要がありました。 アプローチの 1 つは、stdio または過剰なレベルの C ライブラリ機能を厳密に回避し、コードと情報を再利用することで構成されていました。 これにより、コードは静的にビルドされ、わずかな依存関係のおかげでわずかに (現在のフロッピー モデルでは 1KB 未満) ライブになります。
進化したので、Cライブラリの初期化コードが圧倒的に多い合計で以下のバイナリサイズだったのですが、 . 減量プログラム libc への切り替えが役立ちましたしかし、2010 年には、それはもはや進化しておらず、静寂にはいくつかの境界がありました。 私はそれを適度に大きいものに置き換えましたが、より興味深いものです — uClibc、スケールを低く維持するためのローカル決定に置き換えられた一連の機能 (たとえば、640 バイトが に保存されました)。 memmove() と strpcy())、2016 年までこれを軽量化します。
次に、uClibc はいくつかの厄介な境界をカモフラージュし始め、それまでに強化できなかったアーキテクチャ (aarch64 と同等) にコードを移植しようとすると苦悩に変わり、その維持費は低下していました。
klibc を使うことを想像し始めました)、これはカーネル開発者によって開発および維持されるという最もクールな点もありましたが、klibc は以前はよりスマートに好まれ、持ち運び可能で、私の欲求に近いものでしたが、静かに、より早く個別にビルドする必要があるという同じ苦痛がありました最終的な実行可能ファイルにリンクされるよりも。その後、preinit コードが C ライブラリに非常に微視的な依存関係を持っている場合、意味をアセンブルして、マシンが適切に呼び出すアウトラインを修正し、それを使用して実行します。 そのため、2017 年 1 月に、一部のマシン コール定義がマクロに移動されました。 C ライブラリにまったく期待せずにぶらぶらするこの新鮮なスキルにより、このベンチャーの自然な響きのタイトル「nolibc」がすぐに生まれました。
以前は、現在のコードをシームレスにハングアップできるようにすることに主眼が置かれていました。 、nolibcまたは比類のないCライブラリのいずれかを使用して、コード内のifdefの糸で処理するのは面倒です. これには、nolibc が肯定行に既に含まれていたときに「encompass」ブロックを回避するスキルが含まれます。また、更新のハング コースが必要になる可能性がある外部情報に依存しないようになりました。 このおかげで、最高のマクロと静的定義は軽いものであると私たちは決心していました。
迅速なテストの利点の 1 つは、nolibc.h コンパイラの確認行から適切になりました、および NOLIBC の定義に依存するこのファイルから、比類のない C ライブラリ ヘッダーとの距離を保つために、発明の賞賛:
#ifndef NOLIBC #encompass #encompass /.. . */ #endif これにより、nolibc を欠くアーキテクチャ上で比類のない C ライブラリを使用してプログラムを構築できるようになり、nolibc が利用可能になったときに簡単に置き換えることができるようになります。 それが現在、rcutorture がそれを利用している方法です。 カモフラージュを示すもう 1 つのレベルは、次のバイナリが nolibc と定期的に静的にリンクされているのに対して (この真実から、自己含まれており、共有ライブラリを別々に配置する必要はありません)、静的または動的リンクのいずれかを使用して、glibc よりも全体的に小さく静かです:
$ size init-glibc-static init-glibc init-nolibc text info bss dec hex filename 707520 22432 26848 756800 b8c40 init-glibc-static 16579 848 19200 36627 8f13 init-glibc 13398 0 23016 36414 8e3e init-nolibcこのバイナリが私のすべてのカーネルのレベルにあることを考えると、私の直後の焦点は、私が機械的に使用していた多くのアーキテクチャをサポートすることでした。使用: i386、x86_64、armv7、aarch64、および mips。 これは、プラットフォーム用の構造スクリーム セットアップ コードを提供することで解決されていました。 マシンコールについては、アーキテクチャ間で異なるように見えました (例: opt out() または poll() 一部のアーキテクチャにはバリアントが存在します)、さらに一部の構造が異なる場合があります。struct stat
stat() に渡される マシンコール。 このように、指定された ifdef は nolibc コードの間はすべて適切であり、一般的な呼び出しをエミュレートするために時折ラッパー ライトを使用して、使いやすさを差し控える、ということが私たちの心に決めていました。つらい errno
以前は の扱いだった閉店不安レベルエラー番号 。 最初の試行で、preinit ローダーは有害なマシン コールの戻り値を生成して、エラー リターン コードを昇格させます。 これは以前は便利でしたが、そうすると POSIX 準拠のプログラムの糸で移植性が損なわれ、世界中の errno 変数であり、最高のクエリ マシン呼び出しが再び来て、エラーの場合は -1 になります。 (私の考えでは、この通常の発明はすべてを複雑にする間違いであると思いますが、現在は交互に行われるわけではなく、適応する必要があります).
反動がerrno は、それが世界変数であると予想されることです。これは、nolibc とプログラムの残りの部分との間で何らかのコードをリンクする必要があることを意味します。 それはおそらく使いやすさの必要な割り当てを消費する可能性があるため、代替オフがつまずいたことがありました.nolibcを使用するプログラムの大多数は単一のファイルを包含するため、叫び声を修正しましょうerrno スタティック。 これは、nolibc を含む情報から見ても公平であり、技術的な光がなければコンパイラによって根絶されることさえあります。
このアプローチの反動は、プログラムが複数の情報から作成された場合、それらのすべてが特定のエージェントを覆すことです エラー番号 。 より強力なマルチファイル プログラムを作成する必要性は nolibc ではまれであり、プログラムは errno
の価格を当てにしています。 quiet from one more file は比類のないものではないため、これは適切な代替オフと見なされていました。 現在アセンブルされているプログラムについては、気にせず、実際に言うと、想像する準備ができているように見える最小のものに落ち着きます. errno (定義による
NOLIBC_IGNORE_ERRNO。 提案
上記の成分が基準を満たしていませんでした。 . 私は提案書
を送りました、それが生きることもできるものを展示: 睡眠のモデル
664 バイトのバイナリでプログラムします。 (それ以来、binutils のいくつかのバリエーションが “.show camouflage.gnu.property と呼ばれる割り当てを追加し始めたことを説明します。 “、プログラムが拡張機能を強化することについての知識を集めています。 re シャドウスタック
、コードを 4KB 離します)。
McKenney はこのアプローチに熱意を表明したので、私たちは彼の現在のプログラムを nolibc に移植することを共同で試みました。彼のテスト用の小さな写真 (これは特定のケースで特に注目を集めます。たとえば、TFTP を介してネットワークから起動しているときに感心します)。 この代替により、rcutorture は nolibc が構造に役立つかどうかを自動的に検出し、そのような状況でそれを使用します。そうでない場合は、比類のない静的ハングに再び陥ります。
私が見ていた閉鎖反動は、この日、私はタビータイムよりも大きいということですHAProxy と、受信トレイに届いたすべての linux-kernel メッセージをすばやく確認し始めると、特定のスケジュールで時間を言うことは非常に高度になりました。 私はベンチャーのボトルネックになることを求めていませんでしたが、それがカーネルにマージされていることも公平に落ち着いています。 マッケニーは、彼の -rcu ツリーの割り当てとしてそれをホストし、合併住居の窓を維持することを申し出ました。 それは私たち全員にとって莫大な量の賞賛に聞こえ、その準備作業は以前は迅速でした
提出済み を含めます。貢献と機能強化
ベンチャーが合併したという理由で、どうしたらよいか、いくつか議論があったようです。さまざまな雇用条件のためにそれを強化し、それをより適切に地域化する方法。 インゴ・モルナー の指導 RCU サブディレクトリから取り出して、さまざまなイニシアチブに使用できるように、より簡単に組み立てます。 コードは正式に instruments/ の下に移動されていました)。
RISC V の Toughen は Pranith Kumar によって 5.2 で導入されました。 S390 make strong は Sven Schnelle によって 6.2-rc の長さで投稿され、将来のモデルのためにマージされていました。 Ammar Faizi はいくつかの注目につまずいた- ABI要素とこれまでの矛盾
を掴む 5.17 で対処; ボリスラフ・ペトコフを掘らせたglibc と x86 psABI(プロセッサは ABI を叫ぶ — 基本的には、特定のプラットフォーム用に構築されたすべての本体プログラムが相互運用可能であることを決定する呼び出し会議です。トピック コンパイラおよびライブラリ ライト)。 この作業の後、psABI 仕様 以前は更新されていました 本当のことを言うと、glibc が行うことを仲介します。
このレベルでは、ベンチャーが離陸したとさえ言えます。 窮屈な動的回想アロケータ
高速化に十分です strdup() インスタンスとして肯定行の引数のコピーを保存します。 シグナル処理はさらに 現在検討中です。 複数の情報に分割中
単一ファイルのアプローチが最も便利でないことは明らかでした。寄稿者、そして比類のない ifdef-out をしなければならないことは、以前は微視的で面倒だった移植性を再び備えています。 1つの反動は、nolibc.hと一緒に作るという便利な意図を差し控えることでした コンパイラがそのレーティング カーネル ヘッダーを提供するときにソース ディレクトリから適切なファイルを取得し、インストール可能な構造スクリーム レイアウトを作成します。 この反動は、5.19 で nolibc.h ファイルは、すべての分類されたファイルを包含します。 さらに、NOLIBC を定義します。古いプログラムを使用して、頻繁にマシンのヘッダーをロードすることから少し離れた場所を維持します。
これで、ファイル レイアウトは次のようになります:
-+- nolibc.h (すべての分類されたものを含む) +- arch-$ARCH. h (ソースツリー内の最上位) +- arch.h (ラッパーまたは上記のいずれか) +- ctype.h +- errno.h +- signal.h +- std.h +- stdio.h +- stdlib. h +- string.h +- sys.h +- time.h +- kind.h +- unistd.harch.h ソース ツリー内のファイルは、ターゲット構造を評価し、対応するファイルを含みます。 セットアップ ツリー内では、これらの情報の 1 つに置き換えられるだけです。 ソースツリー内では、nolibc.h によって最高参照されます。 。 この新鮮なアプローチは、 のように) より簡単に処理できることが既に確認されていましたs390 パッチ は、いくつかの情報に触れました。 この快適なパッチ領域は、新しい構造をより強くする方法の例として、再び公平になる可能性があります。 基本的に必要なことは、会議コードを含む新しい arch-scream ヘッダー ファイルを追加し、arch.h ファイルを作成し、必要な重要な現在のマシン コールの 1 つを調整して、新しい構造の特性を保持する可能性があります。 次に、カーネルのセルフテストと rcutorture も公平に更新できます (以下のアンダーカバー エージェント)。 2つの操作モード
カーネルは、特定の人物の位置に必要なさまざまな定数と構造を定義して使用します。知覚する; これらには、マシン呼び出し番号 ioctl() が含まれます。 ) 数値、構造は賞賛 struct stat、およびすぐ。 これは、「特定の個人位置情報 API」の UAPI によって識別され、カーネル ヘッダーを通じて公開されます。 これらのヘッダーは、カーネルと賢く対話するために nolibc に必要です。
nolibc と UAPI ヘッダーを結合するには、文字通り 2 つの主要な手法があります:
セットアップを必要とせずに libc 対応のツールチェーンで動作する短い意図。 この場合、UAPI ヘッダーは asm/ の下でつまずいた および linux/ は、ツールチェーン(おそらくネイティブのもの)。 このモードは、"-encompass $TOPDIR/instruments/encompass/nolibc/ nolibc.h」.
と同等のネイキッド スチール ツールチェーンでスマートに楽しい快楽の意図 つまずいたものkernel.org で、ただし、UAPI ヘッダーへのエントリを取得するための設定が必要です。 それは「assembly headers_install」で行います。 これを nolibc ヘッダーと組み合わせることで、1 つの構造のネイキッド スチール コンパイラでスマートに使いやすく、単純なソース ファイルを動作する実行可能ファイルにビルドできる、わずかで可搬性のあるマシン ルートが得られます。 これは、ターゲット構造が識別されて固定されるため、カーネルの自己テストに最も適した動作モードです。 テスト
最新のアーキテクチャの追加により、多くのマシンコールが賢明に利用されていることも検証できるいくつかのセルフテストを増やす緊急性。 マシン コールの 1 つの目標は、成功の 1 つのケースに対して、失敗の原因を想像する準備ができているように見える合計倍数が存在することです。 errno 値。 それらすべてを調べるのは決して簡単なことではありませんが、安価なフレームワークが設計され、最新のテストが追加され、準備ができているように見えるほど強力です。想像する、負担のない。
テストはクラスによってラベル付けされます—「syscall」(マシンコール)および「stdlib」(従来のCライブラリ機能)は今のところ正しいです—そして、すべてのクラスで番号が付けられているだけです。 あなたの総努力には、最も長く確立された条件を検証するための十分なマクロを提供することが含まれます. コードはテストを行うのにそれほど必要ではありませんが、目的は達成され、それらの大部分は簡単に追加できます。
見てみるプログラムが実行されると、選択されたクラスのテストをループし、見てみるだけを出力します。番号、象徴的なタイトル、および実際に見てみる結果に応じて、それぞれの「OK」または「FAIL」。 たとえば、ここに chdir() の 2 つのテストがあります。 、一流を目指すものと失敗の詰め合わせ:
CASE_TEST(chdir_dot); EXPECT_SYSZR(1, chdir(".")); 骨折; CASE_TEST(chdir_blah); EXPECT_SYSER(1, chdir("/blah"), -1, ENOENT); 骨折; いくつかのテストは、また、特定の構成の代替提案が有効になっていない場合、またはプログラムが十分な権限で高速化されていない場合も、公平に失敗します。 これは、NOLIBC_TEST 大気変数。 ここでの目標は、単純に組み立てて、ブートローダーの確認行でテストを高速化することです。 出力には、確認ごとの OK/FAIL の結果と合計エラーの依存が含まれます。
instruments/testing/ の Makefile selftests/nolibc は、現在の構造体を nolibc に配置し、それを initramfs に配置して、カーネルを構築するとともに、テストの実行を処理します。それに対応し、QEMU の下でカーネルを起動する可能性があります。 すべての構造スクリーム設定は、変数を持つ領域です。 これらには、defconfig タイトル、カーネル イメージ タイトル、QEMU アファーム行などが含まれます。例として、カーネルを少し離れたマシンまたは TFTP サーバーにコピーする可能性のあるスクリプトからも公平性を保つことができます。
$ アセンブル - Cinstruments/testing/selftests/nolibc selftests/nolibc 以下のサポートされているターゲット: すべて以下の「speed」ターゲットをもう一度呼び出します これも sysroot ここで nolibc sysroot を取得します ($ARCH を使用します) nolibc-実行可能ファイルをハングアップします ( $CC と $CROSS_COMPILE を使用します) initramfs nolibc-で initramfs を準備します-defconfig を確認します 新しいデフォルト設定を取得します ($ARCH を使用します) カーネル initramfs でカーネルを (再) ハングします ($ARCH を使用します) ) 速度は、ビルド後に QEMU でカーネルを実行します ($ARCH、$TEST を使用します) 事前にビルド済みのカーネルを QEMU で再実行します ($ARCH、$TEST を使用します) 喜ばしい sysroot、initramfs、ハングおよび出力情報 ( ...))さらに、カーネルを構築せずに、ローカル マシンのバイナリまたは叫ぶ構造を修正することを想像する準備ができているように見えることも (新しいテストを増やすときに重要です)。
セルフテストの便利な要素の 1 つは、カーネルをハングアップする必要がある場合でも、公平に静かにすることです。必要なすべての代替提案をそのカーネルに渡すことに注意してください。 カーネル (ARCH, CROSS_COMPILE など ) つまり、構築中のカーネルと内部に離れて配置されている実行可能ファイルとの間の不一致の可能性を制限します。
主要なメイクファイルからテストを呼び出すことは決して簡単な意図ではない可能性があるため、一見あなたのようには見えません。比類のない設定を継承して、カーネル イメージのタイトル、defconfig を賞賛するか、別の提案をハングアップすることを想像する準備ができているでしょう。 これは、必要なすべての変数を構造体ごとに列挙することで解決されていましたが、これが最もメンテナンスしやすいようです。
nolibc のマシンコール実装のバグを発見することを期待して作成されたテストが配信されていました (そしていくつかはさらに、従来のライブラリ機能 (たとえば memcmp() および fd_set 実装が汚染されていた)。 マシン コールに影響を与えるカーネル リグレッションを検出するために、いつの日かそれらが重要になる可能性があることを想像する準備ができているようです。 テストはやや単純であり、さまざまなイニシアチブでより多くの合計テストがすでに存在しているため、明らかにこれには時期尚早です Linux プロジェクトを見てみましょう。 苦悩の可能性が高いのは、新しいマシン コールが並行して開発されていることと、マシン コールをデバッグするために軽量であることを確認することです。 ミュートアサート、境界、および将来の計画
現在の主張は、わずかなプログラムも公平であることができるということです。書かれ、トロットに基づいて構築され、軽量です。 コードが clone() マシン コールであり、スレッド ローカル ストレージまたはスレッド キャンセルに依存しません。 Impress Brown はなんとか実装しました arm64 での TPIDR2 のいくつかのテストでは、現在、適切に管理されているスレッドを使用しています。
多くのマシン コールと、ネットワーク指向のコールや些細なコールが賞賛されます getuid() は誰も必要としておらず、現在は使われておらず、string.h および
stdlib.h の機能はかなり窮屈ですが、ほとんどのカーネル開発者はおそらく必要に応じて不足しているものを実装する準備ができているので、正直なところ反動はありません。
データ ストレージの不足は、まず errno の場合、 )environ 変数、そして最後に 補助ベクトル (AUXV には、いくつかの設定が含まれており、ゲイン ページ サイズを賞賛します)。 それらはすべて predominant() からプログラムによって不安なく回復することもできますが、 、そのような境界により、新しい拡張機能の実装がより高度になり、これに対処するパッチ領域は以前は でした。 最近投稿されました。
マッケニーと私は、よりインタラクティブなツールについて話し合いました。一緒に梱包されます。 このツールには、窮屈なシェルが含まれている可能性があり、特定のシーケンスを見ていく準備ができている可能性があります。 反対に、それが常に低レベルの自己テストベンチャーの割り当てであるべきか、それとも代わりに、好奇心のレベルが公平で静かな生活をして、新しいテストをより簡単に取得できるかどうかは不明ですが、さまざまなスイートを見てみましょう。
ベンチャーのタンデムチーム
同一の「タンデム」グループが他に類を見ないかどうかはわかりませんが、今組み立てていますさまざまなサブシステムがありましたが、メンテナンス作業を McKenney と私の間で分担していた方法は、快適な雰囲気であることを認めたいと思います。 nolibc ベンチャーでの私の時間は、コントリビューション レポート、ワームの修正、機能強化に費やされています。McKenney が -rcu への組み込みに関連するすべてのことを気にかけているおかげで、カーネルへの組み込みよりも早く、以前ほど強力ではなくなりました。ツリー、会議時間の締め切り日、およびプル リクエストの送信。 コントリビューターの場合、コントリビューションを承認するよりも早く変更しなければならない場合、コントリビューションが印刷されてから将来のカーネルで表示されるまでの時間がわずかに長くなる可能性がありますが、もし私がそうしていたらもっと悪いでしょう。私の評価でそれを世話する。
テスト インフラストラクチャを適応させて記述するには、かなりの労力がかかりましたが、今ではほとんど比類のない維持管理が行われています。 それぞれの側面でのベンチャーによって生じる処理オーバーヘッドの量を制限することで、カーネル内で低トレースで使用できるようになっていることに感心しているように見えます。 マッケニー氏にとって、教唆は、彼の検査機器が切断を賞賛するための負担の軽減です。 私にとって、フィードバック、ワームの経験、修正、およびベンチャーの機能強化を取得することは、今では光よりも露出されていることを高く評価しています.
( ログイン
フィードバックを投稿する) 𝚆𝚊𝚝𝚌𝚑 𝙽𝙾𝚆 📺