Linux Boot Sequence を読む ( 7 ) ~ start_kernel() ~

February 19, 2018

x8664start_kernel()

startup_64 では x86_64_start_kernel() が呼ばれていたが、x86_64_start_kernel() では割り込みハンドラを仮設定したあとに start_kernel() を呼び出すことになる。start_kernel() では init プロセスが動作するまでのカーネル環境の初期化処理が行われる。
まずは x86_64_start_kernel() を軽く見てみる。ソースコードは arch/x86/kernel/head64.c となる。

void __init x86_64_start_kernel(char * real_mode_data)  
{  
  
        ... (中略) ...  
  
        /* clear bss before set_intr_gate with early_idt_handler */  
        clear_bss();  
  
        for (i = 0; i < NUM_EXCEPTION_VECTORS; i++)  
                set_intr_gate(i, early_idt_handlers[i]);  
        load_idt((const struct desc_ptr *)&amp;idt_descr);  
  
        copy_bootdata(__va(real_mode_data));  
  
        /*                                                                                                                                                                                 
         * Load microcode early on BSP.                                                                                                                                                    
         */  
        load_ucode_bsp();  
  
        if (console_loglevel == 10)  
                early_printk("Kernel alive\n");  
  
        clear_page(init_level4_pgt);  
        /* set init_level4_pgt kernel high mapping*/  
        init_level4_pgt[511] = early_level4_pgt[511];  
  
        x86_64_start_reservations(real_mode_data);  
}  
  
void __init x86_64_start_reservations(char *real_mode_data)  
{  
        /* version is always not zero if it is copied */  
        if (!boot_params.hdr.version)  
                copy_bootdata(__va(real_mode_data));  
  
        reserve_ebda_region();  
  
        start_kernel();  
}  

start_kernel()

いよいよ start_kernel() だが、こちらは init/main.c に定義されており、前述の通り様々なコンポーネントに対して初期化処理が走る。

  • CPU
  • メモリ
  • IO
  • プロセススケジューラ
  • 割り込み

…などなど。

asmlinkage void __init start_kernel(void)  
{  
        char * command_line;  
        extern const struct kernel_param __start___param[], __stop___param[];  
  
	...  
  
        local_irq_disable(); //割り込み禁止  
        early_boot_irqs_disabled = true;  
  
	...  
  
	setup_arch(&amp;command_line);  
  
	...  
  
        setup_log_buf(0); //カーネルのログバッファ初期化  
        pidhash_init(); //PID のハッシュテーブル初期化  
        vfs_caches_init_early(); // inode ハッシュテーブル初期化  
        sort_main_extable();  
        trap_init();  
        mm_init();  
  
	...  
  
	sched_init(); //ランキューの初期化  
  
	...  
  
        early_irq_init();  
        init_IRQ();   
  
	...  
  
        softirq_init();  
        timekeeping_init();  
        time_init();  
  
	...  
  
        early_boot_irqs_disabled = false;  
        local_irq_enable(); //割り込み許可  
  
	...  
  
        fork_init(totalram_pages); //タスク数上限の設定  
	  
	...  
	  
        signals_init(); // シグナルキューの作成  
  
	...  
  
	proc_root_init(); // proc fs のマウント  
  
	...  
  
	rest_init();  
}  

以下、いくつかのコンポーネントに対して軽く説明を記載する。

setup_arch()

setup_arch() は機種依存な初期化処理を行う関数であり、arch/x86/kernel/setup.c に定義されている。
いくつかの割り込みハンドラの登録やメモリマップやページングの設定などが行われる。

mm_init()

mm_init() は page 構造体とメモリアロケータの初期化を行う関数であり、init/main.c に定義されている。
実際にこれらの処理は内部で呼ばれている mem_init() (arch/x86/mm/init_64.c) で実行されており、コンソールに以下のようなメッセージを表示するのもこいつ。

Memory: 5742800k/15990784k available (6916k kernel code, 262540k absent, 534876k reserved, 4551k data, 1800k init)

これらの値がどこから来たのかは以下に記載されている。(arch/x86/mm/init_64.c)

void __init mem_init(void)  
{  
  
	... (中略) ...  
  
        printk(KERN_INFO "Memory: %luk/%luk available (%ldk kernel code, "  
                         "%ldk absent, %ldk reserved, %ldk data, %ldk init)\n",  
                nr_free_pages() << (PAGE_SHIFT-10),  
                max_pfn << (PAGE_SHIFT-10),  
                codesize >> 10,  
                absent_pages << (PAGE_SHIFT-10),  
                reservedpages << (PAGE_SHIFT-10),  
                datasize >> 10,  
                initsize >> 10);  
}  

absent_pages というのは、メモリホール (物理メモリ上の “使用できない” 領域のこと) のページ数であり、maxpfn はメモリホールを気にせず、とにかく最上位物理アドレスのページフレーム番号である。
15990784k (= 15.99 GB) というのは、max
pfn であり、これから absent_pages を引いた値が実メモリ量となるため、一見ちょっと多くメモリを持っているように見える。
(実際このインスタンスは c4.2xlarge であり、メモリは 15 GB)

trap_init()

現時点で IDT (Interrupt Descriptor Table) は、x86_64_start_kernel() により仮のテーブルが設定されている状態である。
trap_init() は、あらかじめカーネル内のデータ領域に静的に確保されている新しい IDT にゲートデスクリプタ* エントリを登録して初期化する処理を行う。arch/x86/kernel/traps.c に実装が定義されている。

init_IRQ()

init_IRQ() は、外部ハードウェアの割り込みコントローラ 8259A の初期化及びそれらに対する割り込みハンドラを IDT に登録する処理を行う。
arch/x86/kernel/irqinit.c に定義されている init_IRQ() は以下のように for ループでベクタ番号から IRQ 番号への変換テーブルを定義しており、この vector_irq[] という配列は do_IRQ() が IRQ 番号を得るために使用される。

void setup_vector_irq(int cpu)  
{  
#ifndef CONFIG_X86_IO_APIC  
        int irq;  
  
        for (irq = 0; irq < nr_legacy_irqs(); irq++)  
                per_cpu(vector_irq, cpu)[IRQ0_VECTOR + irq] = irq;  
#endif  
  
        __setup_vector_irq(cpu);  
}  

timekeeping_init()

timekeeping_init() は RTC から現在時刻を取得し、システム時刻を保持する timekeeper 構造体 tk に設定する処理を行う。
実装は kernel/time/timekeeping.c に定義されている。

void __init timekeeping_init(void)  
{  
        struct timekeeper *tk = &amp;timekeeper;  
        struct clocksource *clock;  
        unsigned long flags;  
        struct timespec64 now, boot, tmp;  
        struct timespec ts;  
  
	...(中略)...  
  
        tk_set_xtime(tk, &amp;now);  

time_init()

time_init() は HPET (High Performance Event Timer) のタイマ割り込みの設定と、タイマ割り込みハンドラの登録、CPU クロック周波数の計算処理を行う。


 © 2023, Dealing with Ambiguity