天天看點

start_kernel啟動函數start_kernel啟動函數

機器上電後,會進行BIOS自檢,之後是系統引導,核心加載,最後是初始化階段。我們這裡主要關心的是初始化階段(start_kernel)的事情,而從上電到初始化之前的事情直接忽略了。

            start_kernel函數, 也是核心啟動函數,位于init/main.c檔案中,該函數中調用的函數都是一個大分支。

asmlinkage void __init start_kernel(void)

{

        char * command_line;

        extern const struct kernel_param __start___param[], __stop___param[];

        /* 

         * Need to run as early as

possible, to initialize the

         * lockdep hash:

         */

        lockdep_init();

        smp_setup_processor_id();

        debug_objects_early_init();

         * Set up the the initial

canary ASAP:

        boot_init_stack_canary();

        cgroup_init_early();

        local_irq_disable();

        early_boot_irqs_disabled = true;

/*

 * Interrupts are still disabled. Do

necessary setups, then

 * enable them

 */

        boot_cpu_init();

        page_address_init();

        pr_notice("%s", linux_banner);

        setup_arch(&command_line);// 特定于體系結構的、記憶體管理的高層設定

        mm_init_owner(&init_mm,

&init_task);

mm_init_cpumask(&init_mm);

setup_command_line(command_line);

        setup_nr_cpu_ids();

setup_per_cpu_areas();

        smp_prepare_boot_cpu(); /* arch-specific boot-cpu hooks */

        build_all_zonelists(NULL, NULL);

        page_alloc_init();

        pr_notice("Kernel command line: %s\n", boot_command_line);

        parse_early_param();

        parse_args("Booting kernel", static_command_line, __start___param,

                   __stop___param -

__start___param,

                   -1, -1,

&unknown_bootoption);

        jump_label_init();

        /*

         * These use large bootmem

allocations and must precede

         * kmem_cache_init()

        setup_log_buf(0);

        pidhash_init();

        vfs_caches_init_early();

        sort_main_extable();

        trap_init();

        mm_init();

         * Set up the scheduler

prior starting any interrupts (such as the

         * timer interrupt). Full topology setup

happens at smp_init()

         * time - but meanwhile we

still have a functioning scheduler.

        sched_init();//初始化排程器的資料結構,并建立運作隊列

         * Disable preemption -

early bootup scheduling is extremely

         * fragile until we

cpu_idle() for the first time.

        preempt_disable();

        if (WARN(!irqs_disabled(), "Interrupts were enabled *very* early, fixing it\n"))

local_irq_disable();

        idr_init_cache();

        perf_event_init();

        rcu_init();

        tick_nohz_init();

        radix_tree_init();

        /* init some links before init_ISA_irqs() */

        early_irq_init();

        init_IRQ();

        tick_init();

        init_timers();

        hrtimers_init();

        softirq_init();//注冊用于普通和高優先級的tasklet的軟中斷隊列

        timekeeping_init();

        time_init();//從硬體讀取系統時間

        profile_init();

        call_function_init();

        WARN(!irqs_disabled(), "Interrupts were enabled early\n");

        early_boot_irqs_disabled = false;

        local_irq_enable();

        kmem_cache_init_late();

         * HACK ALERT! This is

early. We're enabling the console before

         * we've done PCI setups

etc, and console_init() must be aware of

         * this. But we do want

output early, in case something goes wrong.

        console_init();

        if (panic_later)

                panic(panic_later,

panic_param);

        lockdep_info();

         * Need to

run this when irqs are enabled, because it wants

         * to self-test

[hard/soft]-irqs on/off lock inversion bugs

         * too:

        locking_selftest();

#ifdef CONFIG_BLK_DEV_INITRD

        if (initrd_start &&

!initrd_below_start_ok &&

page_to_pfn(virt_to_page((void *)initrd_start)) < min_low_pfn) {

                pr_crit("initrd overwritten (0x%08lx < 0x%08lx) - disabling it.\n",

page_to_pfn(virt_to_page((void *)initrd_start)),

                    min_low_pfn);

                initrd_start = 0;

        }

#endif

        page_cgroup_init();

        debug_objects_mem_init();

        kmemleak_init();

        setup_per_cpu_pageset();

        numa_policy_init();

        if (late_time_init)

                late_time_init();

        sched_clock_init();

        calibrate_delay();

        pidmap_init();

        anon_vma_init();

#ifdef CONFIG_X86

        if (efi_enabled(EFI_RUNTIME_SERVICES))

efi_enter_virtual_mode();

        thread_info_cache_init();

        cred_init();

        fork_init(totalram_pages);

        proc_caches_init();

        buffer_init();

        key_init();

        security_init();

        dbg_late_init();

vfs_caches_init(totalram_pages);

        signals_init();

        /* rootfs

populating might need page-writeback */

        page_writeback_init();

#ifdef CONFIG_PROC_FS

        proc_root_init();

        cgroup_init();

        cpuset_init();

        taskstats_init_early();

        delayacct_init();

        check_bugs();

        acpi_early_init(); /* before LAPIC and SMP init */

        sfi_init_late();

        if (efi_enabled(EFI_RUNTIME_SERVICES)) {

                efi_late_init();

efi_free_boot_services();

        ftrace_init();

        /* Do the

rest non-__init'ed, we're now alive */

        rest_init();//rest_init();啟動init程序。

}

            主要有如下幾點:

l   特定于體系結構的設定

l   解釋指令行參數

l   初始化資料結構和緩存

l   查找已知的系統錯誤

l   最後建立idle和init程序,調用do_basic_setup函數,進行驅動設定。

驅動程式和子系統的一般初始化工作是在do_basic_setup函數中開始的。

startup_kernel->rest_init->kernel_init->kernel_init_freeable->do_basic_setup();

static void __init do_basic_setup(void)

        cpuset_init_smp();

        shmem_init();

        driver_init();

        init_irq_proc();

        do_ctors();

        usermodehelper_enable();

        do_initcalls();

}  

            其中do_initcalls函數調用驅動程式相關的初始化函數。

我們需要先來看下系統的initcall機制。

linux初始化的過程中,核心采用了一種initcall的機制,它利用gcc的擴充功能以及ld的連接配接控制腳本實作了在核心初始化的過程中通過簡單的循環就實作了相關驅動的初始化。

Linux提供了一個頭檔案“vmlinux.lds.h”,該檔案定義了一些宏用于輔助寫連接配接腳本,從其中可以看到最終會出現在連接配接腳本中的各個記憶體section以及它們的相對位置.

#define INIT_CALLS_LEVEL(level)                                         \

VMLINUX_SYMBOL(__initcall##level##_start) = .;          \

KEEP(*(.initcall##level##.init))                        \

                KEEP(*(.initcall##level##s.init))                       \

#define INIT_CALLS                                                     

\

VMLINUX_SYMBOL(__initcall_start) = .;                   \

KEEP(*(.initcallearly.init))            

               \

                INIT_CALLS_LEVEL(0)                                     \

                INIT_CALLS_LEVEL(1)                                     \

                INIT_CALLS_LEVEL(2)                                     \

                INIT_CALLS_LEVEL(3)                                     \

                INIT_CALLS_LEVEL(4)                                     \

                INIT_CALLS_LEVEL(5)                                     \

INIT_CALLS_LEVEL(rootfs)          

                     \

                INIT_CALLS_LEVEL(6)                                     \

                INIT_CALLS_LEVEL(7)                                     \

VMLINUX_SYMBOL(__initcall_end) = .;

do_initcalls是從特定的記憶體區域取出初始化函數的指針,然後來調用它的,定義的各個等級的初始化函數都應該被放在特定的記憶體區域,每個等級的初始化函數都被放在自己特定的初始化區域中。

static void __init do_initcalls(void)

        int level;

        for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)

do_initcall_level(level);

核心中經常遇到初始化函數是這樣定義的:

static int __init init_func(); 與普通函數相比,定義中多了__init。

  __exit宏告知編譯器,将函數放在".exit.text"這個區域中。__exitdata宏則告知編譯器将資料放在".exit.data"這個區域中。

在核心初始化時,從__initcall_start到__initcall_end之間的initcall被一次執行。

  核心組建可以利用定義在<b>include/linux/init.h</b><b>中的</b><b>__setup</b><b>宏來注冊關鍵字和相關聯的處理函數</b><b>,</b><b>此外還有如下</b><b>__init</b><b>開頭的宏。</b>

#define __init         

__section(.init.text) __cold 

__latent_entropy __noinitretpoline                                 

#define __initdata     

__section(.init.data)

#define __initconst    

__section(.init.rodata)

#define __exitdata     

__section(.exit.data)

#define __exit_call    

__used __section(.exitcall.exit)  

            gcc在編譯時會将被修飾的内容放到這些宏所代表的section.

            在檔案<b>include/linux/init.h</b><b>中定義了初始化例程并定義其順序或優先級:</b>

#define pure_initcall(fn)              

__define_initcall(fn, 0)

#define core_initcall(fn)              

__define_initcall(fn, 1)

#define core_initcall_sync(fn)         

__define_initcall(fn, 1s)

#define postcore_initcall(fn)          

__define_initcall(fn, 2)

#define postcore_initcall_sync(fn)     

__define_initcall(fn, 2s)

#define arch_initcall(fn)              

__define_initcall(fn, 3)

#define arch_initcall_sync(fn)         

__define_initcall(fn, 3s)

#define subsys_initcall(fn)            

__define_initcall(fn, 4)

#define subsys_initcall_sync(fn)       

__define_initcall(fn, 4s)

#define fs_initcall(fn)                

__define_initcall(fn, 5)

#define fs_initcall_sync(fn)           

__define_initcall(fn, 5s)

#define rootfs_initcall(fn)            

__define_initcall(fn, rootfs)

#define device_initcall(fn)            

__define_initcall(fn, 6)

#define device_initcall_sync(fn)       

__define_initcall(fn, 6s)

#define late_initcall(fn)              

__define_initcall(fn, 7)

#define late_initcall_sync(fn)          __define_initcall(fn, 7s)

繼續閱讀