カーネル探検(start_kernel以前)

# 前回
[環境構築からstart_kernel()を表示](http://qiita.com/saikoro-steak/items/6ab4d80f8f6b213eaf3f)

# 今回
やはりstart_kernel()より以前の処理も自分が気になる箇所だけをピックアップしてみた！

# 前提
* プロセス識別子 0 は swapper。後にアイドル・タスクになるやつ。
    - initを立ち上げるまでのプロセスなので、要するにブート時の初期化とかしているコンテキストそのもののこと
* プロセス識別子 1 は init

# 起動シーケンス概要
## ブートローダ
grub1を使用

```bash
$ grub --version
grub (GNU GRUB 0.97)
```
* grub1=0.9系
* grub2=1.9系

## 起動
* 電源on
↓

* BIOS起動
↓

* BiosがMBRを読み出し(stage1がメモリに展開)
↓

* ブートローダ起動(stage1)
↓

* セクタ2~64にあるstage1.5を起動
↓

* /boot/grub/stage2(stage2ブートローダ)を起動
↓

* カーネル起動
↓

* initramfs
↓

* /etc/inittab
↓

* RCスクリプト
* Upstart
* systemd


# 起動シーケンス概要詳細(電源ONからstart_kernelまで)
* 電源ON
↓

* BIOS起動
    - **0x07c00**にMBRのstage1をロード.stage1起動
    - 16bitのリアルモード(.code 16)

↓

* ブートローダ起動(stage1)
↓

* セクタ2~64にあるstage1.5を起動
    - MBRの直後から最初のパーティション手前までにある先頭30KB以内に位置するローダ起動(stage1.5起動)
    - Stage 1.5ローダのイメージはファイルシステム固有のドライバを含んでいる.stage2はファイルシステムの中にあるので

↓

* /boot/grub/stage2(stage2ブートローダ)を起動
    * /boot/grub/menu.lst(grub.conf)
    * memu.lst は grub.conf へのシンボリックリンク
    * centOSなどでもgrub.confを使用しますが、ディストリビューションによってはmemu.lstが使用されます。ディストリビューション間の互換性のためシンボリックリンクの設定が行われているようです。

```bash
$ ll /boot/grub/menu.lst
lrwxrwxrwx. 1 root root 11 Sep 28  2016 /boot/grub/menu.lst -> ./grub.conf
```

↓

* stage2
    - リアルモードから32bitプロテクトモードへ(.code 32)
    - アセンブラには`.code 32`の記述がある。バイナリが32ビット用に生成される。

↓

* arch/x86/boot/compressed/bead_64S/の解凍コード実行
    - 0x1000000に圧縮されたままのカーネル(vmlinuz)をロード&解凍(解凍先も0x1000000)
    - **0x1000000**は物理アドレス

↓

* 解凍コードの中で64bitモード移行

↓

* jmp \*%rbp(カーネルの**エントリポイントにジャンプ0x1000000**)
    - **0x1000000**はカーネルヘッダに書かれている。
    - 下はvmlinuzをヘキサダンプした結果。0x258からがカーネルエントリポイントの0x1000000になってる。ブートローダーはここから値をとってきてる。
![カーネルヘッダエントリポイント.png](https://qiita-image-store.s3.amazonaws.com/0/91729/f0380d0f-ca03-1b66-910b-a8526543d643.png)

↓

### **startup_64()** 0x1000000
* Linuxのエントリポイント
* 仮のプロテクトモードの環境を整え直す
    - ページテーブルの再設定。カーネル用のマッピング？？
    - CPUの機能設定(cpuid)
    - GDTの再設定
    - スタックの設定(swapper用。後にinitプロセス用)


 ```bash:カーネルの仮想アドレスへのマップ

$ cat /boot/System.map-2.6.32-642.13.2.el6.x86_64 | more
0000000000000000 A VDSO32_PRELINK
0000000000000000 D __per_cpu_start
0000000000000000 D per_cpu__irq_stack_union
0000000000000000 A xen_irq_disable_direct_reloc
0000000000000000 A xen_save_fl_direct_reloc
0000000000000040 A VDSO32_vsyscall_eh_frame_size
00000000000001e7 A kexec_control_code_size
00000000000001f0 A VDSO32_NOTE_MASK
0000000000000400 A VDSO32_sigreturn
0000000000000410 A VDSO32_rt_sigreturn
0000000000000420 A VDSO32_vsyscall
0000000000000430 A VDSO32_SYSENTER_RETURN
0000000000004000 D per_cpu__gdt_page
0000000000005000 d per_cpu__exception_stacks
000000000000a000 d per_cpu__idt_desc
・
・
ffffffff81000000 T _text
ffffffff81000000 T startup_64
ffffffff810000b7 t ident_complete
ffffffff81000100 T secondary_startup_64
ffffffff8100018f t bad_address
ffffffff81000198 T _stext
ffffffff81001000 T hypercall_page
ffffffff81002010 T do_one_initcall
ffffffff81002290 t cpumask_weight
ffffffff810022b0 t run_init_process
ffffffff810022e0 t init_post
ffffffff810023e0 T name_to_dev_t
ffffffff81002640 t create_dev
ffffffff810026a0 t create_dev.clone.0
ffffffff810026d0 t create_dev
ffffffff81002730 t bstat
・
・
```

* **ffffffff80000000**以降の領域はLinuxカーネルのプログラムがロードされる**仮想アドレス**。Linuxカーネルのっプログラムファイルは仮想アドレス**ffffffff81000000**に配置されるようにリンクされる。ユーザプロセスはここにアクセスする。もちろん仮想アドレスなので実際は物理アドレスに変換される。
* ちなみにユーザープロセスは**0000000000000000**~**0000800000000000**にリンクされるようです。127TB。pmapコマンドではここの中で使用されている箇所を表示している。実際に0~800000000000の中の様子。vsyscallだけ、この中に収まっていなかった。なんでだろう？

```bash:pmap結果
$ cat /proc/4705/maps 
7f3a13e5a000-7f3a13e6f000 r-xp 00000000 fd:00 2381451                    /usr/lib64/php/modules/zip.so
7f3a13e6f000-7f3a1406f000 ---p 00015000 fd:00 2381451                    /usr/lib64/php/modules/zip.so
7f3a1406f000-7f3a14071000 rw-p 00015000 fd:00 2381451                    /usr/lib64/php/modules/zip.so
7f3a14071000-7f3a140b0000 r-xp 00000000 fd:00 2381450                    /usr/lib64/php/modules/phar.so
7f3a140b0000-7f3a142af000 ---p 0003f000 fd:00 2381450                    /usr/lib64/php/modules/phar.so
7f3a142af000-7f3a142b2000 rw-p 0003e000 fd:00 2381450                    /usr/lib64/php/modules/phar.so
7f3a142b2000-7f3a142bc000 r-xp 00000000 fd:00 2381449                    /usr/lib64/php/modules/json.so
7f3a142bc000-7f3a144bb000 ---p 0000a000 fd:00 2381449                    /usr/lib64/php/modules/json.so
7f3a144bb000-7f3a144bc000 rw-p 00009000 fd:00 2381449                    /usr/lib64/php/modules/json.so
・
・
7ffe61b3a000-7ffe61b4f000 rw-p 00000000 00:00 0                          [stack]
7ffe61b7b000-7ffe61b7c000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]

```

↓

swapper用(後にinitプロセス用)のスタック設定

```asm:arch/x86/kernel/head_64.S
        /* Setup a boot time stack */
        movq stack_start(%rip),%rsp

        ENTRY(stack_start)
        .quad  init_thread_union+THREAD_SIZE-8
        .word  0
```
* init_thread_unionはinitプロセスのための領域
* その他：ENTRY()はマクロ。展開されて↓みたいな感じになる。外部からnameでアクセスできる。

```c
#define ENTRY(name)
.global name;
ALIGN;
name:
```
* スタックポインタを設定しているので、これが最初に使うカーネルスタックの実体。最初のプロセスのスタックは直で書かれている。initプロセスが動作するまでのカーネル環境構築。

↓

### x86_64_start_kernel()
ここからC言語。割り込みハンドラを仮設定

- x86_64_start_reservations()
- start_kernel()

```asm
252         movq    initial_code(%rip),%rax
253         pushq   $0              # fake return address to stop unwinder
254         pushq   $__KERNEL_CS    # set correct cs
255         pushq   %rax            # target address in negative space
256         lretq

261         ENTRY(initial_code)
262         .quad   x86_64_start_kernel
```
```c:arch/x86/kernel/head64.c
void __init x86_64_start_kernel(char * real_mode_data)
{
        int i;

        /*
         * Build-time sanity checks on the kernel image and module
         * area mappings. (these are purely build-time and produce no code)
         */
        BUILD_BUG_ON(MODULES_VADDR < KERNEL_IMAGE_START);
        BUILD_BUG_ON(MODULES_VADDR-KERNEL_IMAGE_START < KERNEL_IMAGE_SIZE);
        BUILD_BUG_ON(MODULES_LEN + KERNEL_IMAGE_SIZE > 2*PUD_SIZE);
        BUILD_BUG_ON((KERNEL_IMAGE_START & ~PMD_MASK) != 0);
        BUILD_BUG_ON((MODULES_VADDR & ~PMD_MASK) != 0);
        BUILD_BUG_ON(!(MODULES_VADDR > __START_KERNEL));
        BUILD_BUG_ON(!(((MODULES_END - 1) & PGDIR_MASK) ==
                                (__START_KERNEL & PGDIR_MASK)));
        BUILD_BUG_ON(__fix_to_virt(__end_of_fixed_addresses) <= MODULES_END);

        /* clear bss before set_intr_gate with early_idt_handler */
        clear_bss();

        /* Make NULL pointers segfault */
        zap_identity_mappings();

        /* Cleanup the over mapped high alias */
        cleanup_highmap();

        for (i = 0; i < NUM_EXCEPTION_VECTORS; i++) {
#ifdef CONFIG_EARLY_PRINTK
                set_intr_gate(i, &early_idt_handlers[i]);
#else
                set_intr_gate(i, early_idt_handler);
#endif
        }
        load_idt((const struct desc_ptr *)&idt_descr);

        if (console_loglevel == 10)
                early_printk("Kernel alive\n");

        x86_64_start_reservations(real_mode_data);
}

void __init x86_64_start_reservations(char *real_mode_data)
{
        copy_bootdata(__va(real_mode_data));

        reserve_trampoline_memory();

        reserve_early(__pa_symbol(&_text), __pa_symbol(&__bss_stop), "TEXT DATA BSS");

#ifdef CONFIG_BLK_DEV_INITRD
        /* Reserve INITRD */
        if (boot_params.hdr.type_of_loader && boot_params.hdr.ramdisk_image) {
                unsigned long ramdisk_image = boot_params.hdr.ramdisk_image;
                unsigned long ramdisk_size  = boot_params.hdr.ramdisk_size;
                unsigned long ramdisk_end   = ramdisk_image + ramdisk_size;
                reserve_early(ramdisk_image, ramdisk_end, "RAMDISK");
        }
#endif

        reserve_ebda_region();

        /*
         * At this point everything still needed from the boot loader
         * or BIOS or kernel text should be early reserved or marked not
         * RAM in e820. All other memory is free game.
         */

        start_kernel();
}
```

start_kernel()を呼び出して終わる。start_kernel()ではカーネルスレッドなどの生成をしている

# まとめ

リアルモードから32bitプロテクトモードへ
↓
カーネル(vmlinuz)をロード&解凍
↓
64bitモード移行
↓
startup_64()　**カーネルエントリポイント**
↓
x86_64_start_kernel()
↓
x86_64_start_reservations()
↓
start_kernel()