特集記事
Last updated 2002-07-29

インターフェース 2002年7月号 特集記事サポートページ

特集「Linux徹底詳解 ― ブート&ルートファイルシステム
FDブートできるほど小さなLinuxシステムを構築する

プロローグ なぜLinuxは難しいのか?
 第一章  標準Cライブラリを使わないプログラミング
 第二章  BIOS版「Hello,world!」プログラムの作成
 第三章  Linuxオリジナルブートローダの作成
 第四章  オリジナルルートファイルシステムの構築
 追補   x86エミュレータBochsの使い方

記事中で使用したソースツリー(3.2MB) とその解説
記事中で作成したフロッピーディスクイメージ(3.3MB) とその解説
読者の皆様からの質問&訂正コーナー

私がインターフェースに初めて出会ったのは、今から20年近く前でしょうか。当時のインターフェース誌は黒い表紙を身にまとい、私にとっては憧れの雑誌でした。今でも当時の雑誌を大事に保存していますが、今回の特集記事もまた末永く読者の方々に読んで頂けることを願いながら執筆しました。

本特集のテーマは「Linux システムの構築」です。巷には数え切れないほどの Linux ディストリビューションが溢れていますが、いずれも数百MBを超えるような巨大システムであり、個人でその全貌を把握することは不可能に近い状況です。デスクトップ環境として利用する場合にはこれでも良いのですが、開発現場で標的機器に組み込もうとすると途端に破綻を来します。RAMや二次記憶装置の容量が限られた環境では、システム全体のフットプリントを数MB前後にまで抑える必要があるからです。

よって数百MBのシステムから無駄な贅肉を削り取り、真に必要とされるコア部分だけを取り出す技術が必要とされています。それでは PC-UNIX の心臓部は一体どこに隠されているのでしょうか?本特集では従来触れられることのなかったこの問題に取り組み、私なりの解答を用意してみました。当初予定していた内容の半分近くしか記述できませんでしたが、「自分の頭でシステムを理解し、自分の手で作り上げる」という目的は、ある程度達成することが出来たのではないかと思います。

なお、ページ数の関係から UNIX における実行可能ファイル形式ELF(Executable and Linking Format)について解説することが出来ませんでした。プログラムの本質を理解するためには、システムコールだけでなく、実行可能ファイルの技術背景についても把握しておく必要があります。これらについては、UNIX USER で連載中の GCC プログラミング工房で詳細に解説していますので、本特集と併せて読んで頂くとより理解が深まるかと思います。

最後に、企画の段階から入稿・校正に至るまで、辛抱強くサポートしてくださったインターフェース編集部 村上真紀氏にこの場を借りてお礼申し上げます。

西 田 亙 (NISHIDA Wataru)

プロローグ:なぜLinuxは難しいのか? p52-54

まず最初に、本特集の目的と全体像をまとめました。Linux に限らず、PC-UNIX の学習が難しい背景には次のような問題があるようです。

  1. システムコールが標準ライブラリーにより隠蔽化されることで、プログラムの本質が見えにくくなっている。
  2. GNU開発ツールの運用方法に関する実践的な解説書が存在しない。
  3. カーネル起動部分があまりに複雑であり、いまだにブラックボックスとなっている。
  4. カーネルばかりが注目され、UNIX システムを支えるルートファイルシステムの重要性が見落とされている。

本特集では具体的なコードの実例を示しながら、これらの問題ひとつひとつを自力で解決していきます。

訂正一覧

  1. p53 図1と図2の内容が入れ替わっています
  2. p54 コラム左下から2行目 CPU は現在数十種 -> CPU は現在十種類

リファレンスリスト

GCC ホームページ GNU Compiler Collection
GCC スナップショット GCC 最新版のスナップショット集
binutils ホームページ GNU binary utilities

第一章:標準Cライブラリを使わないプログラミング p55-65

hello.c を通して、普段あまり意識することのない共有ライブラリとダイナミックリンカー・ローダーの存在を明らかにしていきます。「gcc コマンドの裏側では何が起きているのか」、「プログラムはどのような工程を通じて出来上がるのか」、「ライブラリーに頼るという事は一体何を意味しているのか」、これらの謎を自分の頭と目で解いていきます。

標準Cライブラリーの限界が分かったところで、いよいよシステムコールの登場です。GCCの優れた機能である「拡張アセンブラ」を用いて、Cプログラム中に直接システムコール呼び出しルーチンを埋め込みます。最後に -nostdlib オプションを用いて、crt ファイルやライブラリーに全く依存しない "Hello, world!" プログラムを完成させます。

最終的なプログラムサイズは本特集では 1012 バイトになっていますが、実はここにもまだ「贅肉」が残っています。この贅肉はELFの構造に由来していますが、GNU開発ツールを使いこなせば、さらにこの無駄も削除することが可能です。具体的な方法については GCC プログラミング工房 第1〜3回を参照してください(驚くなかれ、最小80バイトまで削り込み可能です!)。

訂正 なし

リファレンスリスト

gcc 3.x info マニュアル (PDF 2.8MB)

"Extensions to the C Language Family" の章に拡張アセンブラ、後述の __attribute__ 記述子を始めとする、GCC 固有の機能が網羅されている

as info マニュアル (PDF 1MB) GNU assembler
binutils info マニュアル (PDF 312KB) GNU binary utilities
ld info マニュアル (PDF 500KB)

GNU linker loader
リンカースクリプトに関する唯一の解説資料

第二章:BIOS版「Hello,world!」プログラムの作成 p66-77

GNU開発ツールを用いてリアルモードプログラミングが可能であることは、不思議なことにあまり知られていません。GNU assembler "as" には GCC が出力したコードを16ビットセグメント内で32ビットアドレッシング可能にする .code16gcc という特殊ディレクティブが用意されています(下記リファレンス参照)。このディレクティブの意味を理解するすためには、x86 CPU におけるアドレス/データサイズプレフィックスを把握しておく必要があるのですが、今回その説明は割愛となってしまいました。またの機会にご紹介したいと思います。

まず最初に Linux カーネルローダーまで考慮したアドレス・ワークエリア空間をデザインします。コーディングについては、crt プログラム(crt.S)のみをアセンブリソースで作成し、残りの部分は全てCソースで記述する方針を取りました。Linux を始め PC-UNIX の起動部分はアセンブリソースがかなりの部分を占めていますが、.code16gcc 機能と拡張アセンブラ機能を最大限に活用すれば、そのほとんどはCで記述可能なはずです。第二章では古典的な BIOS コールを用いて、「BIOS 版 Hello, world! ブートディスク」を作成します。添付したソースツリーを用いて、是非ともご自分の手でブートディスクを作成し、PCを起動してみてください。デスクトップ上のアプリケーション・プログラミングとは、また違った面白さを実感して頂けると思います。

また記事中では、様々な GCC コンパイルオプションを組み合わせることで、一バイトの無駄もないアセンブリコードへ最適化していくノウハウを示しました。-fomit-frame-pointer は Linux カーネルでも採用されている有名なオプションですが、-fcall-used-ebx は今回が初めての紹介ではないでしょうか。これら以外にも、GCC には豊富なコンパイルオプションが用意されており、経験を積んだプログラマーであれば縦横無尽に出力コードをコントロールすることが可能になっています。GCC ならではの芸当と言えるでしょう。

最後に「第二のプログラム」、リンカースクリプトの記述方法を紹介します。中でもメモリーコンフィギュレーションは、システムプログラミングで威力を発揮する機能ですので、この機会にしっかりマスターしましょう。

冒頭で簡単に紹介した Linux カーネル(vmlinux/bzImage)の詳細な内部構造とその作成技術については、GCC プログラミング工房にて第6回から解説中です。全4回を通して Makefile やリンカースクリプト中で使われているスーパーテクニックを紹介しながら、Linux カーネル誕生の舞台裏を明らかにします。

訂正一覧

  1. p66 表1 下から2行目 vmlinux -> vmlinuz
  2. p70 右カラム上から12行目 各9行目 -> 9行目
  3. p71 左カラム上から6および8行目 15 -> 14
  4. p73 左カラム下から3行目 .bbs -> .bss
  5. p74 リスト11 13行目 { *(.rodata) } -> { *(.rodata*) }
  6. p76 右カラム下から9行目 .head -> .sign

リファレンス

.code16gcc の説明を as.info マニュアルから抜粋しておきます(Node: i386-16bit)。

Writing 16-bit Code
-------------------

While `as' normally writes only "pure" 32-bit i386 code or 64-bit x86-64 code depending on the default configuration, it also supports writing code to run in real mode or in 16-bit protected mode code segments. To do this, put a `.code16' or `.code16gcc' directive before the assembly language instructions to be run in 16-bit mode. You can switch `as' back to writing normal 32-bit code with the `.code32'
directive.

`.code16gcc' provides experimental support for generating 16-bit code from gcc, and differs from `.code16' in that `call', `ret', `enter', `leave', `push', `pop', `pusha', `popa', `pushf', and `popf' instructions default to 32-bit size. This is so that the stack pointer is manipulated in the same way over function calls, allowing access to function parameters at the same stack offsets as in 32-bit mode. `.code16gcc' also automatically adds address size prefixes where necessary to use the 32-bit addressing modes that gcc generates.

第三章:Linuxオリジナルブートローダの作成 p78-97

意を決して Linux カーネルのハッキングに取り組む人達は多いと思いますが、そのほとんどは起動部分の解析で挫折しまっているのではないでしょうか?実は、私自身もそうした苦い経験を持っています。これは、あまりのコードの汚さに途中で嫌気がさしてしまうこと、一般的には知られていないGNU開発ツールの高度なテクニックが随所で使われていること、などが原因です。

しかし、組み込みシステムのように開発者がブートコードに始まる全てを把握する必要がある場合には、「分からない」ではすまされません。何とか Linux カーネルの入り口に立ちはだかる障害物を取り除けないものだろうかと思案した結果、オリジナルのブートローダーを作り上げてしまおうという結論に達しました。

本章では、第二章までの知識を応用して段階を踏みながらオリジナル・Linux ブートローダーを構築します。このブートローダーは全部で3つのステージから構成されます。

stage1 はフロッピーディスクのブートセクターに格納される512バイトのプログラムであり、後続の stage2 のロード、および stage2 への起動パラメーターの伝達を担当します。Linux 起動パラメーターはブートセクターの後半に記録され、今回はカーネルコマンド行も併せて指定可能としました。

stage2 は続く stage3 および bzip2 圧縮カーネルイメージをメモリー上にロードする作業を担当します。圧縮カーネルイメージは BIOS を用いて、拡張メモリー領域に転送されます。

stage3 では、.bss セクションのゼロクリア、システムの初期化および搭載メモリー量などの取得、システムパラメーター領域の設定を行い、プロテクトモードに移行します。また stage3 はbunzip2 の機能を搭載しており、プロテクトモード移行後に拡張メモリー上の圧縮カーネルイメージを物理アドレス 0x100000 以降に展開します。 stage3 中には16ビットおよび32ビットのコードが混在している点に注意してください。この離れ業を可能にしているのが、stage3.c 先頭で定義しているセグメントディスクリプター 020 および 0x28 です。両者のベースアドレスが、リアルモードセグメントの開始アドレス(0x00007C00)になっているところがポイントです。このあたりは大変重要な部分なのですが、残念ながら今回はスペースの都合で説明できませんでした。x86 のアドレス管理機構については、具体的なコード例を交えながら別稿で解説する予定です。

今回使用したソースツリーはコンパクトですので、個人でも十分全体像を把握できると思います。ひとつひとつのコードの意味を吟味しながらブートローダーの動きを追っていけば、Linux 起動シーケンスは必ず制覇できますし、その過程を通してGNU開発ツールの使いこなし方を学ぶこともできるでしょう。

訂正一覧

  1. p80 リスト4 22行目 { *(.rodata) } -> { *(.rodata*) }
  2. p81 リスト7 16行目 { *(.rodata) } -> { *(.rodata*) }
  3. p81 左カラム 下から14行目 また、stage2 をアセンブリソースで記述した場合 -> もしも stage2 をアセンブリソースで記述し、
  4. p83 リスト10 37行および38行 setup.ls -> stage2.ls
  5. p83 左カラム 上から5行目 図1の実行前には「 ./config pass-param.bin "mem=4M" 1 0 256 2 3 4 」を行っています。
  6. p85 リスト12 56行 TRACK_BUFFER -> (void*) TRACK_BUFFER
  7. p90 リスト24 16行目 { *(.rodata) } -> { *(.rodata*) }

リファレンスリスト

bzip2/libbzip2 ホームページ

第四章:オリジナルルートファイルシステムの構築 p98-112

本特集の目玉である、ルートファイルシステムの構築方法について解説します。Linux ではフロッピーディスクを用いたブート・ルートディスクの実装技術が発達しており、たった一枚のフロッピーへ簡単に起動カーネルとルートファイルシステムを組み込むことができます。

まず最初にカーネルサイズを極限まで絞り込むことから始めましょう。今回ネットワーク関連のコンパイルオプションは敢えて除外しています(リスト1)。「本質」さえ掴んでしまえば、枝葉を付けていく作業はとても簡単なことだからです。

次に、最小限のデバイスノードを持ったルートファイルシステムを用意します。具体的な項目数はなんと11個!信じられないほど簡素なシステムですが、これでも立派な UNIX ファイルシステムです。ブートディスクおよびルートディスクの2枚を用意してPCを起動すると、立派に hello プロセスが動き始めます。

2枚のフロッピーディスクを用意するのは面倒なので、一枚のブート・ルートディスクにまとめてしまいましょう。このためには、ブートセクター内の RAMDISK 変数を操作し、dd コマンドでカーネルイメージと圧縮ファイルシステムイメージを書き込みます。ファイルシステム・サイズは全体で4MBですので、色々なツールやコマンドを格納するには十分な余裕があります。

ここまでは静的リンク形式のプログラムばかりを扱ってきましたが、現場では共有ライブラリーを用いる必要があります。そこで bash と ls コマンドのふたつと、これに関連した共有ライブラリー、およびダイナミックリンカー・ローダーをルートファイルシステム中に組み込みます。ldconfig コマンドの使い方に注目してください。

見事シェル環境が起動すれば、さらにエディターやシステムツールを整備します。最後に、起動スクリプトで proc FS もマウントして、マイ・オリジナル Linux の出来上がりです!

訂正一覧

  1. p98 右カラム 上から8行目 すべて"y"になっている点に注目 -> 削除
  2. p100 左カラム 下から3行目 両者に -> 両者を
  3. p100 右カラム 上から13行目 最初の -> 最初に
  4. p104 右カラム 上から8行目 もっとも簡単な・・押す方法です。 -> 削除
  5. p108 右カラム 上から16行目 デバイスノート -> デバイスノード

リファレンスリスト

The Linux Bootdisk HOWTO Tom Fawcett 氏による、Linux ブートディスクに関する優れた解説書。本書は数ある HOWTO の中でも最高傑作だと思います。
e3 editor Albrecht Kleine 氏による超小型エディター。全てアセンブラーで記述されているため、サイズは10KB前後。組み込み用途には最適。

追補:x86エミュレータBochsの使い方 p114-120

本編中でも活躍したフリーの x86 PC エミュレーター Bochs を紹介しています。Bochs は VMware とはことなり、全てをソフトウエア上でエミュレートしていますので、動作が遅いという欠点はありますが、数多くのプラットホームでサポートされている、マルチCPUをサポートしている、x86 の動きを詳細に追うことができる、など数多くのメリットを有しています。今回のようにリアルモードからプロテクトモードへの移行を伴うようなプログラム開発では、大変な威力を発揮してくれます。

ソースパッケージ中には ROM-BIOS / VGA BIOS が用意されており、ROM-BIOS についてはソースリストまで添付されています。このため、自由自在に BIOS を改変することが可能であり、PC-UNIX 専用のオリジナル・ブートROMを開発することも夢ではありません。

Bochs の用途はシステムプログラムやアプリケーションのデバッグ・解析だけでなく、教育現場でよりその実力を発揮するのではないかと私は考えています。

訂正一覧

  1. p116 左カラム 下から1行目 プログラム開発をしていると Debian で良かった・・ -> プログラム開発をしていると Debian で良かったと思うことが、しばしばある。
  2. p119 左カラム 7.Bochs のデバッガコマンド: 本特集入稿後、Bochs デバッガコマンドに関する資料を発見しました。詳細はこちらをご参照ください。
  3. p120 表2 上から18行目 64バイト分 -> 16バイト分
  4. p120 表2 上から25行目 解析するすが -> 解析するが
  5. p120 表2 上から26行目 リアルモード -> 16ビットモード

リファレンスリスト

Bochs ホームページ


Your SysOp is Wataru Nishida, M.D., Ph.D.