普段私たちが利用している Ubuntu などの Linux ディストリビューションは、インストーラーやパッケージマネージャーによって構築、管理が自動化されています。ユーザーが複雑な設定を意識する必要はなくなりますが、その反面、構築、管理として具体的に何が行われているかが見えにくくなっています。
本記事では、Linux From Scratch (LFS) の手順に従い、与えられたソースコードのみから Linux システムを構築します。日本語訳も用意されていますが、それを読んでなお分かりづらい部分があったので、詰まった部分やややこしい部分などを共有しながら、LFS を解説したいと思います。
LFS は、既存ディストロに頼らず、上流のソースコードから Linux システム一式を自分で組み上げてみるためのドキュメントです。本記事はその全手順をなぞるものではなく、特に以下のポイントにフォーカスした補助的な解説になっています。
やること
grub.cfg でハマりやすい点を紹介します。やらないこと
./configure && make && make install の具体的なオプションや、パーティションレイアウトなどの細かい手順は、本家ドキュメントに任せます。https://www.linuxfromscratch.org
Linux From Scratch (LFS) is a project that provides you with step-by-step instructions for building your own custom Linux system, entirely from source code.
LFS (Linux From Scratch) は、特定のディストリビューションに依存せず、上流のソースコードから直接 Linux システムを構築するための手順を示したドキュメントです。
ドキュメントに沿って作業を進めると、コンパイラ、ライブラリ、カーネルなど、システムを構成するツールをすべて自分自身の手でコンパイルし、インストールしていくことになります。
普段apt やpacman のようなパッケージマネージャを使ってツールをインストールするのとは全く異なり、依存関係の解決も含めてすべて手動で行います。これにより、Linux システムがどのようにして成り立っているかについて生きた知識を得ることができます。
派生として、グラフィック環境まで用意する BLFS(Beyond LFS) などもあります。
自分は試していませんが、ホストのブートローダーの設定を汚さずに qemu で行う方法もあるようなので、ホストのブートローダーは触りたくないが予備のディスクを持っていない人などは、こちらを参考にすると良いかもしれません。
https://zenn.dev/arimax/articles/37e783f3be53a0
まず、ドキュメントを見つけるのが少し難しいです。Linux From Scratch で検索するとホームページが見つかりますが、Home, Wiki, Website Mirrors などには最新のドキュメントへのリンクは見つかりません。一番わかりやすいのは、News にあるリンクを踏むことです。
本当にありがたいことに LFS には日本語訳があります。手順や操作は基本的にこちらを参照すれば全く問題ありません。おそらく日本語情報を用意することに重きをおいているため、直訳っぽい言葉になっていて意図が不明確な箇所が一部存在します。そのようなときは原文の英語を確認しに行きましょう。
それでは、ドキュメントを見ながら LFS をやってみましょう。
現在の最新版である LFS 12.4 に沿って説明します。sysvinit 版(systemd 版ではない方)を前提に話をします(クロスツールチェーン周りの流れはどちらもほぼ同じです)。本記事の構成は基本的に本家の構成と対応しています。
https://lfsbookja.github.io/lfsbookja-doc/12.4-ja/prologue/organization.html
LFS のブック全体は大きく見ると、
という流れになっています。本記事では、特に 2. と 3. のあたりを中心に補足していきます。
まず、普段利用しているディストリビューション上に作業用ディレクトリやパーティションを用意し、必要なソースコード一式を展開します。ここで使っている既存の環境が、LFS でいうところの「ホストシステム」に相当します。
次に、ホストに依存しない LFS システムを作るために、一時的なクロスツールチェーンを構築します。アーキテクチャ自体は同じであっても、ホストとは別の環境向けにビルドしているとみなすことで、最終的にホストから独立したシステムを得る、というのが LFS の重要な考え方です。この段階では binutils や glibc, GCC などのツールを、機能を一部制限しながら用意していきます。
そのクロスツールチェーンを使って最低限必要なツール群をビルドし、chroot でこれから起動する LFS システムの中に入ったつもりになって作業を続けます。ここで binutils や GCC をビルドし直すことで、ビルドに必要なツールチェーンがホスト側に依存しなくなり、LFS システムの内側だけで完結するようになります。この境界が分かりづらいので、本記事ではビルド/ホスト/ ターゲットを整理しながら説明します。
その後は、bash やcoreutils などの基本的なツールや各種ライブラリを順にビルドしていき、LFS システム上でテストを実行して、環境が正しく構築できているかを確認します。ここまでくると、普段ディストリビューションが裏側でやってくれているベースシステムの構築を自分の手で一通りなぞったことになります。
最後に、各種設定ファイルを整え、カーネルとブートローダをインストールすれば、ディスクから起動できる Linux システムが完成します。本家 LFS のブックは BIOS ブートを前提としていますが、UEFI での起動を行いたい場合は BLFS の該当章も合わせて参照する必要があります。このあたりの具体的な注意点については、記事の後半で触れます。
LFS ブックを読み、書いてあるとおりに操作をすれば、作業自体は間違いなく進められますが、なぜその操作を行うのか?本当に必要な操作なのか?などの本質的な理解を得ることができません。
この章では、そこに焦点を当てて説明を行います。
手順や操作自体は絶対に LFS ハンドブックの記述の通りに行うようにしてください。本記事ではパーティションの設定などの説明は省いていますし、ここでの説明はあくまでも理解を助けるためのものです。
https://lfsbookja.github.io/lfsbookja-doc/12.4-ja/partintro/toolchaintechnotes.html
まずは、ここから説明の際に用いる用語や記法などについて定義しておきます。
ビルド、ホストはプログラム全般に対して定義される用語です。
ターゲットは、コンパイラのみに対して定義される用語とされていますが、本記事では説明を簡単にするため、リンカやアセンブラのようなバイナリを扱うツールに対しても定義されるとします。
つまり、プログラムの視点から
という状況をコンパイラの視点から見ると、
となります。これはネイティブコンパイラとクロスコンパイラの定義でもありますね。
実際にはx86_64-pc-linux-gnu のようなトリプレットという文字列で ビルド/ホスト/ターゲットが表現されます。LFS では$LFS_TGT としてx86_64-lfs-linux-gnu のような値を使い、これがターゲットを表します。
また、これからの説明には以下のような表記を用います。
pc と表記します。lfs と表記します。A がツールB に依存していることをA → B のように表記します。(e.g.,glibc → gcc)A のホストがマシンB であることをA(B) と表記します。(e.g.,glibc(pc))C のホストがhost で、ターゲットがtarget のとき、C(host→target) のように表記します。(e.g.,gcc(pc→lfs))| 記法 | 意味 |
|---|---|
glibc(pc) | pc 上で動く glibc |
gcc(pc→lfs) | pc で動いてlfs 向けバイナリを吐く gcc |
bash(pc) → glibc(pc) | pc 上で動く bash はpc 上の glibc に依存する |
https://lfsbookja.github.io/lfsbookja-doc/12.4-ja/partintro/toolchaintechnotes.html
この節のすべてをすぐに理解する必要はありません。 この先、実際の作業を行っていけば、いろいろな情報が明らかになってくるはずです。
とありますが、ここで理解しないとただコマンドを叩き続けるだけの作業になってしまいます。
LFS の説明を軽く読むだけでは理解するのは難しいと思います。特に、ドキュメントの説明が今ホスト環境について言及しているのかターゲット環境について言及しているのかが分かりづらく迷子になりやすいと思いました。ここではそれをより明確にして説明をします。
ここで必要なツールチェーンは C/C++ で書かれているものがほとんどなので、以下の議論は C/C++ に限っています。
ここでは区別のため、大文字のGCC をパッケージ、gcc をコマンド名として書き分けます。
クロスツールチェーンとは、クロスコンパイルをするために必要なツール群のことを指します。具体的には、リンカ、標準ライブラリ、コンパイラなどです。
まず最初に、なぜクロスツールチェーンを作る必要があるのかを理解するべきです。個人で LFS を行う場合、LFS を行う環境と使う環境でアーキテクチャが異なることはほぼないと思われます。ではクロスツールチェーンは必要ないのでしょうか?
そんなことはありません。LFS の最終的な目標を思い出すと、今動いている環境に依存しない、ゼロから作られた独立した環境を作ることです。つまり LFS では、実際にはアーキテクチャとしてホストとターゲットが一致していても、一致していないと思い込みます。そして"見せかけ"のクロスコンパイルを行います。そうしないとホストの環境に依存しているということになってしまうからです。
この章の目的を具体的なツール名を用いて一言で表すと、binutils(pc→lfs),glibc(lfs),GCC(pc→lfs)(gcc(pc→lfs),libgcc(lfs),libstdc++(lfs)) を用意することです。
これらのツールのビルド時、実行時の依存関係を考えます。
前提としてbinutils(pc→pc),glibc(pc),gcc(pc→pc),libgcc(pc),libstdc++(pc) は存在するものとします。
from scratch と言いつつ強い仮定だと思ったかもしれませんが、上流のソースコードをコンパイルする必要はあるので、必要な仮定です。
用意するbinutils(pc→lfs) について、リンカld(pc→lfs) などが含まれますが、このようなツールはglibc(lfs) に依存しないため、そこまで難しい話ではありません。gcc(pc→pc) でビルドする際に適切なオプションをつけて、ライブラリなどの探索パスをlfs 上のパスに設定し、lfs 向けのコードを生成するように設定するだけで良いです。
--with-sysroot=$LFS: ライブラリの探索などを$LFS などから行うようにします。--target=$LFS_TGT: ld などが$LFS_TGT 向けのコードを生成するようにします。残りのパッケージは単純に考えると依存関係が循環してしまいます。glibc(lfs) はgcc(pc→lfs) によってビルドします。gcc(pc→lfs) を完全に機能させるにはlibgcc(lfs),libstdc++(lfs) が必要です。しかしどちらも通常glibc(lfs) の機能に依存しており、libstdc++(lfs) はlibgcc(lfs) にも依存しています。
また、そもそも実行時の動的リンクや共有ライブラリという仕組み自体、glibc があるから成り立つものです。
つまり、gcc(pc→lfs) のlfs 上の依存関係は以下のようになっています。
これでは依存関係が循環してしまいクロスツールチェーンを用意できません。
しかし実は、gcc(pc→pc) によってGCC(pc→lfs) をビルドするときに特定のオプションをつけると、gcc(pc→lfs) とlibgcc(lfs) の機能を制限する代わりにこの依存関係の循環を解消することができます。
--with-newlib: libgcc の glibc 依存部分を使用しない--disable-shared: libgcc を gcc に静的リンクする--disable-libstdcxx: そもそも libstdc++ を作らないこれらのオプションによって、上の依存関係は以下のように変化します。
これで依存関係が循環することなく、機能は不完全ですがgcc(pc→lfs),libgcc(lfs) を用意することができます。具体的にはスレッドや例外処理などの機能が欠けています。コンパイラが動くようになれば、あとは Linux API headers と呼ばれるカーネルの C 言語 API を示すヘッダファイルを配置すればglibc(lfs) はビルドすることができます。意外にも、gcc(pc→lfs),libgcc(lfs) の機能が不完全でもglibc(lfs) の機能は不完全とはなりません。glibc 自体のビルドは、スレッドや例外などの一部の高度な機能がなくても通るように設計されているためです。あとは GCC パッケージからlibstdc++(lfs) を取り出し、gcc(pc→lfs),libgcc(lfs) とは独立にビルドすれば良いです。こちらは機能制限の影響を受けます。
これらの手順を踏むことで、クロスツールチェーンをすべて用意することができます。
ただし、binutils(pc→lfs),gcc(pc→lfs) はpc に依存しているため、また、libgcc(lfs),libstdc++(lfs) は不完全であるため、これらは最終的な LFS 環境には含めません。これを解消するのが次のステップです。
https://lfsbookja.github.io/lfsbookja-doc/12.4-ja/chapter06/introduction.html
クロスツールチェーンを用意した目的は、pc から独立した環境を作ること、つまり、lfs がホストのバイナリで環境を作り、それだけで完結することです。現状では、binutils(pc),gcc(pc→lfs) がpc に依存しています。
この章の目的は、binutils(lfs→lfs) を作ったうえで、不完全なgcc(pc→lfs),libgcc(lfs),libstdc++(lfs) から完全なgcc(lfs→lfs),libgcc(lfs),libstdc++(lfs) を用意することです。
Linux From Scratch の説明を順番に読むと、gcc(lfs→lfs) の依存関係であるM4(lfs) や、次の chroot 環境を作るために必要なパッケージbash(lfs) 等を先に準備していますが、それら個別の説明は読めばわかることなので本記事では説明しません。
重要なのは、2回目の binutils, GCC のビルドです。と言っても先程ほど複雑ではありません。
既に完全なglibc(lfs) が用意されているため、共有ライブラリを作成、使用することができます。gcc(pc→lfs) でビルドすることで、共有ライブラリも含めてbinutils(lfs→lfs) を作ることができます。
これらはこれから chroot する最終的な LFS 環境に含まれます。
既に完全なglibc(lfs) が用意されているため、gcc(pc→lfs) によるGCC(lfs→lfs) のコンパイル時に完全なlibgcc(lfs) を作成することができます。libstdc++(lfs) は新しく作られた完全なlibgcc(lfs) を使用するように設定することで完全な状態になります。これらのライブラリが完全な状態になれば、完全なgcc(lfs→lfs) ができます。
LDFLAGS_FOR_TARGET=-L$PWD/$LFS_TGT/libgcc: libstdc++ が今回ビルドされた新しい libgcc を使うようにする以上の処理を行うことで、ビルドに必要な全てのツールチェーンからpc への依存がなくなり、完全に隔離された chroot 環境に入る準備が整います。
https://lfsbookja.github.io/lfsbookja-doc/12.4-ja/chapter08/introduction.html
ここまで丁寧にやってようやく純粋に自身のシステムで依存が完結する環境が構築できます。パッケージマネージャーはないため、あとは必要なパッケージを依存関係の順番通りに一つ一つ真心を込めて構築するのみです。
LFS ブックを見ると、binutils(lfs→lfs),glibc(lfs),GCC(lfs→lfs) など、既にあるものを再コンパイルしていることに疑問を抱くかもしれません。これには主に2つの理由があります。
1つは、既にあるこれらのツールはクロスコンパイルによって作られたものなので、システムの安定化のためにネイティブコンパイラでコンパイルし直すという理由です。
もう1つは、lfs 環境が正しく構築できているかについて、ここまでは簡単なチェックしかなかったものを、ようやく全体的なテストができるから、そのテストを行うためにビルドし直すという理由です。ここで初めてミスが発覚したらかなり絶望ですが、やらないわけにはいかないですね…
ここまで順調に行けば、あとはシステム設定をするのみです。ここは本家にも十分な説明があるため、本記事では割愛します。
https://lfsbookja.github.io/lfsbookja-doc/12.4-ja/chapter10/grub.html
LFS では BIOS ブートのみ説明されていて、UEFI ブートの手順は BLFS に委ねられているので、BLFS 側の対応するページを見に行かなければいけません。
基本的には BLFS 側のみを参照していれば良いです。grub.cfg の記法に関しての説明は LFS 側でなされているので、適宜参照すると良いです。
ここで一応頭の片隅に入れておいてほしいのは、LFS と BLFS で使用している Linux のバージョンが一致しているとは限らないということです。今回説明の基準にしている 12.4 では問題ないと思いますが、自分が LFS をやったバージョン 12.3 では、LFS はLinux-6.13.4 で BLFS はLinux-6.13.2 でした。grub.cfg にこれをハードコードする箇所があるので、BLFS のみを参考にしてgrub.cfg を書くとブートできずに困った記憶があります。
また、LFS にも注意書きがありますが、/boot を別パーティションで作る場合は、grub.cfg のmenuentry 内に記述するパスが/boot 基準になることにも注意しましょう。
ここまでの手順をすべて乗り越えて最後に再起動するときが、一番手に汗握る瞬間ですね...。無事起動できれば、LFS は完了です。恒例の neofetch(fastfetch) がすぐにできないのはちょっともどかしいですが、今までと同様にインストールすれば良いだけですので、ぜひやってみましょう!(自分はやっていません)
再起動した結果残念ながら起動しなくても、最終システムの構築時に行ったテストがだいたいうまくいっていれば、原因はそこではなくブートプロセスにあるでしょう。落ち着いて Linux や grub の設定を見直しましょう。
本記事では、LFS の流れに沿いながら、ビルド/ホスト/ターゲットの整理、見せかけのクロスコンパイルを行う理由、クロスツールチェーンの依存関係、二回目の binutils/GCC のビルドの意味、そして最後の UEFI ブートまわりの注意点までをまとめて説明しました。特に、どのツールがpc 側に依存していて、どこからlfs 側だけで完結するようになるのかを意識できるように書いたつもりです。
実際にやってみたときも、一番迷ったのはクロスツールチェーンの章と、ブートローダ設定の細かい部分でした。ホストとターゲットのどちらの話をしているのか、今作っているバイナリがどこで動くのかが見えなくなると一気にわからなくなるので、そのあたりを補うことを意識しています。
本記事の内容が、LFS ブックを読み進めるときの補助として、単に手順をなぞるだけでなく、なぜそのコマンドを実行しているのかを考えるきっかけになれば幸いです。
バッジを受け取った著者にはZennから現金やAmazonギフトカードが還元されます。
