<kgh12351@nifty.ne.jp>
たぶん、一度くらいどこかで目にしたことがあるかもしれませんが 「Linux」という名前は当時フィンランドの大学生であった Linus Torvalds さんが自分の作った「カーネル」に、自分の名前から 一部を取って付けた名前です。
さて、そこで問題。「カーネル」って何でしょう ?
「カーネル」とは、システム内で動作中の各プログラムから出される 要求に応じてメモリーやディスクなどのハードウェア資源の管理や、 CPU 時間の配分などを行なう OS の中枢部分です。
例えば "top" コマンドを実行してしばらく眺めていると、 リストされているプロセスの順番が時々入れ替わることがあるのに 気づくでしょう。この「順番の入れ替え」はカーネルのタスク管理 (スケジューリング) によるものです。また、プリンタを接続して 印刷できるように設定する際、
cat test.pr >/dev/lp0
などの操作によってパラレルポートへの信号出力をテストした 経験はありませんか ? この「 /dev/lp0 に出力したデータはそのままパラレルポートに出力される」 という動作はカーネル内の lp ドライバによって実現されています。
以下、この文書では Linux カーネルのソースコードツリーを、 コメントを手がかりにしてちょっとだけ探検してみることにします。
まずは、バージョン 2.2.5 のカーネルを展開して、そのトップディレクトリ に移動してみましょう。そう、通常 /usr/src/linux/ として見ることが できる場所です。
まずはどんなファイルがあるか、 "ls" で調べてみます。
$ ls -F COPYING Makefile arch/ init/ mm/ CREDITS README drivers/ ipc/ net/ Documentation/ REPORTING-BUGS fs/ kernel/ scripts/ MAINTAINERS Rules.make include/ lib/
最初に読んでおくべきファイルは "README" ですね。これは カーネルでなくても、一般のアプリケーションのソースでも同じです。 が、ここではこのファイルの内容については省略して、その代わりに "CREDITS" と "MAINTAINERS" という 2 つのファイル の内容について紹介したいと思います。
"CREDITS" ファイルの冒頭には
This is at least a partial credits-file of people that have contributed to the Linux project. It is sorted by name and formatted to allow easy grepping and beautification by scripts. The fields are: name (N), email (E), web-address (W), PGP key ID and fingerprint (P), description (D), and snail-mail address (S). Thanks, Linus
と書かれています。このファイルには Linux カーネルの開発に貢献した 人々 (の一部) の名前が書かれている、というわけです。
例えば、先日 TV の特集で取材されていた日本の新部さんの名前も
N: Niibe Yutaka D: PLIP driver D: Asynchronous socket I/O in the NET code
と記載されています。もちろん、Linus さんの名前は
N: Linus Torvalds D: Original kernel hacker
としっかり載っていますし、 ac パッチで有名な Alan Cox や fat32 対応を実装した Gordon Chaffee、それに以前 Linux Kernel の Sound Driver を書いていた OpenSoundSystem の Hannu Savolainen の 名前もあります。
N: Alan Cox D: Linux Networking (0.99.10->2.0.29) D: Original Appletalk, AX.25, and IPX code D: Current 3c501 hacker. >>More 3c501 info/tricks wanted<<. D: Watchdog timer drivers D: Linux/SMP x86 (up to 2.0 only) D: Initial Mac68K port D: Video4Linux design, bw-qcam and PMS driver ports. D: 2.1.x modular sound
N: Gordon Chaffee D: vfat, fat32, joliet, native language support
N: Hannu Savolainen D: Kernel sound drivers
他にも、Slackware の Patrick Volkerding、 Debian の Ian A. Murdock と Ian Jackson 他、それに いろいろと各方面で有名な Eric S. Raymond や XFree86 の Dirk Hohndel (彼も TV で取材されていましたね) や Harald Koenig の名前もありますし、LDP 関係者である Michael K. Johnson や Matt Welsh も載っています。
N: Patrick Volkerding D: Produced the Slackware distribution, updated the SVGAlib D: patches for ghostscript, worked on color 'ls', etc.
N: Ian A. Murdock D: Creator of Debian distribution
N: Ian Jackson D: FAQ maintainer and poster of the daily postings D: FSSTND group member D: Debian core team member and maintainer of several Debian packages
N: Eric S. Raymond D: terminfo master file maintainer D: Editor: Installation HOWTO, Distributions HOWTO, XFree86 HOWTO D: Author: fetchmail, Emacs VC mode, Emacs GUD mode
N: Dirk Hohndel D: The XFree86[tm] Project
N: Harald Koenig D: XFree86 (S3), DCF77, some kernel hacks and fixes
N: Michael K. Johnson D: The Linux Documentation Project D: Kernel Hackers' Guide D: Procps D: Proc filesystem D: Maintain tsx-11.mit.edu D: LP driver
N: Matt Welsh D: Linux Documentation Project coordinator D: Author, _Running_Linux_ and I&GS guide D: Linuxdoc-SGML formatting system D: Keithley DAS1200 device driver D: Maintainer of sunsite WWW and FTP, moderator c.o.l.answers
ちょっと " egrep '^N:' CREDITS |wc" として 数えてみたところ、2.2.5 カーネルの CREDITS ファイルには ざっと 273 人の名前が挙がっているようです。(2.0.36 で調べてみたら 204 人でした) この中に書かれている名前を何人知っているか、 数えてみると Linux 界へのハマリ度がわかっておもしろいかもしれません。
そうそう、「コメント」にこだわりを持つものとしては、 このファイルの最後も見逃さないようにしておくことが必要です。
# Don't add your name here, unless you really _are_ after Marc # alphabetically. Leonard used to be very proud of being the # last entry, and he'll get positively pissed if he can't even # be second-to-last. (and this file really _is_ supposed to be # in alphabetic order)
"pissed off" っていうのは、「激しく怒る」とか 「頭にくる」という意味らしいですね。何ていうか、カーネル開発者たち も人間なんだな、という感じがして微笑ましい気がします。
あと、もうひとつ忘れてました。これです。
N: Lars Wirzenius D: Linux System Administrator's Guide D: Co-moderator, comp.os.linux.announce D: Original sprintf in kernel D: Personal information about Linus D: Original kernel README D: Linux News (electronic magazine) D: Meta-FAQ, originator D: INFO-SHEET, former maintainer D: Author of the longest-living linux bug
最後の行に注目してください。なかなかユーモアのある方のようです。
普通「自分がバグを入れた」というのは、あまり自慢できることではないので、 コード開発への貢献に謝意を表するための「献辞」 CREDITS に載せるようなことは あまりしないと思うのですが、上記の文がわざわざ書いてあるというのは、 たぶんこの Wirzenius さんが自分で「こう書いてくれ」と依頼されたんじゃないかと 想像しています。
上記の「最も長く生きのびたバグの著者」という文からは、 通常ならあまり公表したくないと思うような過去の「バグ」のことでさえ、 「笑い」の対象にしてしまおうというバイタリティというか、 ユーモアのセンスを感じます。 「どうだい ? この俺がかの有名な『最も長く解決 されずに残ったバグ』の著者なんだぜ ! スゴイだろう !!」 みたいな感じですね。
さて、そろそろ次に移りましょう。 今度は "MAINTAINERS" ファイルです。 このファイルの冒頭には
List of maintainers and how to submit kernel changes Please try to follow the guidelines below. This will make things easier on the maintainers. Not all of these guidelines matter for every trivial patch so apply some common sense.
と書かれています。つまり基本的には Linux カーネルを自分の手で変更して 楽しむ人 (カーネルハッカー) を対象に、そのパッチをどこに送れば 標準として採用されるのか、という手順を説明したものです。
しかし、このファイルにも実は楽しめる点があるのです。 それはこのファイルの一番最後。
THE REST P: Linus Torvalds S: Buried alive in reporters
ちなみに、2.0.36 までの 2.0.xx 系ではこうなっていました。
REST: P: Linus Torvalds S: Buried alive in email
ここ数年の Linux と Linus さんを取り巻く状況の変化を 物語っているようです。
ここではシステムの電源 ON から /sbin/init が起動されるまでの 流れを大まかに説明します。
PC/AT 互換機ではマザーボードの BIOS が実行するブートシーケンスによって 起動ディスクのブートセクタに書き込まれているコードを特定のメモリー位置 へロードし、そのコードを実行 (ロードしたコードの開始位置へ jump) します。
(「特定のメモリー位置」とは segment 0h / address 7C00h または [0000:7C00] ですが、Linux カーネルの中では offset が 0 から 始められるように segment を 7C0 にしているらしいという情報を 頂きました。)
PC 上で利用される各 OS は、この BIOS によるブートを前提として、 最初に読込まれるコードから始まって順に自分より大きいコードを ロードし、最終的に必要なコードをすべてメモリーに読み込んで 実行を開始するという処理を行なっています。
この一連の動作を一般にブートストラップ、と呼びます。 ブートは boot (長靴)、ストラップは strap (革紐) のことで、 自分が履いている長靴の紐を自分で引っ張って体を持ち上げようとすることに 例えています。
なお「Bootstrap」についての解説が http://www.oreilly.com/reference/dictionary/terms/B/Bootstrap.htm
にありますので、興味のある方は調べてみることをお勧めします。 なおこの Web ページには「"Bootstrap" のことを IBM 用語で IPL, Initial Program Load と呼ぶ」と書かれています。
Linux カーネルの内部には、(あらためて聞くと驚かれるかもしれませんが) すくなくとも i386 系ではハードディスクから自前でブートさせるための コードは用意されていません。 ハードディスクからのブートでは LILO や LOADLIN などのブートローダーを 利用することが前提となっています。
フロッピーからの起動の場合、カーネル内で最初にロードされるコードは arch/i386/boot/bootsect.S です。このコードは同じディレクトリにある arch/i386/boot/setup.S のコードと、カーネルの残りをロードして setup.S に制御を移行します。
arch/i386/boot/setup.S はメモリーサイズやディスク情報、 またコンソール用ビデオカードの情報や APM BIOS のチェックなど、 システムに関するいろいろな情報を BIOS から取得して、 後でデバイスドライバーを初期化する際に使用できるよう、 メモリー上に保存します。
さらに LILO などのブートローダーを使用した場合には、 起動時にキーボードから入力されたカーネルオプションを 後で参照できるようにメモリー上の特定の場所にコピーするのも setup.S の仕事です。
setup.S は BIOS 情報の取得を完了すると、CPU のモードを 起動時の 16bit モードから 32bit (protected) モードに切り替えて、 arch/i386/boot/compressed/head.S に処理を移行します。
arch/i386/boot/compressed/head.S はいくつかのチェックと SMP の場合に必要な処理を実行した後で、同じディレクトリに 存在する arch/i386/boot/compressed/misc.c で定義された decompress_kernel() という関数を使って、 gzip 圧縮された状態でメモリーにロードされているカーネル本体を 展開します。
コンソールモニターに
Uncompressing Linux...
という表示が出力されるのは、この decompress_kernel() の実行中です。
展開が終了すると、arch/i386/boot/compressed/head.S は 新しくメモリー上に現われた本来のカーネルコードへと 処理を移行します。
arch/i386/boot/compressed/head.S によって展開された 「本来のカーネルコード」の先頭に存在しているのは arch/i386/kernel/head.S です。
このコードは主に CPU に関する初期化 (ページテーブルの準備や 割り込み (インタラプト) テーブルの初期化など) を実行し、 最後に init/main.c にある start_kernel() を実行します。
この start_kernel() ではここまでにシステムから集めてきて、 メモリー上の特定位置に保存されているいろいろな情報を使って、 メモリー管理やタスクスケジュールに関連するカーネル内の 各デバイスドライバーを設定していきます。 またコンソール出力の設定を実行するのもこの start_kernel() です。
start_kernel() は同じ init/main.c にある init() を kernel_thread () を使って起動した後、やはり同じ init/main.c に ある cpu_idle() を実行します。この cpu_idle() は arch/i386/kernel/process.c の中で定義されている sys_idle() を 実行する無限ループです。
さて、start_kernel() から kernel_thread() を経由して 起動された init/main.c で定義されている init() ですが、 最初に lock_kernel() (これは SMP の場合にのみ意味があります) を 実行したあと、do_basic_setup() を実行します。
この do_basic_setup() も init/main.c の中で定義されていますが、 バスの初期化や各デバイスドライバの初期化、ファイルシステム 関連コードの初期化、ルートファイルシステムのマウントなどを ここで実行しています。ちなみに、これらの処理は 2.0.36 では start_kernel() の内部で実行されていました。2.2.xx 系になって 移植性を高めるためか、このあたりの処理がさらに細分化され コードの構成が変更されたようです。
init/main.c で定義されている init() は do_basic_setup() から 処理が戻ってくると、起動時にのみ必要とされたメモリー領域の開放と コンソール出力のオープンを実行し、
/sbin/init, /etc/init, /bin/init, /bin/sh
を順番に試して、最初に見つかった実行可能なものへと処理を移行します。
これ以降の処理は、/etc/inittab での設定や /etc/rc.d または /etc/init.d と /etc/rc?.d による起動スクリプトの設定などに よって動作が決まります。これについては、たぶん既にご承知の かたも多いのではないでしょうか。
「カーネルの起動」についての概要はこれで終わりです。 次の節から、上に述べた各段階で使用されるコードについて コメントを頼りに調べていくことにします。
上記の「概要」で説明したように、フロッピーからの起動の場合、 カーネル内で最初にロードされるコードは arch/i386/boot/bootsect.S です。
まずは、このファイルから眺めてみることにしましょう。
! ! bootsect.s Copyright (C) 1991, 1992 Linus Torvalds ! modified by Drew Eckhardt ! modified by Bruce Evans (bde) ! ! bootsect.s is loaded at 0x7c00 by the bios-startup routines, and moves ! itself out of the way to address 0x90000, and jumps there. ! ! bde - should not jump blindly, there may be systems with only 512K low ! memory. Use int 0x12 to get the top of memory, etc. ! ! It then loads 'setup' directly after itself (0x90200), and the system ! at 0x10000, using BIOS interrupts. ! ! NOTE! currently system is at most (8*65536-4096) bytes long. This should ! be no problem, even in the future. I want to keep it simple. This 508 kB ! kernel size should be enough, especially as this doesn't contain the ! buffer cache as in minix (and especially now that the kernel is ! compressed :-) ! ! The loader has been made as simple as possible, and continuous ! read errors will result in a unbreakable loop. Reboot by hand. It ! loads pretty fast by getting whole tracks at a time whenever possible.
上の「概要」に書いたことが、しっかり冒頭にコメントとして 記載されていますね。
「bootsect.s は BIOS のブートシーケンスによって メモリー上のアドレス 0x7c00 にロードされる。 次に bootsect.s は自分自身をアドレス 0x90000 に移動し、 そしてそこへジャンプ (制御を移行) する。」
ひとつ飛ばして
「次に自分自身の直後 (0x90200) に 'setup' を、またシステムを 0x10000 に BIOS インタラプトを使ってロードする」
その次が今となっては時代を感じるコメントですね。
「注意! 現在、システムは最大でも (8*65536-4096) バイトまでの長さ である。この制限については、例え将来においても、問題を生じることは無い。 物事はなるべく単純にしておきたいものだ。このカーネルサイズ 508 kB と いう制限は、minix のようにバッファーキャッシュを含んでいるわけでは ないことを考えると (そしてまた、現在カーネルは圧縮された状態でロード されていることを考えると :) まったく十分な大きさと言えるはずである。」
実際には数年前から既にこの「508kB の壁」は十分とは言えなくなって しまい、bZimage という抜け道が用意されています。しかし、このために 「物事はなるべく単純に」という Linus の希望から実際のコードがやや 離れてしまったように感じられます。
そろそろ、コードの中身に入ってみましょう。
#include <linux/config.h> /* for CONFIG_ROOT_RDONLY */ #include <asm/boot.h> .text SETUPSECS = 4 ! default nr of setup-sectors BOOTSEG = 0x07C0 ! original address of boot-sector INITSEG = DEF_INITSEG ! we move boot here - out of the way SETUPSEG = DEF_SETUPSEG ! setup starts here SYSSEG = DEF_SYSSEG ! system loaded at 0x10000 (65536). SYSSIZE = DEF_SYSSIZE ! system size: number of 16-byte clicks
最初の行、#include <linux/config.h>
は
include/linux/config.h の定義を使う、ということです。
で、このヘッダーファイルの中身はこうなってます。
#ifndef _LINUX_CONFIG_H #define _LINUX_CONFIG_H #include <linux/autoconf.h> #endif
これだけ。これは include/linux/autoconf.h の定義を使いなさい、と いうことです。で、このファイルを探してみると、ありません。
実はこの autoconf.h は make config / menuconfig / xconfig などを 実行して始めて作成されるファイルであって、単にアーカイブを展開した だけでは存在しないものです。
今回は時間の都合で、このファイルを作られる手順を追いかけるのは
省略し、次の #include <asm/boot.h>
を調べてみる
ことにします。
こちらは include/asm-i386/boot.h が探しているファイルで
#ifndef _LINUX_BOOT_H #define _LINUX_BOOT_H /* Don't touch these, unless you really know what you're doing. */ #define DEF_INITSEG 0x9000 #define DEF_SYSSEG 0x1000 #define DEF_SETUPSEG 0x9020 #define DEF_SYSSIZE 0x7F00 /* Internal svga startup constants */ #define NORMAL_VGA 0xffff /* 80x25 mode */ #define EXTENDED_VGA 0xfffe /* 80x50 mode */ #define ASK_VGA 0xfffd /* ask for it at bootup */ #endif
と書かれています。このあたりも、2.0.36 と比較して変更された部分ですね。 (2.0.36 ではこの定義が include/linux/config.h にありました)
さて、話をもとの bootsect.S に戻します。
.text SETUPSECS = 4 ! default nr of setup-sectors BOOTSEG = 0x07C0 ! original address of boot-sector INITSEG = DEF_INITSEG ! we move boot here - out of the way SETUPSEG = DEF_SETUPSEG ! setup starts here SYSSEG = DEF_SYSSEG ! system loaded at 0x10000 (65536). SYSSIZE = DEF_SYSSIZE ! system size: number of 16-byte clicks
この部分の最初にある ".text" は、コンテキストという意味で 実行コードの開始位置を示しています。
最初、私はここから次の".globl _main" までは「変数」の定義だと 思っていたのですが、そうではなくて、アセンブル (機械語に翻訳) する際に数値に 変換されてコードに代入される「定数定義」(C の #define で定義されるマクロ に似たもの) と考えたほうが良いと教わりました。
ここで参照されている
DEF_INITSEG, DEF_SETUPSEG, DEF_SYSSEG, DEF_SYSSIZE
は既に見てきたように、include/asm/boot.h で定義されています。
この部分の下にも、いくつか定数の定義が続いていますが、省略して 次に進みます。
次の ".globl _main" 以降から実際に動作する際に 使われるコードが始まっています。
! ld86 requires an entry symbol. This may as well be the usual one. .globl _main _main: #if 0 /* hook for debugger, harmless unless BIOS is fussy (old HP) */ int 3 #endif mov ax,#BOOTSEG mov ds,ax mov ax,#INITSEG mov es,ax mov cx,#256 sub si,si sub di,di cld rep movsw jmpi go,INITSEG ! ax and es already contain INITSEG
ここで "#BOOTSEG" は BIOS によって bootsect.S の コードがロードされたアドレス、"#INITSEG" は bootsect.S が自分自身をコピーして処理を移す(ジャンプする) アドレスです。ここでは "movsw" までの行で「自分自身の コピー」を実行し、"jmpi" で "#INITSEG" にコピーされた自分自身の "go" ラベルの位置へジャンプ しています。
このあとしばらくフロッピードライブをうまく動作させるための準備が 行なわれます。そして次の "load_setup" から setup.S の コードをロードしていきます。
load_setup: xor ah,ah ! reset FDC xor dl,dl int 0x13
最初はフロッピードライブコントローラをリセットするところから始まって
xor dx, dx ! drive 0, head 0 mov cl,#0x02 ! sector 2, track 0 mov bx,#0x0200 ! address = 512, in INITSEG mov ah,#0x02 ! service 2, nr of sectors mov al,setup_sects ! (assume all on head 0, track 0) int 0x13 ! read it jnc ok_load_setup ! ok - continue
ドライブ、ヘッド、セクター、トラックなどの位置を初期化し、 読み出しアドレスを指定して "int 0x13" の実行に よって BIOS のセクター読み込み機能を利用して "setup.S" のコードをメモリー上にロードしています。ロード先のアドレスは "INITSEG" のアドレス 0x9000 に 0x200 を加えた 0x9200 となります。
setup.S のロードを完了すると "Loading" という メッセージを表示して、次の段階 (圧縮されたシステム本体のロード) へと進みます。
got_sectors: ! Restore es mov ax,#INITSEG mov es,ax ! Print some inane message mov ah,#0x03 ! read cursor pos xor bh,bh int 0x10 mov cx,#9 mov bx,#0x0007 ! page 0, attribute 7 (normal) mov bp,#msg1 mov ax,#0x1301 ! write string, move cursor int 0x10 ! ok, we've written the message, now ! we want to load the system (at 0x10000) mov ax,#SYSSEG mov es,ax ! segment of 0x010000 call read_it call kill_motor call print_nl
上の "int 0x10" は画面表示制御の BIOS インタラプト であり、 "#msg1" は bootsect.S の終りのほうで
msg1: .byte 13,10 .ascii "Loading"
として定義されています。ここで ".byte" の
13 は CR
(キャリッジリターン)、
10 はLF
(ラインフィード) です。
さて、システム本体のロードですが、これは上に書かれた "call read_it"によって呼び出される
! This routine loads the system at address 0x10000, making sure ! no 64kB boundaries are crossed. We try to load it as fast as ! possible, loading whole tracks whenever we can. ! ! in: es - starting address segment (normally 0x1000) ! sread: .word 0 ! sectors read of current track head: .word 0 ! current head track: .word 0 ! current track read_it:
以降の部分、特に次の rp_read: から始まる部分によります。
rp_read: #ifdef __BIG_KERNEL__ #define CALL_HIGHLOAD_KLUDGE .word 0x1eff,0x220 ! call far * bootsect_kludge ! NOTE: as86 can't assemble this CALL_HIGHLOAD_KLUDGE ! this is within setup.S #else mov ax,es sub ax,#SYSSEG #endif cmp ax,syssize ! have we loaded all yet? jbe ok1_read ret
ここで "CALL_HIGHLOAD_KLUDGE" はコメントにあるように ちょうどこの部分のすこし前にロードした setup.S のコードに含まれて いる bootsect_kludge に対応したアドレスから始まるコードです。 setup.S の中では "bootsect_kludge" は次のように 定義されています。
bootsect_kludge: .word bootsect_helper,SETUPSEG
またこの bootsect_helper は setup.S の中で以下のように 定義されています。
! This routine only gets called, if we get loaded by the simple ! bootsect loader _and_ have a bzImage to load. ! Because there is no place left in the 512 bytes of the boot sector, ! we must emigrate to code space here. ! bootsect_helper:
コメントにしっかり「bootsect.S によって bzImage 形式のカーネルが ロードされた場合に限って実行される、と書いてありますね。
さて、bootsect.S の中で、実際にフロッピーからシステムを 読んでいるのは、以下の部分です。
read_track: pusha pusha mov ax, #0xe2e ! loading... message 2e = . mov bx, #7 int 0x10 popa
ここの 0x10 は画面表示を行なう BIOS インタラプトコールです。 既に setup.S のコードをロードした時点で、 "Loading" というメッセージが画面に出力されているはずなので、ここでは "." の出力のみを行なっています。
mov dx,track mov cx,sread inc cx mov ch,dl mov dx,head mov dh,dl and dx,#0x0100 mov ah,#2 push dx ! save for error dump push cx push bx push ax int 0x13 jc bad_rt add sp, #8 popa ret
BIOS インタラプト "Int 0x13" の実行によって フロッピー上のシステムファイルがメモリーにロードされていきます。
ロードが完了すると、先に引用した
call read_it call kill_motor call print_nl
を順に実行してフロッピードライブのモーターを OFF にし、 画面に改行コード (NewLine) を出力します。
その後、ルートデバイスのチェックを経て
! after that (everything loaded), we jump to ! the setup-routine loaded directly after ! the bootblock: jmpi 0,SETUPSEG
"SETUPSEG" に存在する setup.S のコードに ジャンプします。
さて、arch/i386/boot/setup.S に進みましょう。
まずはファイル冒頭のコメントから。
! ! setup.S Copyright (C) 1991, 1992 Linus Torvalds ! ! setup.s is responsible for getting the system data from the BIOS, ! and putting them into the appropriate places in system memory. ! both setup.s and system has been loaded by the bootblock. ! ! This code asks the bios for memory/disk/other parameters, and ! puts them in a "safe" place: 0x90000-0x901FF, ie where the ! boot-block used to be. It is then up to the protected mode ! system to read them from there before the area is overwritten ! for buffer-blocks. ! ! Move PS/2 aux init code to psaux.c ! (troyer@saifr00.cfsat.Honeywell.COM) 03Oct92 ! ! some changes and additional features by Christoph Niemann, ! March 1993/June 1994 (Christoph.Niemann@linux.org) ! ! add APM BIOS checking by Stephen Rothwell, May 1994 ! (Stephen.Rothwell@canb.auug.org.au) ! ! High load stuff, initrd support and position independency ! by Hans Lermen & Werner Almesberger, February 1996 ! <lermen@elserv.ffm.fgan.de>, <almesber@lrc.epfl.ch> ! ! Video handling moved to video.S by Martin Mares, March 1996 ! <mj@k332.feld.cvut.cz> ! ! Extended memory detection scheme retwiddled by orc@pell.chi.il.us (david ! parsons) to avoid loadlin confusion, July 1997
どうでしょう ? もうこのコメントだけ読めば、このファイルに 書かれているコードが何をしているのか、だいたいわかったような 気になりませんか ?
要するに「setup.S は BIOS からシステムに関するデータを取得し、 システムメモリーの適切な場所に保管するためのコードである。」 ということです。
まあ、これだけではあんまりなので、ちょっと面白そうなところを 抜き出してみると、
! SETUP-header, must start at CS:2 (old 0x9020:2) ! .ascii "HdrS" ! Signature for SETUP-header .word 0x0201 ! Version number of header format ! (must be >= 0x0105 ! else old loadlin-1.5 will fail) realmode_swtch: .word 0,0 ! default_switch,SETUPSEG start_sys_seg: .word SYSSEG .word kernel_version ! pointing to kernel version string ! note: above part of header is compatible with loadlin-1.5 (header v1.5), ! must not change it type_of_loader: .byte 0 ! = 0, old one (LILO, Loadlin, ! Bootlin, SYSLX, bootsect...) ! else it is set by the loader: ! 0xTV: T=0 for LILO ! T=1 for Loadlin ! T=2 for bootsect-loader ! T=3 for SYSLX ! T=4 for ETHERBOOT ! V = version loadflags: ! flags, unused bits must be zero (RFU) LOADED_HIGH = 1 ! bit within loadflags, ! if set, then the kernel is loaded high CAN_USE_HEAP = 0x80 ! if set, the loader also has set heap_end_ptr ! to tell how much space behind setup.S | can be used for heap purposes. ! Only the loader knows what is free! #ifndef __BIG_KERNEL__ .byte 0x00 #else .byte LOADED_HIGH #endif
"type_of_loader" のところで
LILO, Loadlin, bootsect-loader, SYSLX, ETHERBOOT
がリストされています。
Linux のカーネルローダーも結構種類がありますね。
この最初の部分ではローダーのチェックをしています。古いローダーでは "big kernel" をうまく扱えないため、そういう場合には 警告を発して止るようになっています。
ローダーのチェックが終わると、メモリーサイズのチェックが 始まります。
loader_ok: ! Get memory size (extended mem, kB) #ifndef STANDARD_MEMORY_BIOS_CALL push ebx xor ebx,ebx ! preload new memory slot with 0k mov [0x1e0], ebx mov ax,#0xe801 int 0x15 jc oldstylemem
"int 0x15" と #0xe801 を組み合わせて メモリーサイズをチェックしています。
! Memory size is in 1 k chunksizes, to avoid confusing loadlin. ! We store the 0xe801 memory size in a completely different place, ! because it will most likely be longer than 16 bits. ! (use 1e0 because that's what Larry Augustine uses in his ! alternative new memory detection scheme, and it's sensible ! to write everything into the same place.)
次はキーボードリピートレートの設定です。
! Set the keyboard repeat rate to the max mov ax,#0x0305 xor bx,bx ! clear bx int 0x16
そしてコンソール用ビデオカードのチェック。
! Check for video adapter and its parameters and allow the ! user to browse video modes. call video ! NOTE: we need DS pointing to boot sector
この "call video" で呼び出されているのは 同じディレクトリにある arch/i386/boot/video.S の中で 定義されている関数です。
以後、各ハードウェアをチェックしている部分で、コメントだけ 拾っていくと ( [] 内はコメントを和訳したものです)
! Get hd0 data [hd0 のデータを取得] ! Get hd1 data [hd1 のデータを取得] ! Check that there IS a hd1 :-) [hd1 が接続されているかどうかチェック] ! check for Micro Channel (MCA) bus [マイクロチャンネル (MCA) バスをチェック] ! Check for PS/2 pointing device [PS/2 のポインタ装置 (マウス、パッド、スティックなど) をチェック] #ifdef CONFIG_APM ! check for APM BIOS [APM BIOS をチェック] ! ! Redo the installation check as the 32 bit connect ! modifies the flags returned on some BIOSs ! [32bit で接続するとフラッグの値を変更する BIOS があるので インストレーションチェックを再度実行] done_apm_bios: #endif
などの処理があります。
この後、
! Now we want to move to protected mode ... [いよいよプロテクトモードへ移行する時だ、、、] ! we get the code32 start address and modify the below 'jmpi' ! (loader may have changed it) [code32 の開始アドレスを取得して下の "jmpi" を変更する (ローダーによって変更されているかもしれないので)] ! Now we move the system to its rightful place ! ...but we check, if we have a big-kernel. ! in this case we *must* not move it ... [さあ、システムを正規の場所へ移動しよう、、、しかしその前に big-kernel を使っているかどうかチェックしないとダメだ。 もし big-kernel を使っているなら、場所を移動 *してはならない* ] ! then we load the segment descriptors [次にセグメントデスクリプタ (アドレスを示す情報) をロードする] ! If we have our code not at 0x90000, we need to move it there now. ! We also then need to move the parameters behind it (command line) ! Because we would overwrite the code on the current IP, we move ! it in two steps, jumping high after the first one. [もしカーネルコードが 0x90000 に無かったら、この時点でそこへ移動する 必要がある。また、この後のパラメータ (コマンドラインパラメータ) も あわせて移動しなければならない。 この動作は現在の IP にあるコードを上書きしてしまうため、移動は 2 段階に分けて行われる。最初の移動の後で high 領域へ移行するのだ。] ! that was painless, now we enable A20 [これはたいしたことじゃない。さあ、A20 を有効にしよう。] ! wait until a20 really *is* enabled; it can take a fair amount of ! time on certain systems; Toshiba Tecras are known to have this ! problem. The memory location used here is the int 0x1f vector, ! which should be safe to use; any *unused* memory location < 0xfff0 ! should work here. [a20 が「本当に」有効になるまで待とう。ある種のシステムではこのために えらく長い時間が必要なんだ。 Toshiba の Tecra シリーズはこの問題を 持っていることで知られている。ここで使われているメモリーの位置は ベクター int 0x1f で、使っても大丈夫なはず。この時点では 0xfff0 より 下の使われていないメモリー位置ならどこでも利用できるはずなんだ。]
と、32bit protect mode への移行の準備を進めていきます。
最後の "wait until a20 really ..." は 2.0.xx 系 カーネルで bzImage にすると起動できなかった東芝の TECRA や Portege などを始めとしたノート PC などへの対策ですね。 2.2 系カーネルでは TECRA などのノート PC でも安心して bzImage を 利用できるようになったらしいという話を聞いたことがあります。
続いて、インタラプト関係の処理。
! make sure any possible coprocessor is properly reset.. [接続されている可能性のあるすべてのコプロセッサがちゃんとリセットされて いることを確認しよう、、、] ! well, that went ok, I hope. Now we have to reprogram the interrupts :-( ! we put them right after the intel-reserved hardware interrupts, at ! int 0x20-0x2F. There they won't mess up anything. Sadly IBM really ! messed this up with the original PC, and they haven't been able to ! rectify it afterwards. Thus the bios puts interrupts at 0x08-0x0f, ! which is used for the internal hardware interrupts as well. We just ! have to reprogram the 8259's, and it isn't fun. [ああ、ここまではたぶんうまくいった。そう思うよ。さて、これから 割り込みを再設定 (reprogram) しなきゃいけない。(ウンザリ) Linux では割り込みを Intel が予約したハードウェア割り込みのすぐ後、 int 0x20-0x2F に押し込むんだ。ここなら何も邪魔されないからね。 悲しいことに、IBM は最初の PC を作る際、割り込みの設定をメチャクチャに してしまった。そして、結局彼らはこれを直すことができなかったんだ。 だから、BIOS の割り込みはハードウェアの内部割り込みが使っているのと 同じ領域、0x08-0x0f にあるんだよ。というわけで、僕らはこれから 8259 を プログラムし直さないといけない。そしてこいつは全然楽しくないんだ。] ! Well, that certainly wasn't fun :-(. Hopefully it works, and we don't ! need no steenking BIOS anyway (except for the initial loading :-). ! The BIOS routine wants lots of unnecessary data, and it's less ! "interesting" anyway. This is how REAL programmers do it. [うーん、こいつはたしかにちっとも面白くない (ああ疲れた)。 とにかく、これでうまく動いてくれると思う。それに、どっちにしても もう BIOS をいじくる必要は無いんだ。(最初のロード以外は。わかるよね) BIOS ルーチンはやたらとたくさんの不要なデータを欲しがるし、こいつは 全然 "おもしろい" ことじゃない。「本物の」プログラマならこうするさ。] ! Well, now's the time to actually move into protected mode. To make ! things as simple as possible, we do no register set-up or anything, ! we let the GNU-compiled 32-bit programs do that. We just jump to ! absolute address 0x1000 (or the loader supplied one), ! in 32-bit protected mode. [さて、今度こそ本当にプロテクトモードへ移行する時だ。できるだけ物事を 単純に保つために、レジスター設定とかそういったものは何もしない。 ここでは GNU のツールでコンパイルされた 32-bit のプログラムにそれを やらせるんだ。ただ単に 32-bit のプロテクトモードで絶対アドレス 0x1000 (ローダーが指定した場所) へジャンプするだけだよ。] ! Note that the short jump isn't strictly needed, although there are ! reasons why it might be a good idea. It won't hurt in any case. [ここで short jump がどうしても必要ってわけじゃないことに注意。 ただ、こうしておいたほうがいい理由もいくつかあるんだ。それに こうしたからって何か問題が起きるってこともないしね。]
どうやら Linus さんは最初に開発を始めた頃、このあたりの処理に
相当苦労したらしく、"Sadly IBM messed this up" とか
"and it isn't fun." また
"Well, that certainly wasn't fan :-(
."
なんてのもあります。一方で、自分の挙げた成果にはそれなりに愛着も
持っているようで、"This is how REAL programmers do it."
などと書いてあったりもします。
JF の資料に "Linux HISTORY" という文書がありますが、 その中にこんな一節があります。
> 1) カーネルを作っているときには、 だいたいどうやってデバッグしますか ?
使っているマシンと、作業の進み具合によります。 もっとシンプルなシステムならたいていセットアップはもっと簡単です。 プロテクトモードの 386 で私がやらなければならなかったことをかきます。
一番厄介な所は一番最初です。printf 等が使える最低限のシステムを 手に入れることができた後であってさえも、386 でのプロテクトモードへの 移行は楽しくないです。 386 のアーキテクチャを良く知らぬままに始めたのであればなおさらです。 この段階では、システムは死にたくなるほどリブートしまくります。 もし 386 がなにかがヘンだと気づいた日には、シャットダウンして リブートしてしまいます。何が悪いのかの証拠を残す暇もありません。
printf() もたいして役に立ちません。リブートすれば画面もきれいさっぱりです。 それから、VRAM も叩かなければだめです。 VRAM はセグメントが間違っていたりすると落っこちてくれます。 デバッガなんて考えるだけ無駄です。386 のプロテクトモードまで ついていくデバッガなんて聞いたことがありません。386 エミュレータや、 一部の重装備のマシンならなんとかなるかもしれませんが、大抵は駄目です。
私が使ったのは、ただの時間稼ぎのループでした。
die: jmp die
このようなものをここぞというところに入れます。止まってしまえば OK ですし、 リブートしてしまったら、すくなくともこの die ループの前が怪しいとわかります。 変わりにサウンドポートも利用できますが、私は PC のハードはいじったことが なかったので、全然使いませんでした。これ以外に方法がないわけではありません。 私はカーネルを書こうと思って始めたのではなく、ただ 386 のタスクスイッチ等に ついて知りたかっただけです。しかし、とにかくこうして書き始めました。 (91年の4月のことでした)
最低限のシステムが出来上がり、スクリーンを出力に使えるようになると、 少々楽になります。しかし、ここで割り込みを有効にしなければなりません。 ドカ〜ン。 いきなりリブートして、また最初の方法に逆戻り。全てがこの調子で、 およそ 2か月かけて、386 のまわりをまともに動くようにしました。 それからは、リブートしないようにと気を使いながら、同時に基本的なもの (ページング、タイマ割り込み、単純なタスクスイッチャ、セグメントのテスト) を作るということをしないで済むようになりました。
このあたりの話を読むと、setup.S のインタラプト関係の処理や プロテクトモードへの移行に関する処理にあるコメントの背景が 何となくわかるような気がしてきませんか ?
なお、setup.S の処理は最後にカーネル本体のアドレスへ ジャンプして終了します。
! NOTE: For high loaded big kernels we need a ! jmpi 0x100000,__KERNEL_CS [注意: high 領域にロードされた big カーネルの場合、ここで "jmpi 0x100000,__KERNEL_CS" を実行する必要がある。] ! but we yet haven't reloaded the CS register, so the default size ! of the target offset still is 16 bit. ! However, using an operant prefix (0x66), the CPU will properly ! take our 48 bit far pointer. (INTeL 80386 Programmer's Reference ! Manual, Mixing 16-bit and 32-bit code, page 16-6) [でもこの時点ではまだ CS レジスターを再ロードしていないから、 ターゲットオフセットのデフォルトサイズはまだ 16 bit なんだ。 ところが、オペラントプレフィックス (0x66) を使えば、CPU は うまいこと 48 bit の far ポインタを扱ってくれる。 (INTeL 80386 プログラマーズレファレンスマニュアル、 16-bit と 32-bit のコードの混用、ページ 16-6)] db 0x66,0xea ! prefix + jmpi-opcode code32: dd 0x1000 ! will be set to 0x100000 for big kernels dw __KERNEL_CS
さて arch/i386/boot/setup.S から処理を引き継いだ「カーネル本体」ですが、 実はまだその主要部分は圧縮された状態でメモリー中に置かれています。
実際にカーネルが動作を始める前に、まずこの圧縮されたカーネルを 復元しなければいけません。
これは、arch/i386/boot/compressed/head.S にある
/* * Do the decompression, and jump to the new kernel.. */ subl $16,%esp # place for structure on the stack pushl %esp # address of structure as first arg call SYMBOL_NAME(decompress_kernel) orl %eax,%eax jnz 3f xorl %ebx,%ebx ljmp $(__KERNEL_CS), $0x100000
によって実行されます。なおこの decompress_kernel は同じディレクトリ にある arch/i386/boot/compress/misc.c の中で
int decompress_kernel(struct moveparams *mv) { if (SCREEN_INFO.orig_video_mode == 7) { vidmem = (char *) 0xb0000; vidport = 0x3b4; } else { vidmem = (char *) 0xb8000; vidport = 0x3d4; } lines = SCREEN_INFO.orig_video_lines; cols = SCREEN_INFO.orig_video_cols; if (free_mem_ptr < 0x100000) setup_normal_output_buffer(); else setup_output_buffer_if_we_run_high(mv); makecrc(); puts("Uncompressing Linux... "); gunzip(); puts("Ok, booting the kernel.\n"); if (high_loaded) close_output_buffer_if_we_run_high(mv); return high_loaded; }
として定義されており、さらにこの中で使われている gunzip() に ついては lib/infalte.c の中で定義されています。
(これで起動時に "Uncompressing Linux... " という メッセージを出しているのが何処か、わかりましたね。)
さて、arch/i386/boot/compressed/head.S の冒頭に書かれている コメントを以下に引用してみましょう。
/* * linux/boot/head.S * * Copyright (C) 1991, 1992, 1993 Linus Torvalds */ /* * head.S contains the 32-bit startup code. *
おや ? ファイル名が違いますね。これは Linux カーネルが現在のように 多くの機種に移植されていなかった 1.x の頃以前のファイル名でしょう。 当時は現在のようにアーキテクチャに依存した部分が分離されていません でしたから。
さて、圧縮されていたカーネル本体も展開されました。次はこの中に ジャンプしていきます。arch/i386/boot/compressed/head.S の最後は 次のようになっています。
/* * Do the decompression, and jump to the new kernel.. */ subl $16,%esp # place for structure on the stack pushl %esp # address of structure as first arg call SYMBOL_NAME(decompress_kernel) orl %eax,%eax jnz 3f xorl %ebx,%ebx ljmp $(__KERNEL_CS), $0x100000 /* * We come here, if we were loaded high. * We need to move the move-in-place routine down to 0x1000 * and then start it with the buffer addresses in registers, * which we got from the stack. */ 3: movl $move_routine_start,%esi movl $0x1000,%edi movl $move_routine_end,%ecx subl %esi,%ecx cld rep movsb popl %esi # discard the address popl %esi # low_buffer_start popl %ecx # lcount popl %edx # high_buffer_start popl %eax # hcount movl $0x100000,%edi cli # make sure we don't get interrupted ljmp $(__KERNEL_CS), $0x1000 # and jump to the move routine /* * Routine (template) for moving the decompressed kernel in place, * if we were high loaded. This _must_ PIC-code ! */ move_routine_start: rep movsb movl %edx,%esi movl %eax,%ecx # NOTE: rep movsb won't move if %ecx == 0 rep movsb xorl %ebx,%ebx /* * Well, the kernel relies on %esp pointing into low mem, * with the decompressor loaded high this is no longer true, * so we set esp here. */ mov $0x90000,%esp ljmp $(__KERNEL_CS), $0x100000 move_routine_end:
"decompress_kernel" のすぐ後の "jnz" で "3:"へジャンプせずに、 そのまま"ljmp $(__KERNEL_CS), $0x100000" する場合 (zImage) と、いったん "3:" へジャンプして "move_routine_start:" と "move_routine_end:" の間で展開したカーネルの場所の移動を行なってから "ljmp $(__KERNEL_CS), $0x100000" する場合 (bzImage) が あります。
さて、展開されたカーネル本体で最初に実行されるのは、 arch/i386/kernel/head.S です。例によって冒頭のコメント。
/* * linux/arch/i386/head.S -- the 32-bit startup code. * * Copyright (C) 1991, 1992 Linus Torvalds * * Enhanced CPU detection and feature setting code by Mike Jagdis * and Martin Mares, November 1997. */
「32bit スタートアップ」「強化された CPU 検出と機能設定」といった 文が並んでいます。
これもコメントを追いかけてみましょう。
* References to members of the boot_cpu_data structure. * swapper_pg_dir is the main page directory, address 0x00101000 * Set segments to known values * New page tables may be in 4Mbyte page mode and may * be using the global pages. * * NOTE! We have to correct for the fact that we're * not yet offset PAGE_OFFSET.. * Setup paging (the tables are already set up, just switch them on) * Clear BSS first so that there are no surprises... * start system 32-bit setup. We need to re-do some of the things done * in 16-bit mode for the "real" operations. * Initialize eflags. Some BIOS's leave bits like NT set. This would * confuse the debugger if this code is traced. * XXX - best to initialize before switching to protected mode. * Copy bootup parameters out of the way. First 2kB of * _empty_zero_page is for boot parameters, second 2kB * is for the command line. /* check if it is 486 or 386. */ * XXX - this does a lot of unnecessary setup. Alignment checks don't * apply at our cpl of 0 and the stack ought to be aligned already, and * we don't need to preserve eflags.
どうやら、メモリー管理テーブルや CPU のフラッグ設定などを 行なっているようです。
この head.S は最後に start_kernel を実行します。
xorl %eax,%eax lldt %ax cld # gcc2 wants the direction flag cleared at all times call SYMBOL_NAME(start_kernel) L6: jmp L6 # main should never return here, but # just in case, we know what happens.
head.S から呼び出される start_kernel は init/main.c にあります。 このファイルの冒頭にあるコメントを引用します。
/* * linux/init/main.c * * Copyright (C) 1991, 1992 Linus Torvalds * * GK 2/5/95 - Changed to support mounting root fs via NFS * Added initrd & change_root: Werner Almesberger & Hans Lermen, Feb '96 * Moan early if gcc is old, avoiding bogus kernels - Paul Gortmaker, May '96 * Simplified starting of init: Michael A. Griffith <grif@acm.org> */
続いて、start_kernel の最初の部分です。
asmlinkage void __init start_kernel(void) { char * command_line; #ifdef __SMP__ static int boot_cpu = 1; /* "current" has been set up, we need to load it now */ if (!boot_cpu) initialize_secondary(); boot_cpu = 0; #endif /* * Interrupts are still disabled. Do necessary setups, then * enable them */ printk(linux_banner); setup_arch(&command_line, &memory_start, &memory_end); memory_start = paging_init(memory_start,memory_end); trap_init(); init_IRQ(); sched_init(); time_init(); parse_options(command_line);
"linux_banner" というのは init/version.c に定義が あります。例えば (これは私が今使っている 2.0.36 の例ですが)
Linux version 2.0.36 (root@pika) (gcc version 2.7.2.3) #1 Wed Feb 10 21:57:36 JST 1999
といった感じのものです。起動時にこの「バナー」を出しているのは init/main.c の start_kernel だったわけですね。
次に "setup_arch" ですが、これは arch/i386/kernel/setup.c で 定義されています。内容は時間の都合で省略しますが、起動時に BIOS から 収集した情報を (他のデバイスドライバーからアクセスできるよう) あらためて 整理しています。"paging_init" は arch/i386/mm/setup.c に あります。メモリーページテーブルの設定を行ないます。 "trap_init" は arch/i386/kernel/traps.c にあって IDT テーブル の初期化を実行します。"init_IRQ" は arch/i386/kernel/irq.c の中にあります。IRQ 関係の設定を行なうものです。"shced_init" は kernel/sched.c の中で定義されています。 "time_init" は kernel/time.c に、そして parse_optinos は start_kernel と同じく init/main.c の中で定義されています。
このあと、いくつも初期化ルーチンを実行した後
kernel_thread(init, NULL, CLONE_FS | CLONE_FILES | CLONE_SIGHAND);
に到達します。ここで引数として指定されている "init" は 同じ init/main.c の中で定義されているものです。
static int init(void * unused) { lock_kernel(); do_basic_setup(); /* * Ok, we have completed the initial bootup, and * we're essentially up and running. Get rid of the * initmem segments and start the user-mode stuff.. */ free_initmem(); unlock_kernel(); if (open("/dev/console", O_RDWR, 0) < 0) printk("Warning: unable to open an initial console.\n"); (void) dup(0); (void) dup(0);
この "init" の中で呼び出されている lock_kernel は SMP の機械にのみ関係するものです。
次の do_basic_setup は init/main.c の中で定義されています。 この関数の中にはバスの設定やネットワークソケットの初期化、 ファイルシステムの認識、ルートパーティションのマウントなど 「デバイスドライバの初期化」というタイトルにふさわしい内容が 含まれているので、本来ならこの中身をそれぞれ調べてみたいところ なのですが、今回は時間が無くなってしまったので省略します。
もし興味があれば、是非自分で調べてみて下さい。
上の項目で一部紹介した init/main.c で定義されている、カーネル内の "init" 関数ですが、最後はこんな風になっています。
/* * We try each of these until one succeeds. * * The Bourne shell can be used instead of init if we are * trying to recover a really broken machine. */ if (execute_command) execve(execute_command,argv_init,envp_init); execve("/sbin/init",argv_init,envp_init); execve("/etc/init",argv_init,envp_init); execve("/bin/init",argv_init,envp_init); execve("/bin/sh",argv_init,envp_init); panic("No init found. Try passing init= option to kernel.");
ここで if (execute_command)
から始まる 2 行は、起動時の
カーネルオプション init=
によって最初に起動するプログラムを
指定した場合のためのものです。オプションを指定しない場合はそのまま
通過します。
次の execve("/sbin/init",...)
は /sbin/init
を実行して、もし問題が無ければそのまま戻ってこない、という命令です。
通常のシステム起動ではここで制御が /sbin/init
に移行して、
これ以後のコードは使われません。
もし何らかの理由で /sbin/init
を実行できない場合は、
同様の方法で /etc/init
、/bin/init
、そして
/bin/sh
の実行を試します。もし先に試したものがうまく
実行できれば、そのまま処理を渡してしまうのでそれ以降のコードは
実行されません。
最終的に、/bin/sh
も含めて、どうしても処理を渡すことが
できない場合は、最後の panic()
でエラーメッセージを
表示して停止します。
つまり、ここが "man 8 init" に記載されている 「カーネルブートの最後のステップ」というわけです。
カーネルがロードされた時に、どのファイルに入っているコードが 実行されるのか、という点は Makefile を調べると書いてあります。
まずトップディレクトリの Makefile には、次の記述があります。
include arch/$(ARCH)/Makefile vmlinux: $(CONFIGURATION) init/main.o init/version.o linuxsubdirs $(LD) $(LINKFLAGS) $(HEAD) init/main.o init/version.o \ --start-group \ $(CORE_FILES) \ $(FILESYSTEMS) \ $(NETWORKS) \ $(DRIVERS) \ $(LIBS) \ --end-group \ -o vmlinux $(NM) vmlinux | grep -v '\(compiled\)\|\(\.o$$\)\|\( [aU] \)\|\(\.\.ng$$\)\|\(LASH[RL]DI\)' | sort > System.map
今回の話では i386 を前提としているので "arch/i386/Makefile" を 参照してみると、
HEAD := arch/i386/kernel/head.o arch/i386/kernel/init_task.o zImage: vmlinux @$(MAKEBOOT) zImage
という記述が見つかります。これと上記のトップディレクトリの Makefile とから、トップディレクトリの vmlinux の先頭が "arch/i386/kernel/head.o" であることがわかります。 一方、トップディレクトリの Makefile には、次の記述もあります。
boot: vmlinux @$(MAKE) -C arch/$(ARCH)/boot
この記述から、ブートイメージの作成について知りたければ、 "arch/i386/boot" ディレクトリの Makefile を 調べてみると良さそうだ、ということがわかります。
そこで "arch/i386/boot" ディレクトリの Makefile を 見てみると、次の記述があります。
zImage: $(CONFIGURE) bootsect setup compressed/vmlinux tools/build $(OBJCOPY) compressed/vmlinux compressed/vmlinux.out tools/build bootsect setup compressed/vmlinux.out $(ROOT_DEV) > zImage compressed/vmlinux: $(TOPDIR)/vmlinux @$(MAKE) -C compressed vmlinux setup: setup.o $(LD86) -s -o $@ $< setup.o: setup.s $(AS86) -o $@ $< setup.s: setup.S video.S Makefile $(BOOT_INCL) $(TOPDIR)/include/linux/version.h $(CPP) -traditional $(SVGA_MODE) $(RAMDISK) $< -o $@ bootsect: bootsect.o $(LD86) -s -o $@ $< bootsect.o: bootsect.s $(AS86) -o $@ $< bootsect.s: bootsect.S Makefile $(BOOT_INCL) $(CPP) -traditional $(SVGA_MODE) $(RAMDISK) $< -o $@
ここから、bootsect.S から bootsect.s が作成され、さらに bootsect.o を 経由して bootsect になること、同じく setup.S と vide.S から setup.s が 作成され、さらに setup.o を経由して setup になることがわかります。 またこうしてできた bootsect と setup がそれぞれ zImage ファイルの 先頭と 2 番目に該当することもわかります。(従って、フロッピーから カーネルをブートすると最初に bootsect.S のコードが実行されるわけです)
そしてさらに、arch/i386/boot/compressed/Makefile を見ると 以下の記述があります。
HEAD = head.o SYSTEM = $(TOPDIR)/vmlinux OBJECTS = $(HEAD) misc.o vmlinux: piggy.o $(OBJECTS) $(LD) $(ZLINKFLAGS) -o vmlinux $(OBJECTS) piggy.o head.o: head.S $(TOPDIR)/include/linux/tasks.h $(CC) $(AFLAGS) -traditional -c head.S piggy.o: $(SYSTEM) tmppiggy=_tmp_$$$$piggy; \ rm -f $$tmppiggy $$tmppiggy.gz $$tmppiggy.lnk; \ $(OBJCOPY) $(SYSTEM) $$tmppiggy; \ gzip -f -9 < $$tmppiggy > $$tmppiggy.gz; \ echo "SECTIONS { .data : { input_len = .; LONG(input_data_end - input_data) input_data = .; *(.data) input_data_end = .; }}" > $$tmppiggy.lnk; \ $(LD) -m elf_i386 -r -o piggy.o -b binary $$tmppiggy.gz -b elf32-i386 -T $$tmppiggy.lnk; \ rm -f $$tmppiggy $$tmppiggy.gz $$tmppiggy.lnk
これから、compressed/vmlinux の先頭は head.S のコードであることが わかります。
以上のことをまとめると
ということになります。
とりあえずなんとかまとめてみましたが、なにぶん、私もまだまだ 知らないことがたくさんあります。ここに書いた中にも間違いが あるかもしれません。 もし改良のためのアドバイスをお持ちでしたら、是非教えてください。 よろしくお願いします。
最初にこの文書をリリースするまでに、NLUG や JF のメンバーの方々から 多くの有益な意見を頂きました。ありがとうございます。また、日頃お世話に なっている fj.os.linux や Nifty FUNIX の方々にもこの場を借りてお礼を 申し上げます。
最初にリリースした後で、おくじさんから BIOS によるブートストラップの 動作について御指摘を頂きました。 またくりこさんから "the longest-living linux bug" の説明 について有益な御意見を頂きました。どうもありがとうございます。
野本さんから i386 のアセンブラについて参考になる情報を頂きました。 どうもありがとうございます。
copyrighted (c) 1999 Taketoshi Sano
この文書は GNU パブリックライセンス (GPL) バージョン 2 かそれ以降 の条件、あるいは標準的な Linux ドキュメントプロジェクト (LDP) の条件に 基づいた配布ならば自由にしていただいてかまいません。これらのライセンス はこのドキュメントが入手できるようなサイトから入手できます。LDP の条件は (翻訳をのぞく) いかなる修正も許可していません。修正されたバージョンは GPL の基でのみ配布されるものとすることが可能です。