linux內核啓動分析(2)

-----如下內容爲從網絡上整理所得------node

 

主要介紹kernel_init線程(函數),這個線程在rest_init函數中被建立,kernel_init函數將完成設備驅動程序的初始化,並調用init_post函數啓動用戶空間的init進程。程序員

static int __init kernel_init(void * unused)
{
    lock_kernel();
  //鎖住內核
    set_mems_allowed(node_states[N_HIGH_MEMORY]);
  //init能夠在任何節點(node)分配到內存頁
    set_cpus_allowed_ptr(current, cpu_all_mask);
  //init能夠在任何CPU上運行.
    init_pid_ns.child_reaper = current;
    //把當前進程設爲接受其餘孤兒進程的進程
    
    cad_pid = task_pid(current);

    /*SMP系統作準備,激活全部CPU,並開始SMP系統的調度*/
    smp_prepare_cpus(setup_max_cpus);
    do_pre_smp_initcalls();
    start_boot_trace();
    smp_init();
    sched_init_smp();

    do_basic_setup();
    //驅動程序和內核子系統的通常初始化,下面會詳解

    //檢查是否有早期用戶空間的init程序。若是有,讓其執行
    if (!ramdisk_execute_command)ramdidk_execute_command="/init";
    if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0)
    {
        ramdisk_execute_command = NULL;
        prepare_namespace();
    }
    //最後調用init_post,啓動進程負責用戶空間的初始化
    init_post();
    return 0;
}

 

在內核init線程的最後執行了init_post函數,在這個函數中真正啓動了用戶空間進程init,詳解以下:網絡

static noinline int init_post(void)
        __releases(kernel_lock)
{
    /* need to finish all async __init code before freeing the memory */
    async_synchronize_full();
    free_initmem();
    unlock_kernel();
    
    mark_rodata_ro();
    //經過修改頁表,保證只讀數據段爲只讀屬性。大部分構架爲空函數。

    system_state = SYSTEM_RUNNING;
    //設置系統狀態爲運行狀態

    numa_default_policy();
    //設定NUMA系統的內存訪問策略爲默認

    //打開根文件系統中的 /dev/console , 此處不可失敗
    if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)
        printk(KERN_WARNING "Warning: unable to open an initial console.\n");

    /*複製兩次標準輸入(0)的文件描述符
     *它是上面打開的/dev/console,也就是系統控制檯):
     *一個做爲標準輸出(1)
     *一個做爲標準出錯(2)
     *如今標準輸入、標準輸出、標準出錯都是/dev/console了。
     *這個console在內核啓動參數中能夠配置爲某個串口(ttySn、ttyOn等等),
     *也能夠是虛擬控制檯(tty0)。
     *因此咱們就在串口或者顯示器上看到了以後的系統登陸提示。
    **/
    (void) sys_dup(0);
    (void) sys_dup(0);
    
    //設置當前進程(init)爲不能夠殺進程(忽略致命的信號)
    current->signal->flags |= SIGNAL_UNKILLABLE;
    
    //若是ramdisk_execute_command有指定的init程序,就執行它。
    if (ramdisk_execute_command) {
        run_init_process(ramdisk_execute_command);
        printk(KERN_WARNING "Failed to execute %s\n",         
                                ramdisk_execute_command);
    }
    //若是execute_command有指定的init程序,就執行它。
    if (execute_command) {
                run_init_process(execute_command);
                printk(KERN_WARNING "Failed to execute %s.  Attempting "
                                        "defaults...\n", execute_command);
    }
    /*在檢查完ramdisk_execute_command和execute_command爲空的狀況下,
     *順序執行如下初始化程序:若是都沒有找到就打印錯誤信息。
     *這也是咱們作系統移植的時候常常碰到的錯誤信息,出現這個信息頗有多是:
     *一、你的啓動參數配置有問題,經過 指定了init程序,可是沒有找到,
     *    且默認的那四個程序也不在文件系統中。
     *二、文件系統掛載有問題,文件不存在
     *三、init程序沒有執行權限
    **/
    run_init_process("/sbin/init");
    run_init_process("/etc/init");
    run_init_process("/bin/init");
    run_init_process("/bin/sh");

    panic("No init found.  Try passing init= option to kernel.");
}

 

在基本分析完內核啓動流程的以後,還有一個比較重要的初始化函數沒有分析,那就是do_basic_setup。在內核init線程中調用了do_basic_setup,這個函數也作了不少內核和驅動的初始化工做,詳解以下:async

/*
 * 好了, 設備如今已經初始化完成。 可是尚未一個設備被初始化過,
 * 可是 CPU 的子系統已經啓動並運行,
 * 且內存和處理器管理系統已經在工做了。
 *
 * 如今咱們終於能夠開始作一些實際的工做了..
 */
static void __init do_basic_setup(void)
{
    rcu_init_sched(); /* needed by module_init stage. */
    init_workqueues(); //初始化工做隊列

    /*針對SMP系統,初始化內核control group的cpuset子系統。
     *若是非SMP,此函數爲空。
     *cpuset是在用戶空間中操做cgroup文件系統來執行進程
     *與cpu和進程與內存結點之間的綁定。
     *本函數將cpus_allowed和mems_allwed更新爲在線的cpu和在線的內存結點,
     *併爲內存熱插撥註冊了鉤子函數,最後建立一個單線程工做隊列cpuset。
     */
    cpuset_init_smp();

    /*建立一個單線程工做隊列khelper。
     *運行的系統中只有一個,主要做用是指定用戶空間的程序路徑和環境變量, 
     *最終運行指定的user space的程序,屬於關鍵線程,不能關閉。
     */
    usermodehelper_init();

    //初始化驅動模型中的各子系統,可見的現象是在/sys中出現的目錄和文件
    driver_init();

    //在proc文件系統中建立irq目錄,並在其中初始化系統中全部中斷對應的目錄。
    init_irq_proc();

    /*調用連接到內核中的全部構造函數,也就是連接進.ctors段中的全部函數。
     *在內核啓動和模塊掛載時,調用構造函數(gcc生成的類初始化函數)。
     *構造函數就是好比用於初始化gcov數據的函數
     */
    do_ctors();

    /*調用全部編譯內核的驅動模塊中的初始化函數。
     *這裏就是驅動程序員須要關心的步驟,其中按照各個內核模塊初始化函數
     *所自定義的啓動級別(1~7),按順序調用器初始化函數。
     *對於同一級別的初始化函數,安裝編譯是連接的順序調用,
     *也就是和內核Makefile的編寫有關。
     *
     *在編寫內核模塊的時候須要知道這方面的知識,好比你編寫的模塊使用
     *的是I2C的API,那你的模塊的初始化函數的級別必須低於I2C子系統
     *初始化函數的級別(也就是級別數(1~7)要大於I2C子系統)。
     *若是編寫的模塊必須和依賴的模塊在同一級,那就必須注意內核Makefile的修改了。
     */
    do_initcalls();
}

 

上面的函數調用了driver_init函數,做用是驅動模型子系統的初始化,對於內核驅動工程師來講比較重要,詳解以下:函數

/**
 * driver_init - 初始化驅動模型.
 *
 * 調用驅動模型初始化函數來初始化它們的子系統。
 * 由早期的init/main.c中調用。
 */
void __init driver_init(void)
{
    /* These are the core pieces */
    /*初始化驅動模型中的部分子系統和kobject:
     *devices
     *dev
     *dev/block
     *dev/char
     */
    devices_init();
    buses_init();//初始化驅動模型中的bus子系統
    classes_init();//初始化驅動模型中的class子系統
    firmware_init();//初始化驅動模型中的firmware子系統
    hypervisor_init();//初始化驅動模型中的hypervisor子系統

    /* These are also core pieces, but must come after the
     * core core pieces.
     */
    platform_bus_init();//初始化驅動模型中的bus/platform子系統
    system_bus_init();//初始化驅動模型中的devices/system子系統
    cpu_dev_init();//初始化驅動模型中的devices/system/cpu子系統
    memory_dev_init();//初始化驅動模型中的devices/system/memory子系統
}
相關文章
相關標籤/搜索