JF-INDEX (document list of JF Project)

LILO "the Linux Loader" の動作について

佐野武俊 / Taketoshi Sano, (kgh12351@nifty.ne.jp)

$Date: 2000/12/15 18:39:54 $, ($Revision: 1.10 $)


この文書は、LILO "the Linux Loader" の動作について説明するものです。 なお、対象としている LILO は version 21.5.1 であり、現時点での最新版 である LILO version 21.6 とは異なります。

1. 起動時の動作

2. /sbin/lilo コマンドを実行した時の動作

3. 補足


1. 起動時の動作

起動時に実行される 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 の 機能です。

1.1 1st 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 に ロードしてそこへ制御を移行します。

1.2 2nd boot loader

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 defining LCF_READONLY when passing second.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) ための ちょっとした関数を利用可能なメモリーの一番上にインストールしておくと いう作業も必要になる。

1.3 BIOS によるディスクデータのロード

1st boot loader および 2nd boot loader とも、ディスクまたはフロッピーから ファイルをメモリーにロードする作業は、すべて BIOS を利用して行ないます。 このために LILO は BIOS が理解できるよう、対象とするディスクのデバイス ID と セクターアドレスによってロードするファイルを指定します。

LILO がうまく動作せず、カーネルを起動できない原因として、

  1. セクターアドレスが正しくない場合
  2. ディスクのデバイス ID が正しくない場合
の 2 種類が考えられます (両方に該当する場合もあり得ます)。

セクターアドレスが正しくない場合というのは、特に 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 を 確認しておくと良いでしょう。

1.4 Option linear

ときどき勘違いされる方がいますが、lilo.conf のパラメータに "Option linear" を追加しても、LILO のブートローダーが ディスクからファイルをロードする時に INT13 AH=4? な Extended BIOS コールを利用するわけではありません。

このオプションは、マザーボードの BIOS が認識するディスクのジオメトリと Linux カーネルの認識するジオメトリが互いに異なる場合、LILO の使う マップファイルにはリニアセクターアドレスを保存しておいて、起動時に 「BIOS が認識するディスクのジオメトリ」を基準とした CHS セクターアドレス に換算し、このアドレスを BIOS に渡してディスクから必要なセクターを ロードするという動作を指定するものです。

このオプションを指定しても、LILO が「1023 シリンダ」を超える領域に アクセスすることは不可能ですので、御注意下さい。

1.5 Option lba32

このオプションは LILO 21-3 以降でサポートされたものです。

マザーボードもディスクドライブも最近のもので、どうしても 1023 シリンダの 壁を超えて起動させたい場合には、このオプションを試してみると良いでしょう。

1.6 その他

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 の実行は展開されたカーネル 本体に制御を渡して完了します。


2. /sbin/lilo コマンドを実行した時の動作

"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" コマンドの仕事です。


3. 補足

3.1 この文書について

Linux 用のブートローダーとして、この "lilo" を 使う場合が多いのですが、マニュアルでエラーメッセージの意味を確認する 際など、動作を理解しておくときっと役に立つだろうと思ってこの文書を 作成しました。

この文書を書く際には LILO のソースコードや Linux カーネルのソースコード、 またそれらについて過去に fj.os.linux や Nifty FUNIX で議論した内容などを 参考にしています。議論の中でいろいろと教えてくださった方々に感謝します。 また JF のみなさんには文章の校正や内容についての提案など、いろいろと 有益な御意見を頂きました。

内容についてはできる限り正確を期したつもりですが、まだまだ間違いや 誤解している個所などあるかもしれません。この文書に対する御意見、 御感想、間違いの指摘などありましたら、著者または JF project まで 御一報頂ければさいわいです。

なお、この文書の配布等については GPL2 に従うものとします。

3.2 MBR 用ブートセレクタについて

個人的な好みですが、"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) 点も見逃せないポイントです。

3.3 参考


sgml21html conversion date: Sat Dec 16 03:56:52 JST 2000

[