- Linuxカーネルを自分でビルドし、最小限のユーザー空間を構成して「マイクロLinuxディストリビューション」を作る手順を段階的に解説
- オペレーティングシステムのカーネルの役割、Linuxディストリビューションの構成要素、そしてカーネルとユーザー空間の関係を基礎から扱う
- RISC-Vアーキテクチャ(QEMUの
riscv64 virtマシン)を例に使うが、x86など他のアーキテクチャにも同じ原理を適用可能
initプロセス、initramfs、そしてGoで書いた簡単なシェルを含む自分で実行できる最小Linux環境を構築
- 最後に
u-rootプロジェクトを使って実際に役立つマイクロディストリビューションを作る方法を紹介し、Linuxシステム全体の構造理解を助ける入門ガイドとして締めくくる
オペレーティングシステムのカーネルとは何か
- カーネルはハードウェア資源の管理とプログラム実行の制御を担う、オペレーティングシステムの中核要素
- 単一コア環境でも複数のプログラムが同時に動いているように見せるマルチタスク管理機能を提供
- カーネルは入出力デバイス制御を抽象化し、アプリケーションがハードウェアの詳細なアドレスやレジスタ値を直接扱わなくて済むようにする
- たとえばプログラムは単に「標準出力にメッセージを書け」と要求し、カーネルが実際のハードウェアとのやり取りを処理する
- ファイルシステムインターフェースを通じて高水準のデータアクセス手段を提供
- ファイルは単なるディスク上のデータではなく、カーネルと通信する論理インターフェースとして動作する
- カーネルはプロセス間の分離と通信モデルを提供し、各アプリケーションが独立して動作したり協調したりできるようにする
- Linuxカーネルはオープンソースで、さまざまなアーキテクチャで動作でき、世界で最も広く使われているカーネルの1つ
Linuxディストリビューションとは何か
- LinuxカーネルだけではユーザーはWebブラウザやGUIアプリを実行できず、カーネルの上に複数層のソフトウェア基盤が必要
- ネットワーク設定、IP割り当て、VPN管理などはカーネルではなく上位のユーザー空間プログラムが担当する
- したがってLinuxディストリビューションはカーネル + ユーザー空間インフラの組み合わせとして定義される
- ディストリビューションには、カーネルが提供する基本機能に加えて**パッケージ、ツール、設定、初期化プロセス(init)**などが含まれる
- ディストリビューションの複雑さはさまざまで、Arch Linuxのような最小構成からUbuntuのようなユーザーフレンドリーな構成まで存在する
カーネル外部のインフラ: ユーザー空間とinitプロセス
- カーネルがブートを終えると、最初に**PID 1のプロセスである
init**を実行する
initはその後のすべてのユーザー空間プロセスの祖先となり、システムのサービスやツールを順に起動する
initが実行する各種プロセスやツールの集合がLinuxディストリビューションの実質的な構成要素
- ディストリビューションが複雑になるほど不要な機能が積み重なり、**“bloated”**だと批判されることもある
- 逆に、カスタムのマイクロディストリビューションを作れば、最小限の機能だけを含む軽量システムを構築できる
RISC-V向けLinuxカーネルのビルド
x86環境でクロスコンパイル用ツールチェーンを使ってRISC-V向けカーネルをビルド
kernel.orgからlinux-6.5.2.tar.xzソースをダウンロードし、make ARCH=riscv CROSS_COMPILE=riscv64-linux-gnu- defconfigを実行
menuconfigでカーネル設定を視覚的に編集可能
make -j16で並列ビルドするとarch/riscv/boot/Imageが生成される
- QEMUで
qemu-system-riscv64 -machine virt -kernel arch/riscv/boot/Imageとしてブート
- ブートログではSBIレイヤーの検出、UART初期化、printk有効化などのメッセージを確認できる
最初の障害: ルートファイルシステムがない
- カーネルのブート中に
VFS: Unable to mount root fsエラーでカーネルパニックが発生
- 原因: ルートファイルシステム(
initramfs)が提供されていない
- ファイルシステムはディスクだけでなく**RAMベース(
initramfs)**でも構成できる
initramfsはcpioフォーマットでパッケージされ、QEMUでは-initrdオプションでロードできる
initramfsを構築して“Hello world”を実行
- 最小要件は
/initバイナリの存在
init.cを書いて静的リンク(-static)でビルド
cpio -o -H newc < file_list.txt > initramfs.cpioでパッケージ化
- QEMU実行時に“Hello world”を出力した後、
initが終了するため再びカーネルパニックが発生
- 解決策:
initが終了しないよう無限ループを追加
Goで書いた簡単なシェルを追加
initがforkとexeclを使って/little_shellを実行
little_shell.goはユーザー入力を受け取り、コマンドをそのままエコー出力する単純なシェル
GOOS=linux GOARCH=riscv64 go build little_shell.goでRISC-V向けにビルド
initとlittle_shellはどちらもUART経由で出力を共有
- 標準入出力はファイルハンドルとして管理され、
fork時に継承される
- 結果として“Hello from init”とシェル入力が交互に出力される基本的なLinux環境が完成
カーネルの役割の整理
- ハードウェア抽象化: ユーザープログラムはUARTやデバイスの詳細を知らなくても出力できる
- 高水準インターフェースの提供: ファイルシステムを通じて別のバイナリ(
little_shell)にアクセス
- プロセス分離:
initとシェルは独立したメモリ空間で実行される
- カーネルは複雑なハードウェアの上で安定して移植性の高い実行基盤を提供する
オペレーティングシステムの定義
- カーネルだけをオペレーティングシステムと見ることもあれば、ディストリビューション全体をオペレーティングシステムと見ることもある
- 重要なのは、カーネルとユーザー空間の役割の境界と相互作用の構造を理解すること
u-rootで実際に役立つマイクロディストリビューションを作る
- u-rootプロジェクトはGoベースのユーザー空間ツールセットを提供
u-rootにはLinuxカーネル上で動作するユーザー空間ブートローダーとシェル環境が含まれる
- インストール後、
GOOS=linux GOARCH=riscv64 u-rootコマンドでinitramfsを自動生成
/tmp/initramfs.linux_riscv64.cpioファイルをQEMUで実行できる
- ブート時には“Welcome to u-root!”バナーとともに基本シェルプロンプトが提供される
ls, pwd, echoなどの基本コマンドをサポートし、タブ補完機能も含む
ネットワーク接続の実習
- QEMUに
virtio-net-deviceとvirtio-rng-pciデバイスを追加
-device virtio-net-device,netdev=usernet -netdev user,id=usernetオプションを使用
u-rootのdhclientでDHCPによりIPを自動割り当て
- 例:
eth0に10.0.2.15/24が割り当てられる
wget http://google.comで外部ネットワークへのアクセスに成功し、index.htmlのダウンロードを確認
パッケージマネージャーとinitの重要性
- 一般的なディストリビューションはパッケージマネージャーを通じてソフトウェアを動的にインストール・更新する
- この実習は組み込み型のアプローチであり、イメージ全体を再ビルドする必要がある
initは単なるプロセス起動器ではなく、デバイス初期化、サービス管理、システムブート制御の中核要素
u-rootのinitソースコードを通じて、さまざまなデバイス(/dev)設定の過程を確認できる
GitHubリポジトリ
1件のコメント
Hacker Newsのコメント
数か月にわたって マイクロ Linux ディストリビューション を自作している
ユーザーモードは単一の静的バイナリ1つで構成されていて、confidential microVM コンテナをサポートするためのいくつかのファイルだけがある
特に initramfs の構造が興味深い
カーネルが cpio アーカイブを展開して tmpfs に入り、
/initを実行する過程はまるで魔法のようだ複数の cpio アーカイブを連結することもでき、それぞれ圧縮可能で、順番にオーバーレイされる
このシンプルでありながらエレガントな設計のおかげで、自分でアンパックコードを書きながら多くを学んだ
最近では qemu が主要アーキテクチャで uftrace をサポートし始めた
専門家たちが「これをどうやってデバッグするんだ?」と尋ねるとき、そのまま答えになる
関連内容は このスレッド を参照できる
自分も似たようなプロジェクトを進めている — azathos
自作の toy init、shell、そしていくつかのユーティリティを含んでいる
デバッグ用に GNU coreutils を入れていて、今はフレームバッファに ウィンドウを描画する機能 に集中している
このプロジェクトは本当に素晴らしい
98年にフロッピー ベースの「ディストリビューション」を作って、Windows PC のイメージングを UDP ブロードキャストでやっていた時代を思い出す
「make bzimage」、init スクリプトのエラー、無限リブート…思い出が多い
今のやり方もそれほど変わっていないのが興味深い
Raspberry Pi 向けに移植したら面白くて教育的だと思う
自分でも試してみるかもしれない
結局、友人が sftp の内容を CD に焼いてくれて解決したが、そのときは 2 倍速でしか書き込めなかった
これを クラウドイメージ として(例: Vultr, DigitalOcean)動かしたり、GUI を立ち上げて Firefox を動かしたりするのがどれくらい難しいのか気になる
カーネルの基本ドライバさえあればよく、イメージをインストールすればいい
別のディストリビューションで起動してから kexec で自分のカーネルを実行し、メモリ上でインストールする方法も可能だ
実装例としては nixos-anywhere を参考にできる
思ったより簡単な作業だ
このプロジェクトを Raspberry Pi 向けに作ったバージョンがあれば本当に興味深いと思う
なぜこういうものを自作するのか気になったが、ただ Gentoo で Linux を探究するだけではだめなのかとも思った
ユーザー空間のカスタマイズは可能だが、Linux 自体を学ぶにはあまり向いていない
stage3 tarball を見るだけでも、すでに「ミニディストリビューション」級だ
学習用としては本当に良く、素早く完成させたいなら buildroot が良い選択肢だ
この記事のおかげで本当に多くを学んだ
情報量の豊富なポスト に感謝する