レンタルサーバ + Webシステム開発 = E-business

■レンタルサーバご利用参考資料
サーバご利用の参考にJF Project によるJF (Japanese FAQ)を掲載しています。

Linux JF(Japanese FAQ)Project.
JF は, Linux に関する解説文書・FAQ などを作成・収集・配布するプロジェクトです.

グリーンネット・トップページへ戻る


一覧に戻る
  コメントから読む Linux カーネル
  Sano Taketoshi 
  $Date: 2000/06/27 13:57:52 $

  NLUG (名古屋 Linux ユーザーグループ) 第 3 回勉強会のために作成した資料
  です。 今回読むのは Linux カーネル 2.2.5 です。主に PC/AT (i386) 上
  で、電源 ON のあと、カーネルのロード、 ブートアップから /sbin/init が
  実行されるまでをソースコード中のコメントを頼りに追いかけていきます。
  ______________________________________________________________________

  目次

  1. はじめに
     1.1 「カーネル」とは
     1.2 Linux カーネル
     1.3 コード作成に貢献した人々
     1.4 担当者一覧

  2. システムの起動:概要
     2.1 カーネルのロード
     2.2 BIOS 情報の取得
     2.3 カーネル本体の展開
     2.4 デバイスドライバーの設定
     2.5 init の起動
     2.6 システム起動

  3. カーネルのロード
     3.1 bootsect.S - 冒頭のコメント
     3.2 bootsect.S - パラメータ定義
     3.3 bootsect.S - 実際の動作

  4. BIOS 情報の取得
  5. カーネル本体の展開
  6. デバイスドライバーの設定
  7. init の起動
  8. 番外: Makefile について
  9. 終わりに
     9.1 お願い
     9.2 謝辞
     9.3 この文書の配布について

  ______________________________________________________________________

  1.  はじめに

  1.1.  「カーネル」とは

  たぶん、一度くらいどこかで目にしたことがあるかもしれませんが「Linux」
  という名前は当時フィンランドの大学生であった Linus Torvalds さんが自分
  の作った「カーネル」に、自分の名前から一部を取って付けた名前です。

  さて、そこで問題。「カーネル」って何でしょう ?

  「カーネル」とは、システム内で動作中の各プログラムから出される要求に応
  じてメモリーやディスクなどのハードウェア資源の管理や、 CPU 時間の配分
  などを行なう OS の中枢部分です。

  例えば "top" コマンドを実行してしばらく眺めていると、リストされている
  プロセスの順番が時々入れ替わることがあるのに気づくでしょう。この「順番
  の入れ替え」はカーネルのタスク管理 (スケジューリング) によるものです。
  また、プリンタを接続して印刷できるように設定する際、

       cat test.pr >/dev/lp0

  などの操作によってパラレルポートへの信号出力をテストした経験はありませ
  んか ?  この「 /dev/lp0 に出力したデータはそのままパラレルポートに出力
  される」という動作はカーネル内の lp ドライバによって実現されています。

  1.2.  Linux カーネル

  以下、この文書では 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 つのファイルの内容について紹介したいと思います。

  1.3.  コード作成に貢献した人々

  "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 さんが自分で「こう書いてくれ」と依頼され
  たんじゃないかと想像しています。

  上記の「最も長く生きのびたバグの著者」という文からは、通常ならあまり公
  表したくないと思うような過去の「バグ」のことでさえ、「笑い」の対象にし
  てしまおうというバイタリティというか、ユーモアのセンスを感じます。「ど
  うだい ? この俺がかの有名な『最も長く解決されずに残ったバグ』の著者な
  んだぜ ! スゴイだろう !!」みたいな感じですね。

  1.4.  担当者一覧

  さて、そろそろ次に移りましょう。今度は "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 さんを取り巻く状況の変化を物語っているようで
  す。

  2.  システムの起動:概要

  ここではシステムの電源 ON から /sbin/init が起動されるまでの流れを大ま
  かに説明します。

  2.1.  カーネルのロード

  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
  に制御を移行します。

  2.2.  BIOS 情報の取得

  arch/i386/boot/setup.S はメモリーサイズやディスク情報、またコンソール
  用ビデオカードの情報や APM BIOS のチェックなど、システムに関するいろい
  ろな情報を BIOS から取得して、後でデバイスドライバーを初期化する際に使
  用できるよう、メモリー上に保存します。

  さらに LILO などのブートローダーを使用した場合には、起動時にキーボード
  から入力されたカーネルオプションを後で参照できるようにメモリー上の特定
  の場所にコピーするのも setup.S の仕事です。

  setup.S は BIOS 情報の取得を完了すると、CPU のモードを起動時の 16bit
  モードから 32bit (protected) モードに切り替えて、
  arch/i386/boot/compressed/head.S に処理を移行します。

  2.3.  カーネル本体の展開

  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 は新しくメモリー上
  に現われた本来のカーネルコードへと処理を移行します。

  2.4.  デバイスドライバーの設定

  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 系になって移植性を高めるためか、このあたりの処理がさらに細
  分化されコードの構成が変更されたようです。

  2.5.  init の起動

  init/main.c で定義されている init() は do_basic_setup() から処理が戻っ
  てくると、起動時にのみ必要とされたメモリー領域の開放とコンソール出力の
  オープンを実行し、

       /sbin/init, /etc/init, /bin/init, /bin/sh

  を順番に試して、最初に見つかった実行可能なものへと処理を移行します。

  2.6.  システム起動

  これ以降の処理は、/etc/inittab での設定や /etc/rc.d または /etc/init.d
  と /etc/rc?.d による起動スクリプトの設定などによって動作が決まります。
  これについては、たぶん既にご承知のかたも多いのではないでしょうか。

  「カーネルの起動」についての概要はこれで終わりです。次の節から、上に述
  べた各段階で使用されるコードについてコメントを頼りに調べていくことにし
  ます。

  3.  カーネルのロード

  上記の「概要」で説明したように、フロッピーからの起動の場合、カーネル内
  で最初にロードされるコードは arch/i386/boot/bootsect.S です。

  まずは、このファイルから眺めてみることにしましょう。

  3.1.  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 の希望から実際のコードがやや離れてしまっ
  たように感じられます。

  3.2.  bootsect.S - パラメータ定義

  そろそろ、コードの中身に入ってみましょう。

  #include  /* for CONFIG_ROOT_RDONLY */
  #include 

  .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  は include/linux/config.h の定義を
  使う、ということです。で、このヘッダーファイルの中身はこうなってます。

  #ifndef _LINUX_CONFIG_H
  #define _LINUX_CONFIG_H

  #include 

  #endif

  これだけ。これは include/linux/autoconf.h の定義を使いなさい、というこ
  とです。で、このファイルを探してみると、ありません。

  実はこの autoconf.h は make config / menuconfig / xconfig などを実行し
  て始めて作成されるファイルであって、単にアーカイブを展開しただけでは存
  在しないものです。

  今回は時間の都合で、このファイルを作られる手順を追いかけるのは省略し、
  次の #include  を調べてみることにします。

  こちらは 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 で定義されています。

  この部分の下にも、いくつか定数の定義が続いていますが、省略して次に進み
  ます。

  3.3.  bootsect.S - 実際の動作

  次の ".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 のコードにジャンプします。

  4.  BIOS 情報の取得

  さて、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
  ! , 
  !
  ! Video handling moved to video.S by Martin Mares, March 1996
  ! 
  !
  ! 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

  5.  カーネル本体の展開

  さて 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) があります。

  6.  デバイスドライバーの設定

  さて、展開されたカーネル本体で最初に実行されるのは、
  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 
   */

  続いて、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 の中で定義されています。この関数の
  中にはバスの設定やネットワークソケットの初期化、ファイルシステムの認
  識、ルートパーティションのマウントなど「デバイスドライバの初期化」とい
  うタイトルにふさわしい内容が含まれているので、本来ならこの中身をそれぞ
  れ調べてみたいところなのですが、今回は時間が無くなってしまったので省略
  します。

  もし興味があれば、是非自分で調べてみて下さい。

  7.  init の起動

  上の項目で一部紹介した 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" に記載されている「カーネルブートの最後のス
  テップ」というわけです。

  8.  番外: Makefile について

  カーネルがロードされた時に、どのファイルに入っているコードが実行される
  のか、という点は 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 のコードであることがわか
  ります。

  以上のことをまとめると

  1. arch/i386/boot/bootsect.S (フロッピーブート時、最初に実行)

  2. arch/i386/boot/setup.S (+ video.S)

  3. arch/i386/boot/compressed/head.S (+ misc.c)

  4. arch/i386/kernel/head.S

  ということになります。

  9.  終わりに

  9.1.  お願い

  とりあえずなんとかまとめてみましたが、なにぶん、私もまだまだ知らないこ
  とがたくさんあります。ここに書いた中にも間違いがあるかもしれません。も
  し改良のためのアドバイスをお持ちでしたら、是非教えてください。よろしく
  お願いします。

  9.2.  謝辞

  最初にこの文書をリリースするまでに、NLUG や JF のメンバーの方々から多
  くの有益な意見を頂きました。ありがとうございます。また、日頃お世話に
  なっている fj.os.linux や Nifty FUNIX の方々にもこの場を借りてお礼を申
  し上げます。

  最初にリリースした後で、おくじさんから BIOS によるブートストラップの動
  作について御指摘を頂きました。またくりこさんから "the longest-living
  linux bug" の説明について有益な御意見を頂きました。どうもありがとうご
  ざいます。

  野本さんから i386 のアセンブラについて参考になる情報を頂きました。どう
  もありがとうございます。

  9.3.  この文書の配布について

   copyrighted (c) 1999 Taketoshi Sano

  この文書は GNU パブリックライセンス (GPL) バージョン 2 かそれ以降の条
  件、あるいは標準的な Linux ドキュメントプロジェクト (LDP) の条件に基づ
  いた配布ならば自由にしていただいてかまいません。これらのライセンスはこ
  のドキュメントが入手できるようなサイトから入手できます。LDP の条件は
  (翻訳をのぞく) いかなる修正も許可していません。修正されたバージョンは
  GPL の基でのみ配布されるものとすることが可能です。

一覧に戻る
グリーンネット・トップページへ戻る

http://www.green.ne.jp/