chroot("/home/hibari")

備忘録とかに使えそうなノート

linuxカーネル読書がしたい(ブート編2)

前回に引き続きlinux読書を続けていく.header.Sからmainにjmpするので,次はmain.cの読書. 

 

main.c(リアルモードでのカーネルコードモジュール)

これをただただ上から読むと,

↓このページはここから

"0ページ目"にブートヘッダをコピー

early-bootコンソールの初期化(コンソールの準備)

ヒープの初期化

CPUの検証

↑ここまで

BIOSをどのCPUモードで使うかの設定

・メモリレイアウトの検出

・キーボードの初期化

Intel SpeedStep (IST) 情報の照会

・(もし定義されていれば)APM情報の照会

・(もし定義されていれば)EDD情報の照会

・ビデオモードの設定

・プロテクトモードへの移行

をしているようである.つまりリアルモードでする仕事はこれらであるということ.

 

copy_boot_params()

コメントを直訳すると,

"0ページ目にブートヘッダをコピーする."

"ブートパラメータブロックにヘッダをコピーする.これは古いスタイルのコマンドラインプロトコルを壊すので,代わりに新しいスタイルのコマンドラインポインタを入力して調整している."

 後々boot_params,setup_headerは多く出てくるのでここで定義を載せておく.bootparams.hより.

113 /* The so-called "zeropage" */
114 struct boot_params {
115     struct screen_info screen_info;         /* 0x000 */
116     struct apm_bios_info apm_bios_info;     /* 0x040 */
117     __u8  _pad2[4];                 /* 0x054 */
118     __u64  tboot_addr;              /* 0x058 */
119     struct ist_info ist_info;           /* 0x060 */
120     __u8  _pad3[16];                /* 0x070 */
121     __u8  hd0_info[16]; /* obsolete! */     /* 0x080 */
122     __u8  hd1_info[16]; /* obsolete! */     /* 0x090 */
123     struct sys_desc_table sys_desc_table; /* obsolete! */   /* 0x0a0 */
124     struct olpc_ofw_header olpc_ofw_header;     /* 0x0b0 */
125     __u32 ext_ramdisk_image;            /* 0x0c0 */
126     __u32 ext_ramdisk_size;             /* 0x0c4 */
127     __u32 ext_cmd_line_ptr;             /* 0x0c8 */
128     __u8  _pad4[116];               /* 0x0cc */
129     struct edid_info edid_info;         /* 0x140 */
130     struct efi_info efi_info;           /* 0x1c0 */
131     __u32 alt_mem_k;                /* 0x1e0 */
132     __u32 scratch;      /* Scratch field! */    /* 0x1e4 */
133     __u8  e820_entries;             /* 0x1e8 */
134     __u8  eddbuf_entries;               /* 0x1e9 */
135     __u8  edd_mbr_sig_buf_entries;          /* 0x1ea */
136     __u8  kbd_status;               /* 0x1eb */
137     __u8  _pad5[3];                 /* 0x1ec */
138     __u8  sentinel;                 /* 0x1ef */
139     __u8  _pad6[1];                 /* 0x1f0 */
140     struct setup_header hdr;    /* setup header */  /* 0x1f1 */
141     __u8  _pad7[0x290-0x1f1-sizeof(struct setup_header)];
142     __u32 edd_mbr_sig_buffer[EDD_MBR_SIG_MAX];  /* 0x290 */
143     struct e820entry e820_map[E820MAX];     /* 0x2d0 */
144     __u8  _pad8[48];                /* 0xcd0 */
145     struct edd_info eddbuf[EDDMAXNR];       /* 0xd00 */
146     __u8  _pad9[276];               /* 0xeec */
147 } __attribute__((packed));

 

 48 struct setup_header {
 49     __u8    setup_sects;
 50     __u16   root_flags;
 51     __u32   syssize;
 52     __u16   ram_size;
 53     __u16   vid_mode;
 54     __u16   root_dev;
 55     __u16   boot_flag;
 56     __u16   jump;
 57     __u32   header;
 58     __u16   version;
 59     __u32   realmode_swtch;
 60     __u16   start_sys;
 61     __u16   kernel_version;
 62     __u8    type_of_loader;
 63     __u8    loadflags;
 64     __u16   setup_move_size;
 65     __u32   code32_start;
 66     __u32   ramdisk_image;
 67     __u32   ramdisk_size;
 68     __u32   bootsect_kludge;
 69     __u16   heap_end_ptr;
 70     __u8    ext_loader_ver;
 71     __u8    ext_loader_type;
 72     __u32   cmd_line_ptr;
 73     __u32   initrd_addr_max;
 74     __u32   kernel_alignment;
 75     __u8    relocatable_kernel;
 76     __u8    min_alignment;
 77     __u16   xloadflags;
 78     __u32   cmdline_size;
 79     __u32   hardware_subarch;
 80     __u64   hardware_subarch_data;
 81     __u32   payload_offset;
 82     __u32   payload_length;
 83     __u64   setup_data;
 84     __u64   pref_address;
 85     __u32   init_size;
 86     __u32   handover_offset;
 87 } __attribute__((packed));

  

memcpyでヘッダをブートパラメータのヘッダにコピーしている.

 

以下は古いスタイルのコマンドラインプロトコルの時に実行される.古いカーネルがコピーした0x9000...までの領域にコマンドラインが含まれているか調べて,コマンドラインのセグメントを設定.

→含まれている:cmdline_seg = ds();

→含まれてない:cmdline_seg = 0x9000;

(ds()はdsレジスタから変数に返す関数.boot.hにあり.)

コマンドラインのポインタを設定.

 

console_init()

void console_init(void)
{
	parse_earlyprintk();

	if (!early_serial_base)
		parse_console_uart8250();
}

early_serial_console.cより

これだけではなんにもわからないのでparse_earlyprintk()の中身から見ていく.

最初の分岐で"cmdline_find_option("earlyprintk", arg, sizeof arg) > 0"がある.これはoption=argumentを発見するための関数.argumentの長さを返す.

cmdline_find_option

ブートパラメータヘッダのコマンドラインポインタを取得.もしこのアドレスが0x100000以上だとアクセス不可なので-1を返す

この中では4つの状態がある

st_wordstart:ワードの始まりか,空白の後

st_wordcmp:ワードの比較中

st_wordskip:ワード不一致.スキップ

st_bufcpy:バッファにコピー中 

whileで1文字ずつ進みつつ状態を見て行く

・st_wordstart

空白でなければ,状態をst_wordcmpにしてopptrにoption("earlyprintk"のポインタ)を代入

・st_wordcmp

→ c == '='かつ*opptrがNULLでなければ,バッファのポインタを設定して状態をst_bufcpyにする.

→ 空白であれば,状態をst_wordstartにする

c != *opptrであれば,状態をst_wordskipにする

・st_wordskip

空白であれば,状態をst_wordstartにする

・st_bufcpy

空白であれば,状態をst_wordstartにする

それ以外であればbufにwordをコピーしていく

 

 

今回"earlyprintk"がoptionなので,上の関数を通じてearlyprintk=(文字列)の文字列部分がarg[32]にコピーされる.

arg == serialだったらひとまずportをDEFAULT_SERIAL_PORTとする.argには以下のようなものが来ているはずである.

 "serial,0x3f8,115200"

 "serial,ttyS0,115200"

 "ttyS0,115200"

(serial),port,baudというフォーマット

・"serial,0x"であれば,0x移行の数字をportとする.ただし0だったら0x3f8(デフォルト値)

・"serial,ttyS"または"ttyS"の時,ttyS0ならportを0x3f8とし,ttyS1ならportを0x2f8とする.

そのあとの数字をbaudとするが,0なら9600(デフォルト値).*1

port != 0なら,early_serial_init()

 

early_serial_init()
#define TXR             0       /*  Transmit register (WRITE) */
#define RXR             0       /*  Receive register  (READ)  */
#define IER             1       /*  Interrupt Enable          */
#define IIR             2       /*  Interrupt ID              */
#define FCR             2       /*  FIFO control              */
#define LCR             3       /*  Line control              */
#define MCR             4       /*  Modem control             */
#define LSR             5       /*  Line Status               */
#define MSR             6       /*  Modem Status              */
#define DLL             0       /*  Divisor Latch Low         */
#define DLH             1       /*  Divisor latch High        */

static void early_serial_init(int port, int baud)
{
	unsigned char c;
	unsigned divisor;

	outb(0x3, port + LCR);	/* 8n1 */
	outb(0, port + IER);	/* no interrupt */
	outb(0, port + FCR);	/* no fifo */
	outb(0x3, port + MCR);	/* DTR + RTS */

	divisor	= 115200 / baud;
	c = inb(port + LCR);
	outb(c | DLAB, port + LCR);
	outb(divisor & 0xff, port + DLL);
	outb((divisor >> 8) & 0xff, port + DLH);
	outb(c & ~DLAB, port + LCR);

	early_serial_base = port;
}

outb(u8 v, u16 port)はportからvを出力する基本I/O.色々入出力して初期化している.

 

init_heap()

ヒープの初期化をする.ヒープ領域が使えない時はWARNINGが出力される.

boot_params.hdr.loadflagsからヒープ領域が使えるなら,

stack_end = %esp-STACKSIZE(上に伸ばすイメージ)

headend = (size_t)boot_params.hdr.heap_end_ptr + 0x200

この時,heap_end > stack_endとなっているとスタック領域とヒープ領域とが被っている領域があることになるため,heap_end = stack_endとする.

 

validate_cpu

cpu.cより

カーネルが要求しているレベルよりもCPUレベルが低い時にはvalidationに失敗したことになる.まずはcheck_cpu()にかける.

check_cpu

cpucheck.cより

まずはcpuflags.hからここで使用されているcpu構造体を載せる

struct cpu_features {
	int level;		/* Family, or 64 for x86-64 */
	int family;		/* Family, always */
	int model;
	u32 flags[NCAPINTS];
};

デフォルトではcpu.levelは3とする.

if ( has_eflag(X86_EFLAGS_AC) ) cpu.level = 4;とあったのでX86_EFLAGS_ACの定義を参照する.

#define X86_EFLAGS_AC_BIT       18 /* Alignment Check/Access Control */
#define X86_EFLAGS_AC           _BITUL(X86_EFLAGS_AC_BIT)

include/uapi/asm/processor-flags.hより

EFLAGSの下から18bit目(0bit目もある)のACフラグがセットされていればレベル4になる.ACフラグはメモリチェックの時にアライメントチェックが有効であればセットされる.

次にcpuフラグを入手する(get_cpuflags())

get_cpuflags

・FPUの有無をチェック

・EFLAGSのIDフラグのチェック(なければこのままget_cpuflagsを抜ける)

→ あればCPUID命令が使用できる.

eaxに0を指定してCPUID命令でベンダ情報を入れる.

↑でeaxに返って来た値が0x1以上0xffff以下の時,eaxに1を指定してCPUID命令で機能フラグを入手しcpu.flags[0], cpu.flags[4]に代入される.この時eaxにはモデルやファミリーを示す値(プロセッサ・シグネチャ)が代入される.この時cpu構造体に以下のように代入される

11:8の4bit:cpu.levelとfamily

7:3の4bit:cpu.model [0:3]

(cpu.modelが6以上の時)19:16の4bit:cpu.model [7:4]

 次にeaxに0x80000000を指定してeaxに返ってくる最大値を取得.

eaxに返って来た値が0x80000001以上0x8000ffff以下の時,eaxに0x80000001を指定してCPUID命令で拡張機能フラグを入手しcpu.flags[1], cpu.flags[6]に代入される.

 

===== get_cpuflags ここまで=====

CPUがLong Modeに対応していたらcpu.level = 64(X86_FEATURE_LMはcpufeatures.hに定義されている)

得られたフラグ群から各種チェックしていきerrに代入,その後足りないものはRDMSRとWRMSR命令で補う努力をしてみる.(例えばAMDでSSE+SSE2のフラグが立っていなかった時にMSRの該当部分をセットしてみている)

MSRはIntelAMDx86プロセッサー固有の情報を格納した特殊なレジスタ

errが0でなければIntel Xeon PhiのKnights Landingに関するチェックを行っている.ここではKnights Landingで64bitモード,もしくはx86のPAEを使用していない場合はエラーが起こるようになっているようだ.puts()にはプロセッサのエラッタによるものだと記載されている.

最終的なcpu.levelやreq_levelをポインタに代入してから,CPUレベルが要求レベルに足りているかの判定を返り値に指定する.

 

===== check_cpu ここまで =====

 

このあとでCPUID命令が使用できない場合でCONFIG_M486が使用できるなら,カーネルをそのCPUで走らせるために設定を変えるようにするアラートが出る.

このあとでerr_flagsが0でなければ機能が足りてないとして-1を返すようになっている.また先述のKnights Landingのチェックをここでも行っている.

ここまでくぐり抜けて初めてreturn 0となる.

===== validate_cpu ここまで =====

 

 

冗長な記述が散見されてしまったけれど,やはりというか慣れていないので読むのはなかなかしんどい.長文となってしまったので今回はここまでにする.

*1:baudはデジタルデータをアナログデータに変換し,アナログ回線でシリアル転送する際に用いられる単位