Taketoshi Sano
, (kgh12351@nifty.ne.jp
)
起動時に実行される LILO のコードには、1st boot loader と 2nd boot loader の 2 種類があります。
これらのコードは通常、/boot/boot.b というファイルの中に保存されています。 /boot/boot.b の先頭 438bytes が 1st boot loader のコードであり、 先頭から 512bytes を除いた残りが 2nd boot loader のコードに相当します。
"lilo"
コマンドを実行するとコマンドラインオプションの
-b bootdev
や設定ファイル lilo.conf のboot=bootdev
によって指定された場所のブートセクターに 1st boot loader がインストール
されます。
このとき同時に "lilo"
コマンドのオプションや
設定ファイル lilo.conf によって指定された timeout、delay、prompt、
デフォルトの (カーネルの) 起動オプションなどのパラメータ、また 2nd boot
loader やマップ (カーネル位置情報) ファイル、それに (指定された場合には)
起動メッセージファイルやキーボード変換テーブルファイルのディスク上の位置を
示すセクターアドレスなどの情報が 1st boot loader の中のデータ領域に保存されます。
1st boot loader は 2nd boot loader をメモリーにロードするのが役目です。 LILO の起動コードの主要な機能はすべて 2nd boot loader の中にあります。 2nd boot loader は 1st boot loader の中に含まれている情報をもとに マップファイルをロードしてその情報を解析し、その結果に従って カーネルコードをロードします。また必要な場合には initrd で使用する ルートイメージのロードも 2nd boot loader が実行します。 起動プロンプトの表示や使用するカーネルの選択も 2nd boot loader の 機能です。
LILO によるブートでは、最初にブートセクターの先頭 438 bytes に記録された 1st boot loader が BIOS または MBR に置かれた他のブートローダーによって ロードされます。ロードに成功し、1st boot loader が実行を開始すると 最初の "L" という文字が表示されます。
次に 1st boot loader は "lilo"
コマンド実行時に
自分の中に記録された 2nd boot loader のディスク上の位置 (セクターアドレス)
を BIOS に提示して、セクター単位で BIOS に 2nd boot loader をロードさせます。
ロードに成功すると、コンソールに "I" という文字を表示して 2nd boot loader に
制御を渡します。
lilo のソースコードで 1st boot loader に相当するのは first.S です。 (なんてわかりやすい名前でしょう!) 上記の "L", "I", の表示を実行しているのは、 それぞれ
go:
; cli ! no interrupts
mov ds,ax ! AX is already set
;; mov es,ax ! (ES may be wrong when restarting)
mov ax,#STACKSEG
mov ss,ax
mov sp,#STACK ! set the stack
sti ! now it is safe
mov al,#0x0d ! gimme a CR ...
call display
mov al,#0x0a ! ... an LF ...
call display
mov al,#0x4c ! ... an 'L' ...
call display
と
done: mov al,#0x49 ! display an 'I'
call display
jmpi 0,SECONDSEG ! start the second stage loader
の部分です。コメントを読めば、何をしているか想像がつきますね。
もうちょっと詳しく 1st boot loader の動作を説明するために、 lilo のソースと一緒に配布されている tech.tex から引用します。 (この tech.tex は古いバージョンを対象に書かれているので、現状の コードとは必ずしも一致していない部分があります。ただし基本的な 処理の流れはあまり変っていないと思うので、概略を知る上での参考 にはなるでしょう。)
The boot sector is loaded by the ROM-BIOS at address 0x07C00. It moves itself to address 0x9A000, sets up the stack (growing downwards from 0x9B000 to 0x9A200), loads the secondary boot loader at address 0x9B000 and transfers control to it.
ROM-BIOS が 1st boot loader を 0x07C00 にロードすると、 1st boot loader は自分自身を 0x9A00 にコピーし、スタックを 0x9B000 から 0x9A200 へと用意し、2nd boot loader を 0x9B000 に ロードしてそこへ制御を移行します。
2nd boot loader が実行を開始すると、 2 番目の "L" という文字が表示されます。 次に 2nd boot loader は、map ファイルの先頭の部分をロードします。 ここには LILO のブートメニューで選択肢として表示されるラベルの名前や、 パスワードなどの情報が記録されています。
この先頭部分の構造は LILO のソースコードにある common.h 中で 以下のように規定されています。
typedef struct {
char name[MAX_IMAGE_NAME+1];
char password[MAX_PW+1];
unsigned short rd_size[2]; /* RAM disk size in bytes, 0 if none */
SECTOR_ADDR initrd,start;
unsigned short start_page; /* page at which the kernel is loaded high, 0
if loading low */
unsigned short flags,vga_mode;
} IMAGE_DESCR;
このマップファイル先頭部分のロードが完了すると "O" という文字を コンソールに表示し、指定された待ち時間のあいだ、キー入力をチェックします。
この後、プロンプト表示やキー入力に対する処理を行い、これから起動するカーネルに 対応したアドレスマップを上記の SECTOR_ADDR initrd,start によって指定された セクターアドレスからロードし、このマップファイルに記録されているカーネルや initrd ファイルのセクターアドレスを解読して、それぞれをロードしていきます。
必要なセクターのロードがすべて成功した後、カーネルコードに制御を移行して LILO は動作を完了します。
2nd boot loader の動作について再び tech.tex から引用しておきます。
The secondary boot loader loads the descriptor table at 0x9D200 and the sector containing the default command line at 0x9D600. If the default command line is enabled, its magic number is invalidated and the sector is written back to disk. This potentially dangerous operation can be disabled by definingLCF_READONLY
when passingsecond.S
through cpp. Next, the secondary boot loader checks for user input. If either the default is used or if the user has specified an alternate image, the options sector is loaded at 0x9D600 and the parameter line is constructed at 0x9D800. If the resulting line contains the option"lock"
, the command line as entered by the user (it is saved before the final line is constructed) is written to the disk as the new default command line. Also, if a fallback command line is set, it is copied to the default command line sector.
2nd boot loader はデスクリプタテーブル (map file をロードするための中間情報) を 0x9D200 へ、デフォルトのコマンドラインパラメータ (lilo.conf で指定された もの) を 0x9D600 へロード。次にユーザーからの入力を調べ、選択されたイメージの オプションセクターを 0x9D600 へロードして起動パラメータ情報を 0x9D800 に構成。
Next, the floppy boot sector of that image is
loaded at 0x90000 (The floppy boot sector is only used as a source of
setup information.),
the setup part is loaded at 0x90200 and the kernel part is loaded at 0x10000,
or, if the kernel has been compiled for being loaded ``high'' (i.e. with
make bzImage
), it is loaded at 0x100000 instead. During the load
operations, the sectors of the map file are loaded at 0x9D000.
次に、(カーネルの) フロッピーブートセクターを 0x90000 へロードし、
セットアップコードを 0x90200 へ、カーネル本体コードを 0x10000 または
0x100000 へロードする。前者はカーネルが zImage の場合、後者は bzImage の
場合である。
(なおカーネルのフロッピーブートセクターとはフロッピーディスクから
Linux カーネルを起動するためのコードであり、カーネルファイルの先頭
512bytes に相当する。ここには rdev
コマンドで設定できる
ルートデバイスやスワップデバイスなどのカーネルパラメータが記録されて
いるため、HDD から起動する場合にもメモリーにロードされる。
またカーネルのセットアップコードとはフロッピーブートセクターに
続く部分であり、BIOS 関係の初期化やメモリーサイズなどデバイス情報の取得、
ビデオ関係の設定や CPU の 32bit モードへの移行などを行なう。)
マップファイル中のカーネル位置情報 (セクターアドレス) はこのカーネル
ロード作業実行中に 0x9D000 へ順次ロードされ、新しいセクターをロードする
たびに次のセクター位置情報によって上書きされていく。
If the loaded image is a kernel image, control is transferred to its setup code.
ロードされたイメージがカーネルなら、0x90200 へロードしたカーネルの セットアップコードへ制御を移行して LILO の動作は完了。
If a different operating system is booted, things are a bit more difficult: the chain loader is loaded at 0x90200 and the boot sector of the other OS is loaded at 0x90400. The chain loader moves the partition table (loaded at 0x903BE as part of the chain loader) to 0x00600 and the boot sector to 0x07C00. After that, it passes control to the boot sector.
他の OS を起動する場合は、ちょいと話が違ってくる。チェインローダは 0x90200 にロードされ、他の OS の起動セクタは 0x90400 にロードされる。次にチェイン ローダはパーティションテーブル (チェインローダの一部として 0x903BE にロード されている) を 0x00600 にコピーし、(他の OS の) 起動セクタを (ROM-BIOS が 1st boot loader をロードするとの同じ場所である) 0x07C00 にコピーする。 この後で、0x07C00 の起動セクタへ制御を移行すれば、あたかも ROM-BIOS に よってロードされたのと同様に他の OS の起動が開始される、という寸法。
Chain loaders that allow booting from a second drive (either floppy or hard disk) also install a small function to intercept BIOS calls and to swap the drive numbers at the top of available memory.
2 番目のドライブから起動するためのチェインローダには、BIOS コールを トラップ (intercept) してドライブ番号を入れ換える (swap) ための ちょっとした関数を利用可能なメモリーの一番上にインストールしておくと いう作業も必要になる。
1st boot loader および 2nd boot loader とも、ディスクまたはフロッピーから ファイルをメモリーにロードする作業は、すべて BIOS を利用して行ないます。 このために LILO は BIOS が理解できるよう、対象とするディスクのデバイス ID と セクターアドレスによってロードするファイルを指定します。
LILO がうまく動作せず、カーネルを起動できない原因として、
セクターアドレスが正しくない場合というのは、特に CHS アクセスの場合
BIOS が認識しているディスクのジオメトリ (C,H,S の値) と Linux カーネル
が認識しているジオメトリが異なる場合や、24bit の CHS アドレスでは
表現できない位置にある場合 (いわゆる 1023cyl 問題) などがあります。
これらについては linear
オプションや lba32
オプションを
指定することで対処可能です (これらのオプションについては後で説明します)。
一方ディスクのデバイス ID が正しくない場合というのは、LILO が推定した デバイス ID が実際に BIOS の認識する ID と一致していない場合です。 この問題は SCSI と IDE のディスクが混在するシステムや、最近では UDMA66 の ディスクを利用したシステムの場合に発生する可能性が高いようです。
BIOS コール 0x13 では最初のフロッピーデバイスを 0x00、2 番目のフロッピー デバイスを 0x01 として、また最初のディスクデバイスを 0x80、2 番目のディスク デバイスを 0x81 として認識します。つまり BIOS が起動ハードディスクとして 認識しているディスクは常にデバイス ID 0x80 でアクセスされます。
一方 LILO のデフォルトでは hda, hdb, sda, sdb の順にデバイスを検索し、 見つかった順に 0x80, 0x81, 0x82, 0x83 と番号を割り当てていきます。 従って SCSI と IDE の混在したシステムで SCSI ディスクを起動ディスクと して指定している場合や、UDMA66 のディスク (hde) を起動ディスクとして 指定している場合には BIOS の認識と LILO の推定値が一致せず、起動できない という問題が起こります。
こういう場合には、man lilo.conf
に記載されている
disk=device_name
の説明にある bios=dev_id
を
利用して LILO に BIOS の認識しているデバイス ID を教えてあげる
必要があります。
LILO のソースコードには BIOS を利用してデバイス ID とディスク ドライブとの対応を調べるツール (disk.com や dparam.com など) が 含まれているので、これらを利用して BIOS の認識しているデバイス ID を 確認しておくと良いでしょう。
ときどき勘違いされる方がいますが、lilo.conf
のパラメータに
"Option linear"
を追加しても、LILO のブートローダーが
ディスクからファイルをロードする時に INT13 AH=4? な Extended BIOS
コールを利用するわけではありません。
このオプションは、マザーボードの BIOS が認識するディスクのジオメトリと Linux カーネルの認識するジオメトリが互いに異なる場合、LILO の使う マップファイルにはリニアセクターアドレスを保存しておいて、起動時に 「BIOS が認識するディスクのジオメトリ」を基準とした CHS セクターアドレス に換算し、このアドレスを BIOS に渡してディスクから必要なセクターを ロードするという動作を指定するものです。
このオプションを指定しても、LILO が「1023 シリンダ」を超える領域に アクセスすることは不可能ですので、御注意下さい。
このオプションは LILO 21-3 以降でサポートされたものです。
マザーボードもディスクドライブも最近のもので、どうしても 1023 シリンダの 壁を超えて起動させたい場合には、このオプションを試してみると良いでしょう。
Linux カーネルの起動コードの詳細については、 コメントから読む Linux カーネル を参照してください。以下は LILO に関連する部分の概略です。
Linux カーネルの先頭 512bytes はカーネルが自前で持っているブートローダーであり、 "make zdisk" で作成したブートフロッピーからの起動ではこれが使われます。
この自前のブートローダーは i386 系の場合 カーネルソースツリーの arch/i386/boot/bootsect.S にあります。
LILO や LOADLIN からの起動では、カーネルのロードが完了するとこの部分 (bootsect) を飛ばして次の setup.S や video.S のコードに制御を移行します。 CPU の動作を 16bit モードから 32bit モードに移行するのは setup.S の中 で実行しています。
これらの実行が終わると次に arch/i386/boot/compressed/head.S が 実行されます。こちらは最初から 32bit モードで実行されることを前提と したコードになっています。この head.S の中にある decompress_kernel() が 圧縮されたカーネル本体を展開します。 head.S の実行は展開されたカーネル 本体に制御を渡して完了します。
"lilo"
コマンドは別名 map creator (マップ作成ツール) と
呼ばれており、その主な機能は設定ファイル lilo.conf の解読と、その中の指定
およびコマンドラインオプションによる指定に従ってマップファイル (bootloader が
起動時に使用するファイル) を作成し、その中に必要な Map Descriptor Table を
適切に記録することです。
通常 "lilo"
コマンドは lilo.conf を解読して、
使用するカーネルごとに指定された Image ファイル (および、もしあれば
initrd ファイル) に対するセクターアドレスを取得し、それを起動時に
使用する Map Descriptor Table のデータ型式にしてマップファイルに
保存します。
"lilo"
コマンドでファイルからセクターアドレスを
調べる部分は geometry.c の geo_find() にあります。
"lilo"
コマンドに -v オプションを複数重ねて起動すると、
マップ作成に使用されたセクターアドレスを観察することができます。
もし Image= で指定したファイルがマウントされていないファイルシステム
上にあると、"lilo"
コマンドはそのファイルに対する
セクターアドレスを調べることができずにエラーを出します。
なお、起動時に使用する 2nd boot loader のセクターアドレスを
1st boot loader のコード中に書き込むのも "lilo"
コマンドの仕事です。
Linux 用のブートローダーとして、この "lilo"
を
使う場合が多いのですが、マニュアルでエラーメッセージの意味を確認する
際など、動作を理解しておくときっと役に立つだろうと思ってこの文書を
作成しました。
この文書を書く際には LILO のソースコードや Linux カーネルのソースコード、 またそれらについて過去に fj.os.linux や Nifty FUNIX で議論した内容などを 参考にしています。議論の中でいろいろと教えてくださった方々に感謝します。 また JF のみなさんには文章の校正や内容についての提案など、いろいろと 有益な御意見を頂きました。
内容についてはできる限り正確を期したつもりですが、まだまだ間違いや 誤解している個所などあるかもしれません。この文書に対する御意見、 御感想、間違いの指摘などありましたら、著者または JF project まで 御一報頂ければさいわいです。
なお、この文書の配布等については GPL2 に従うものとします。
個人的な好みですが、"lilo"
のインストール先は
MBR (Master Boot Record, ハードディスク先頭のブートセクタ) よりも
Linux システムのルートパーティションのほうが適していると思います。
ハードディスクの MBR にはより一般的なブートセレクタ、たとえば
os-bs, bteasy, extIPL, btsel, GAG
などをインストールした
ほうがトラブルが起きた時の対応などを考慮するとより安心できます。
GAG の附属文書については JF に日本語訳があります。
最近は MBM や SBM などの新しいブートマネージャも人気があるようです。 MBM は起動メニューの背景として好きな画像を表示できる点が特徴です。
この中でも特に個人的なお薦めは extIPL です。バージョン 5 (現在ベータ版のテスト公開中です) からは LBA32 アクセスにも正式対応し、また起動時のメニューが GUI 風なものとなり、 パーティションを識別する文字列を任意に設定できるようにもなりました。 また拡張領域内の論理領域からも従来同様起動可能です。 ソース公開 (Ver 5 からライセンスが GPL2 になりました) であり、かつ Linux 上で make できる (gcc + nasm) 点も見逃せないポイントです。