1. 程式人生 > >Linux核心啟動及檔案系統載入過程

Linux核心啟動及檔案系統載入過程

u-boot開始執行bootcmd命令,就進入linux核心啟動階段

u-boot類似,普通Linux核心的啟動過程也可以分為兩個階段,但針對壓縮了的核心如uImage就要包括核心自解壓過程了。第一階段為核心自解壓過程,第二階段主要工作是設定ARM處理器工作模式、使能MMU、設定一級頁表等,而第三階段則主要為C程式碼,包括核心初始化的全部工作,下面是詳細介紹。

一、Linux核心自解壓過程

核心壓縮和解壓縮程式碼都在目錄kernel/arch/arm/boot/compressed,編譯完成後將產生head.o、misc.o、piggy.gzip.o、vmlinux、decompress.o

這幾個檔案,head.o是核心的頭部檔案,負責初始設定;misc.o將主要負責核心的解壓工作,它在head.o之後;piggy.gzip.o是一箇中間檔案,其實是一個壓縮的核心(kernel/vmlinux),只不過沒有和初始化檔案及解壓檔案連結而已;vmlinux是沒有(zImage是壓縮過的核心)壓縮過的核心,就是由piggy.gzip.o、head.o、misc.o組成的,而decompress.o是為支援更多的壓縮格式而新引入的。

BootLoader完成系統的引導以後並將Linux核心調入記憶體之後,呼叫boot_linux(),這個函式將跳轉到kernel的起始位置。如果kernel

沒有被壓縮,就可以啟動了。如果kernel被壓縮過,則要進行解壓,在壓縮過的kernel頭部有解壓程式。壓縮過的kernel入口第一個檔案原始碼位置在arch/arm/boot/compressed/head.S。它將呼叫函式decompress_kernel(),這個函式在檔案arch/arm/boot/compressed/misc.c中,decompress_kernel()又呼叫proc_decomp_setup(),arch_decomp_setup()進行設定,然後打印出資訊“Uncompressing Linux...”解壓縮linux後,呼叫gunzip()[或者unlz4或者bunzip2或者unlz]將核心放於指定的位置。

下面簡單介紹一下解壓縮過程,也就是函式decompress_kernel實現的功能:解壓縮程式碼位於kernel/lib/inflate.c,inflate.c是從gzip源程式中分離出來的,包含了一些對全域性資料的直接引用,在使用時需要直接嵌入到程式碼中。gzip壓縮檔案時總是在前32K位元組的範圍內尋找重複的字串進行編碼, 在解壓時需要一個至少為32K位元組的解壓緩衝區,它定義為window[WSIZE]inflate.c使用get_byte()讀取輸入檔案,它被定義成巨集來提高效率。輸入緩衝區指標必須定義為inptr,inflate.c中對之有減量操作。inflate.c呼叫flush_window()來輸出window緩衝區中的解壓出的位元組串,每次輸出長度用outcnt變量表示。在flush_window()中,還必須對輸出位元組串計算CRC並且重新整理crc變數。在呼叫gunzip()開始解壓之前,呼叫makecrc()初始化CRC計算表。最後gunzip()返回0表示解壓成功。我們在核心啟動的開始都會看到這樣的輸出:

UncompressingLinux...done, booting the kernel.

重要程式碼:

//解壓核心程式碼

①、在lk層

bootable\bootloader\lk\app\mt_boot\Decompressor.c中:

bool decompress_kernel(unsigned char *in, void *out, int inlen, int outlen)
{
    unsigned long lenp = inlen;
    return gunzip(in, &lenp, out, outlen);
}

在檔案bootable\bootloader\lk\app\mt_boot\Mt_boot.c中:

extern bool decompress_kernel(unsigned char *in, void *out, int inlen, int outlen);

//它被mt_boot.c 中的boot_linux函式呼叫
int boot_linux_fdt(void *kernel, unsigned *tags,
                   char *cmdline, unsigned machtype,
                   void *ramdisk, unsigned ramdisk_size)
{

、、、、、、、、、、、、、、、、、、、、、、、

/* for 64bit decompreesed size.
         * LK start: 0x41E00000, Kernel Start: 0x40080000
         * Max is 0x41E00000 - 0x40080000 = 0x1D80000.
         * using 0x1C00000=28MB for decompressed kernel image size */
        if (decompress_kernel((unsigned char *)zimage_addr, (void *)g_boot_hdr->kernel_addr, (int)zimage_size, (int)0x1C00000)) {
            dprintf(CRITICAL,"decompress kernel image fail!!!\n");
            while (1)
                ;
        }

、、、、、、、、、、、、、、、、、、、、

}

上面的函式被bootable\bootloader\lk\app\mt_boot\Mt_boot.c中的boot_linux函式呼叫,如下:

/*
初始化DTB(device tree block);

準備各種cmdline引數傳入kernel;

關閉I/D-cache、MMU;

列印關鍵資訊,正式拉起kernel.

到這裡,bootloader兩個階段就完了!
*/
void boot_linux(void *kernel, unsigned *tags,
                char *cmdline, unsigned machtype,
                void *ramdisk, unsigned ramdisk_size)
{

、、、、、、、、、、、、、、、、、、、、、、、、、、、、


// 新架構都是走fdt分支.  boot_linux_fdt()函式很重要##################################
#ifdef DEVICE_TREE_SUPPORT   //DEVICE_TREE_SUPPORT := yes
    boot_linux_fdt((void *)kernel, (unsigned *)tags,
                   (char *)cmdline, machtype,
                   (void *)ramdisk, ramdisk_size);

    while (1) ;
#endif

、、、、、、、、、、、、、、、、、、、、、、、、、、、、

}

上面的函式被bootable\bootloader\lk\app\mt_boot\Mt_boot.c中的boot_linux_from_storage函式呼叫,如下:

/*###########################重要########################################*/
/* 這裡乾的事情就比較多了,跟進g_boot_mode選擇各種啟動模式,例如:
normal、facotry、fastboot、recovery等,然後從ROM中的boot.img分割槽找到(解壓)
ramdisk跟zImage的地址loader到DRAM的特定地址中,kernel最終load到DRAM中的地址
(DRAM_PHY_ADDR + 0x8000) == 0x00008000.  (這個資料時原始的)
read the data of boot (size = 0x811800)
*/

//boot_linux_from_storage從函式的名稱就可以看出來,"從儲存其中啟動linux核心"
//在此函式中,將cus_param的資訊新增到cmdline上
//具體流程請檢視我的部落格:android 利用cmdline,將引數從preloader傳遞到kernel
//http://blog.csdn.net/ffmxnjm/article/details/71217309

####################################################################

int boot_linux_from_storage(void)
{
    int ret=0;
#define CMDLINE_TMP_CONCAT_SIZE 100     //only for string concat, 200 bytes is enough

、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、

/*
   從EMMC的boot分割槽取出bootimage載入到DRAM  
   在bootable\bootloader\lk\platform\mt6735\load_image.c中有如下列印資訊:
    dprintf(CRITICAL, " > from - 0x%016llx (skip boot img hdr)\n",start_addr);
    dprintf(CRITICAL, " > to   - 0x%x (starts with kernel img hdr)\n",addr);
    len = partition_read(part_name, g_boot_hdr->page_size, (uchar*)addr, (size_t)g_bimg_sz); //<<= 系統呼叫load到DRAM
    
   開機log:
   [4400]  &gt; from - 0x0000000001d80800 (skip boot img hdr)
   [4400]  &gt; to   - 0x45000000 (starts with kernel img hdr)
 */

#ifdef MTK_GPT_SCHEME_SUPPORT
            ret = mboot_android_load_bootimg("boot", kimg_load_addr);  //載入bootimage
#else

            ret = mboot_android_load_bootimg(PART_BOOTIMG, kimg_load_addr);
#endif

            if (ret < 0) {
                msg_img_error("Android Boot Image");
            }
#ifdef LK_PROFILING
            dprintf(CRITICAL,"[PROFILE] ------- load boot.img takes %d ms -------- \n", (int)get_timer(time_load_bootimg));
#endif
            break;

、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、

  /* 準備啟動linux kernel 跳轉到 boot_linux,正式拉起kernel;*/******************************************
    if (g_boot_hdr != NULL) {
        boot_linux((void *)g_boot_hdr->kernel_addr, (unsigned *)g_boot_hdr->tags_addr,
                   (char *)cmdline_get(), board_machtype(), (void *)g_boot_hdr->ramdisk_addr, g_rimg_sz);
    } else {
        boot_linux((void *)CFG_BOOTIMG_LOAD_ADDR, (unsigned *)CFG_BOOTARGS_ADDR,
                   (char *)cmdline_get(), board_machtype(), (void *)CFG_RAMDISK_LOAD_ADDR, g_rimg_sz);
    }

    while (1) ;

    return 0;
}

②、在kernel層:

在kernel-3.18\arch\arm\boot\compressed中:

/*
 * The C runtime environment should now be setup sufficiently.
 * Set up some pointers, and start decompressing.
 *   r4  = kernel execution address
 *   r7  = architecture ID
 *   r8  = atags pointer
 */
        mov    r0, r4
        mov    r1, sp            @ malloc space above stack
        add    r2, sp, #0x10000    @ 64k max
        mov    r3, r7
        bl    decompress_kernel
        bl    cache_clean_flush
        bl    cache_off
        mov    r1, r7            @ restore architecture number
        mov    r2, r8            @ restore atags pointer

在檔案kernel-3.18\arch\arm\boot\compressed\Misc.c中:

decompress_kernel(unsigned long output_start, unsigned long free_mem_ptr_p,unsigned long free_mem_ptr_end_p,int arch_id)
{
    int ret;

    __stack_chk_guard_setup();

    output_data        = (unsigned char *)output_start;
    free_mem_ptr        = free_mem_ptr_p;
    free_mem_end_ptr    = free_mem_ptr_end_p;
    __machine_arch_type    = arch_id;

    arch_decomp_setup();

    putstr("Uncompressing Linux...");  //解壓縮linux
    ret = do_decompress(input_data, input_data_end - input_data,
                output_data, error);
    if (ret)
        error("decompressor returned an error");
    else
        putstr(" done, booting the kernel.\n");
}

二、Linux核心啟動第一階段stage1

承接上文,這裡所以說的第一階段stage1就是核心解壓完成並出現Uncompressing Linux...done,booting the kernel.之後的階段。該部分程式碼實現在arch/arm/kernel【或者kernel-3.18\arch\arm64\kernel】的 head.S中,該檔案中的彙編程式碼通過查詢處理器核心型別和機器碼型別呼叫相應的初始化函式,再建立頁表,最後跳轉到start_kernel()函式開始核心的初始化工作。檢測處理器型別是在彙編子函式__lookup_processor_type中完成的。

以arm32位為例:通過以下程式碼可實現對它的呼叫:bl__lookup_processor_type(在檔案./arch/arm/kernel/head-commom.S實現)。__lookup_processor_type呼叫結束返回原程式時,會將返回結果儲存到暫存器中。其中r5暫存器返回一個用來描述處理器的結構體地址,並對r5進行判斷,如果r5的值為0則說明不支援這種處理器,將進入__error_pr8儲存了頁表的標誌位,r9 儲存了處理器的ID 號,r10儲存了與處理器相關的struct proc_info_list結構地址。Head.S核心程式碼如下:


  1. ENTRY(stext)  
  2. setmode PSR_F_BIT | PSR_I_BIT | SVC_MODE, r9 @設定SVC模式關中斷  
  3.       mrc p15, 0, r9, c0, c0        @ 獲得處理器ID,存入r9暫存器  
  4.       bl    __lookup_processor_type        @ 返回值r5=procinfo r9=cpuid  
  5.       movs      r10, r5                         
  6.  THUMB( it eq )        @ force fixup-able long branch encoding  
  7.       beq __error_p                   @如果返回值r5=0,則不支援當前處理器'  
  8.       bl    __lookup_machine_type         @ 呼叫函式,返回值r5=machinfo  
  9.       movs      r8, r5            @ 如果返回值r5=0,則不支援當前機器(開發板)  
  10. THUMB( it   eq )             @ force fixup-able long branch encoding  
  11.       beq __error_a                   @ 機器碼不匹配,轉__error_a並列印錯誤資訊  
  12.       bl    __vet_atags  
  13. #ifdef CONFIG_SMP_ON_UP    @ 如果是多核處理器進行相應設定  
  14.       bl    __fixup_smp  
  15. #endif  
  16.       bl    __create_page_tables  @最後開始建立頁表 
對應arm64位如下(其原理差不多):

ENTRY(stext)
    mov    x21, x0                // x21=FDT
    bl    el2_setup            // Drop to EL1, w20=cpu_boot_mode
    bl    __calc_phys_offset        // x24=PHYS_OFFSET, x28=PHYS_OFFSET-PAGE_OFFSET
    bl    set_cpu_boot_mode_flag
    mrs    x22, midr_el1            // x22=cpuid
    mov    x0, x22
    bl    lookup_processor_type
    mov    x23, x0                // x23=current cpu_table
    /*
     * __error_p may end up out of range for cbz if text areas are
     * aligned up to section sizes.
     */
    cbnz    x23, 1f                // invalid processor (x23=0)?
    b    __error_p
1:
    bl    __vet_fdt
    bl    __create_page_tables        // x25=TTBR0, x26=TTBR1

檢測機器碼型別是在彙編子函式__lookup_machine_type (同樣在檔案head-common.S實現) 中完成的。與__lookup_processor_type類似,通過程式碼:“bl __lookup_machine_type”來實現對它的呼叫。該函式返回時,會將返回結構儲存放在r5、r6 和r7三個暫存器中。其中r5暫存器返回一個用來描述機器(也就是開發板)的結構體地址,並對r5進行判斷,如果r5的值為0則說明不支援這種機器(開發板),將進入__error_a,打印出核心不支援u-boot傳入的機器碼的錯誤如圖2。r6儲存了I/O基地址,r7 儲存了 I/O的頁表偏移地址。 當檢測處理器型別和機器碼型別結束後,將呼叫__create_page_tables子函式來建立頁表,它所要做的工作就是將 RAM 基地址開始的1M 空間的實體地址對映到 0xC0000000開始的虛擬地址處。對本專案的開發板DM3730而言,RAM掛接到實體地址0x80000000處,當呼叫__create_page_tables 結束後 0x80000000 ~ 0x80100000實體地址將對映到 0xC0000000~0xC0100000虛擬地址處。當所有的初始化結束之後,使用如下程式碼來跳到C 程式的入口函式start_kernel()處,開始之後的核心初始化工作: bSYMBOL_NAME(start_kernel) 。 

可以通過:http://blog.csdn.net/shiyongyue/article/details/73785082  

三、Linux核心啟動第二階段stage2

核心的初始化過程由start_kernel函式開始,至第一個使用者程序init結束,呼叫了一系列的初始化函式對所有的核心元件進行初始化。其中,start_kernel、rest_init、kernel_init、init_post等4個函式構成了整個初始化過程的主線。

 從start_kernel函式開始

Linux核心啟動的第二階段從start_kernel函式開始。start_kernel是所有Linux平臺進入系統核心初始化後的入口函式,它主要完成剩餘的與硬體平臺相關的初始化工作,在進行一系列與核心相關的初始化後,呼叫第一個使用者程序- init 程序並等待使用者程序的執行,這樣整個 Linux核心便啟動完畢。該函式位於init/main.c檔案中,主要工作流程如圖3所示:

                                                                                 圖3 start_kernel流程圖

kernel3.18\init\main.c:

好一點的文章解釋路徑:

https://wenku.baidu.com/view/c933f4026c175f0e7cd137d4.html  start_kernel函式分析

asmlinkage __visible void __init start_kernel(void)
{
    char *command_line;  //用來存放bootloader存放過來的引數
    char *after_dashes;

    /*
     * Need to run as early as possible, to initialize the需要執行,儘可能早地初始化
     * lockdep hash:
     */

/* //死鎖檢測模組,於2006年引入核心-------來初始化hash表,這個hash表就是一個全域性的鎖鏈表,就是一個前後指向的指標結構體陣列,lock dependency雜湊表。個人理解是鎖的初始化,不再深入研究 */

lockdep_init()函式的主要作用是初始化鎖的狀態跟蹤模組。由於核心大量使用鎖來進行多程序多處理器的同步操作,死鎖就會在程式碼不合理的時候出現,但是要定位哪個鎖比較困難,用雜湊表可以跟蹤鎖的使用狀態。死鎖情況:一個程序遞迴加鎖同一把鎖;同一把鎖在兩次中斷中加鎖;幾把鎖形成閉環死鎖】

    lockdep_init();
    set_task_stack_end_magic(&init_task);
    /*

      * smp_setup_processor_id當只有一個CPU的時候這個函式就什麼都不做,

      * 但是如果有多個CPU的時候那麼它就返回在啟動的時候的那個CPU的號   

針對SMP處理器,用於獲取當前CPU的硬體ID,如果不是多核,函式為空

【判斷是否定義了CONFIG_SMP,如果定義了呼叫read_cpuid_mpidr讀取暫存器CPUID_MPIDR的值,就是當前正在執行初始化的

CPU ID,為了在初始化時做個區分,初始化完成後,所有處理器都是平等的,沒有主從】

      */  
    smp_setup_processor_id();

/*

//初始化雜湊桶(hash buckets)並將static object和Pool object放入poll

列表,這樣堆疊就可以完全操作了 

【這個函式的主要作用就是對除錯物件進行早期的初始化,就是HASH鎖和靜態物件池進行初始化,執行完後,object tracker已經開始完全運作了】


*/

    debug_objects_early_init();

    /*
     * Set up the the initial canary ASAP:
     */

//初始化堆疊保護的迦納利值,防止棧溢位攻擊的堆疊保護關鍵字

    boot_init_stack_canary();
/*

//cgroup_init_early()在系統啟動時初始化cgroups,同時初始化需要early_init的子系統

【這個函式作用是控制組(control groups)早期的初始化,控制組就是定義一組程序具有相同資源的佔有程度,比如,可以指定一組程序使用CPU為30%,磁碟IO為40%,網路頻寬為50%。目的就是為了把所有程序分配不同的資源。

*/
    cgroup_init_early();

    local_irq_disable();  /* 關閉當前CPU的所有中斷相應,操作CPSR暫存器,對應後面的 */  
    early_boot_irqs_disabled = true; //系統中斷關閉標誌,當early_init完畢後,會恢復中斷設定標誌為false


/*
 * Interrupts are still disabled. Do necessary setups, then
 * enable them  中斷仍然是禁用的。做必要的設定去使能他們
 */
/*

boot_cpu_init();設定當前引導系統的CPU在物理上存在,在邏輯上可以使用,並且初

始化準備好,即啟用當前CPU 

【在多CPU的系統裡,核心需要管理多個CPU,那麼就需要知道系統有多少個CPU

,在核心裡使用

cpu_present_map位圖表達有多少個CPU,每一位表示一個CPU的存在。如果是單個CPU,就是第0位設定為1。雖然系統裡有多個CPU存在,但是每個CPU不一定可以使用,或者沒有初始化,在核心使用cpu_online_map點陣圖來表示那些CPU可以執行核心程式碼和接受中斷處理。隨著移動系統的節能需求,需要對CPU進行節能處理,比如有多個CPU執行時可以提高效能,但花費太多電能,導致電池不耐用,需要減少執行的CPU個數,或者只需要一個CPU執行。這樣核心又引入了一個cpu_possible_map點陣圖,表示最多可以使用多少個CPU。在本函式裡就是依次設定這三個點陣圖的標誌,讓引導的

CPU物理上存在,已經初始化好,最少需要執行的CPU。】

*/
    boot_cpu_init();

/*

page_address_init();初始化高階記憶體的對映表 【在這裡引入了高階記憶體的概念,那麼什麼叫做高階記憶體呢?為什麼要使用高階記憶體呢?其實高階記憶體是相對於低端記憶體而存在的,那麼先要理解一下低端記憶體了。在32位的系統裡,最多能訪問的總記憶體是4G,其

中3G空間給應用程式,而核心只佔用1G的空間。因此,核心能對映的記憶體空間,只有1G大小,但實際上比這個還要小一些,大概是896M,另外128M空間是用來對映高階記憶體使用的。因此0到896M的記憶體空間,就叫做低端記憶體,而高於896M

的記憶體,就叫高階記憶體了。如果系統是64位系統,當然就沒未必要有高階記憶體存在了,因為64位有足夠多的地址空間給核心使用,訪問的記憶體可以達到10G

都沒有問題。在32位系統裡,核心為了訪問超過1G的實體記憶體空間,需要使用高階記憶體對映表。比如當核心需要讀取1G的快取資料時,就需要分配高階記憶體來使用,這樣才可以管理起來。使用高階記憶體之後,32位的系統也可以訪問達到64G記憶體。在移動作業系統裡,目前還沒有這個必要,最多才1G多記憶體】 


*/

    page_address_init();   /* 初始化頁地址,使用連結串列將其連結起來 */  
    pr_notice("%s", linux_banner);   /* 顯示核心的版本資訊 */  
     /*
       * 每種體系結構都有自己的setup_arch()函式,是體系結構相關的,具體編譯哪個
       * 體系結構的setup_arch()函式,由原始碼樹頂層目錄下的Makefile中的ARCH變數決定

       //★很重要的一個函式★ arch/arm/kernel/setup.c

       【核心架構相關初始化函式,是非常重要的一個初始化步驟。其中包含了處理器相關引數的初始化、核心啟動引數(tagged list)的獲取和前期處理、記憶體子系統的早期初始化(bootmem分配器)】

主要完成了4個方面的工作,一個就是取得MACHINE和PROCESSOR的資訊然或將他們賦值給kernel相應的全域性變數,然後呢是對boot_command_line和tags接行解析,再然後呢就是memory、cach的初始化,最後是為kernel的後續執行請求資源。

    */ 
    setup_arch(&command_line);

//每一個任務都有一個mm_struct結構來管理記憶體空間,init_mm是核心的mm_struct

    mm_init_cpumask(&init_mm);
    setup_command_line(command_line);//對comline進行備份和儲存
    setup_nr_cpu_ids(); //設定最多有多少個nr_cpu_ids結構

    //setup_per_cpu_areas()為系統中每個cpu的per_cpu變數申請空間,同時草被初始化段裡的資料
    setup_per_cpu_areas(); /* 每個CPU分配pre-cpu結構記憶體, 並複製.data.percpu段的資料 */  
    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();/*解析引數*/
    after_dashes = parse_args("Booting kernel",

                  static_command_line, __start___param,
                  __stop___param - __start___param,
                  -1, -1, &unknown_bootoption);//執行命令列解析,若引數不存在,則呼叫、、//unknown_bootoption
    if (!IS_ERR_OR_NULL(after_dashes))
        parse_args("Setting init args", after_dashes, NULL, 0, -1, -1,
               set_init_arg);

    jump_label_init();


    /*
     * These use large bootmem allocations and must precede
     * kmem_cache_init()
     */
    setup_log_buf(0);
    pidhash_init();   /* 初始化hash表,便於從程序的PID獲得對應的程序描述符指標 */
    vfs_caches_init_early(); /* 虛擬檔案系統的初始化 ,下面還有一個vfs_caches_init */  
    sort_main_extable();
    /*

    trap_init它的執行可以放到稍微後面一點,沒關係。
     trap_init函式完成對系統保留中斷向量(異常、非遮蔽中斷以及系統呼叫)的初始化,

     init_IRQ函式則完成其餘中斷向量的初始化
    */
    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.
     */
     /*
     設定排程程式開始之前的任何中斷(如定時器中斷)。完整的拓撲結構設定發生在smp_init()時間,
     但與此同時我們還有一個功能排程器
    */
    sched_init(); /* 程序排程器初始化 */ 
    /*
     * Disable preemption - early bootup scheduling is extremely
     * fragile until we cpu_idle() for the first time.
     */
     /*禁用搶佔——早期啟動排程是非常脆弱的,直到我們cpu_idle之後()的第一次出現。*/
    preempt_disable();   /* 禁止系統呼叫,即禁止核心搶佔 */  

    /*
      /* 檢查中斷是否已經開啟,如果已經開啟,則關閉中斷 */  
    */

    if (WARN(!irqs_disabled(),
         "Interrupts were enabled *very* early, fixing it\n"))
        local_irq_disable();
    idr_init_cache();
    /*
    RCU機制是Linux2.6之後提供的一種資料一致性訪問的機制,從RCU(read-copy-update)的名稱上看,
    我們就能對他的實現機制有一個大概的瞭解,在修改資料的時候,首先需要讀取資料,然後生成一個副本,
    對副本進行修改,修改完成之後再將老資料update成新的資料,此所謂RCU。
    在作業系統中,資料一致性訪問是一個非常重要的部分,通常我們可以採用鎖機制實現資料的一致性訪問。
      RCU(Read-Copy Update)是資料同步的一種方式,在當前的Linux核心中發揮著重要的作用。RCU主要針對的
    資料物件是連結串列,目的是提高遍歷讀取資料的效率,為了達到目的使用RCU機制讀取資料的時候不對連結串列進行
    耗時的加鎖操作。這樣在同一時間可以有多個執行緒同時讀取該連結串列,並且允許一個執行緒對連結串列進行修改
    (修改的時候,需要加鎖)。RCU適用於需要頻繁的讀取資料,而相應修改資料並不多的情景,例如在檔案系統中,
    經常需要查詢定位目錄,而對目錄的修改相對來說並不多,這就是RCU發揮作用的最佳場景。
    */
    rcu_init();     /* 初始化RCU(Read-Copy Update)機制,為了提高讀取資料的速率 ,即初始化互斥機制*/  
    context_tracking_init();

      radix_tree_init();
    /* init some links before init_ISA_irqs() */
    early_irq_init();/*中斷向量的初始化*/
    init_IRQ();   /*完成其餘中斷向量的初始化*/

/*

tick_init();

//初始化核心時鐘系統,tick control,呼叫clockevents_register_notifier

,就是監聽時鐘變化事件 

【這個函式主要作用是初始化時鐘事件管理器的回撥函式,比如當時鍾 裝置新增時處理。在核心裡定義了時鐘事件管理器,主要用來管理所有需要週期性地執行任務的裝置】

*/

    tick_init();/*初始化時鐘*/
    rcu_init_nohz();
    init_timers(); /* 初始化定時器相關的資料結構 */  
    hrtimers_init(); /* 對高精度時鐘進行初始化 */  
    softirq_init();  /* 初始化tasklet_softirq和hi_softirq */  
    timekeeping_init();
    time_init();  /* 初始化系統時鐘源 */ 
    sched_clock_postinit();
    perf_event_init();
    profile_init();/* 對核心的profile(一個核心效能調式工具)功能進行初始化 */  
    call_function_init();
    WARN(!irqs_disabled(), "Interrupts were enabled early\n");
    early_boot_irqs_disabled = false;
    local_irq_enable();

    kmem_cache_init_late();  /* slab初始化 */ 

    /*
     * 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.
     */
     //*攻擊警報!這是早期的。我們啟用控制檯之前我們做PCI設定等,和console_init()必須意識到這一點。但我們確實希望早點輸出,以防出現問題。
      /*

         * 初始化控制檯以顯示printk的內容,在此之前呼叫的printk

         * 只是把資料存到緩衝區裡

         */  
    console_init();;/*列印中斷的初始化*/
    if (panic_later)
        panic("Too many boot %s vars at `%s'", panic_later,

              panic_param);

    lockdep_info(); /* 如果定義了CONFIG_LOCKDEP巨集,則列印鎖依賴資訊,否則什麼也不做 */  

    /*
     * 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();
    page_ext_init();
    debug_objects_mem_init();
    kmemleak_init();
    setup_per_cpu_pageset();/*空函式*/
    numa_policy_init();/*空函式*/
    if (late_time_init)
        late_time_init();
    sched_clock_init();
    /*

       * 一個非常有趣的CPU效能測試函式,可以計算出CPU在1s內執行了多少次一個

       * 極短的迴圈,計算出來的值經過處理後得到BogoMIPS值(Bogo是Bogus的意思),

    */  
    calibrate_delay();/*校驗延時函式的精確度*/
    pidmap_init();/*程序號點陣圖初始化,一般用一個page來只是所有的程序PID佔用情況*/
    anon_vma_init();/*空函式*/
    acpi_early_init();
#ifdef CONFIG_X86
    if (efi_enabled(EFI_RUNTIME_SERVICES))
        efi_enter_virtual_mode();

#endif
#ifdef CONFIG_X86_ESPFIX64
    /* Should be run before the first non-init thread is created */
    init_espfix_bsp();
#endif
    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();/*CPU在記憶體中開闢快取記憶體,CPU直接訪問快取記憶體提以高速度。當cpu更新了快取記憶體的資料後,需要定期將快取記憶體的資料寫回到儲存介質中,比如磁碟和flash等。這個函式初始化寫回的週期*/
    proc_root_init();/*如果配置了proc檔案系統,則需初始化並載入proc檔案系統。在根目錄的proc資料夾就是proc檔案系統,這個檔案系統是ram型別的,記錄系統的臨時資料,系統關機後不會寫回到flash中*/
    cgroup_init();/*空函式*/
    cpuset_init();/*空函式*/
    taskstats_init_early();/*程序狀態初始化,實際上就是分配了一個儲存執行緒狀態的快取記憶體*/
    delayacct_init();/*空函式*/


     /*
       * 測試該CPU的各種缺陷,記錄檢測到的缺陷,以便於核心的其他部分以後可以
       * 使用它們的工作。
     */  
    check_bugs();/*測試CPU的缺陷,記錄檢測的缺陷,便於核心其他部分工作��需要*/

    acpi_subsystem_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 */
    //satrt kernel最後執行的初始化,建立初始化程序
    rest_init();
}

該函式所做