Linux內核分析實驗3——分析linux內核啓動過程

本文大量內容引用自孟寧老師在《LINUX操作系統分析》課程中的內容
《Linux內核分析》MOOC課程

http://www.xuetangx.com/courses/course-v1:ustcX+USTC001+_/about

環境的搭建參見上面的鏈接

在編譯源碼的過程中,gcc報錯,error: linux/compiler-gcc6.h: 沒有那個文件或目錄
百度之後,發現是因爲我的gcc太新導致了這個問題
解決方案見下面連接
http://blog.chinaunix.net/uid-20680966-id-5322921.html

linux源碼根目錄/init/main.c爲linux內核啓動文件
啓動函數爲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:
     */
    lockdep_init(); /*lockdep是一個內核調試模塊,用來檢查內核互斥機制(尤其是自旋鎖)潛在的死鎖問題。建立一個哈希表(hash tables),就是一個前後指向的指針結構體數組。 (函數的主要作用是初始化鎖的狀態跟蹤模塊。由於內核大量使用鎖來進行多進程多處理器的同步操作,死鎖就會在代碼不合理的時候出現,但是要定位哪個鎖比較困難,用哈希表可以跟蹤鎖的使用狀態。死鎖情況:一個進程遞歸加鎖同一把鎖;同一把鎖在兩次中斷中加鎖;幾把鎖形成閉環死鎖)*/
    set_task_stack_end_magic(&init_task); /*init_task即手工創建的PCB,0號進程即最終的idle進程*/
    smp_setup_processor_id(); /*針對SMP處理器,用於獲取當前CPU的硬件ID,如果不是多核,函數爲空 (判斷是否定義了CONFIG_SMP,如果定義了調用read_cpuid_mpidr讀取寄存器CPUID_MPIDR的值,就是當前正在執行初始化的CPU ID,爲了在初始化時做個區分,初始化完成後,所有處理器都是平等的,沒有主從)*/
    debug_objects_early_init(); /*初始化哈希桶(hash buckets)並將static object和pool object放入poll列表,這樣堆棧就可以完全操作了 (這個函數的主要作用就是對調試對象進行早期的初始化,就是HASH鎖和靜態對象池進行初始化,執行完後,object tracker已經開始完全運作了)*/

    /*
     * Set up the the initial canary ASAP:
     */
    boot_init_stack_canary(); /*canary值的是用於防止棧溢出攻擊的堆棧的保護字。初始化堆棧保護的值,防止棧溢出*/

    cgroup_init_early(); /*在系統啓動時初始化cgroups,同時初始化需要early_init的子系統 (這個函數作用是控制組(control groups)早期的初始化,控制組就是定義一組進程具有相同資源的佔有程度,比如,可以指定一組進程使用CPU爲30%,磁盤IO爲40%,網絡帶寬爲50%。目的就是爲了把所有進程分配不同的資源)*/

    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。)*/
    page_address_init();  /*初始化高端內存的映射表 (在這裏引入了高端內存的概念,那麼什麼叫做高端內存呢?爲什麼要使用高端內存呢?其實高端內存是相對於低端內存而存在的,那麼先要理解一下低端內存了。在32位的系統裏,最多能訪問的總內存是4G,其中3G空間給應用程序,而內核只佔用1G的空間。因此,內核能映射的內存空間,只有1G大小,但實際上比這個還要小一些,大概是896M,另外128M空間是用來映射高端內存使用的。因此0到896M的內存空間,就叫做低端內存,而高於896M的內存,就叫高端內存了。如果系統是64位系統,當然就沒未必要有高端內存存在了,因爲64位有足夠多的地址空間給內核使用,訪問的內存可以達到10G都沒有問題。在32位系統裏,內核爲了訪問超過1G的物理內存空間,需要使用高端內存映射表。比如當內核需要讀取1G的緩存數據時,就需要分配高端內存來使用,這樣纔可以管理起來。使用高端內存之後,32位的系統也可以訪問達到64G內存。在移動操作系統裏,目前還沒有這個必要,最多才1G多內存)*/
    pr_notice("%s", linux_banner); /*輸出各種信息(Linux_banner是在kernel/init/version.c中定義的,這個字符串是編譯腳本自動生成的)*/
    setup_arch(&command_line); /*很重要的一個函數arch/arm/kernel/setup.c
(內核架構相關初始化函數,是非常重要的一個初始化步驟。其中包含了處理器相關參數的初始化、內核啓動參數(tagged list)的獲取和前期處理、內存子系統的早期初始化(bootmem分配器))*/
    mm_init_cpumask(&init_mm); /*每一個任務都有一個mm_struct結構來管理內存空間,init_mm是內核的mm_struct*/
    setup_command_line(command_line); /*對cmdline進行備份和保存*/
    setup_nr_cpu_ids(); /*設置最多有多少個nr_cpu_ids結構*/
    setup_per_cpu_areas(); /*爲系統中每個CPU的per_cpu變量申請空間,同時拷貝初始化段裏數據(.data.percpu)*/
    smp_prepare_boot_cpu();    /* arch-specific boot-cpu hooks */ /*爲SMP系統裏引導CPU(boot-cpu)進行準備工作。在ARM系統單核裏是空函數*/

    build_all_zonelists(NULL, NULL); /*設置內存管理相關的node(節點,每個CPU一個內存節點)和其中的zone(內存域,包含於節點中,如)數據結構,以完成內存管理子系統的初始化,並設置bootmem分配器*/
    page_alloc_init(); /*設置內存頁分配通知器*/

    pr_notice("Kernel command line: %s\n", boot_command_line);
    parse_early_param(); /*解析cmdline中的啓動參數*/
    after_dashes = parse_args("Booting kernel", 
                  static_command_line, __start___param,
                  __stop___param - __start___param,
                  -1, -1, &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); /*使用bootmeme分配一個記錄啓動信息的緩衝區*/
    pidhash_init(); /*進程ID的HASH表初始化,用bootmem分配並初始化PID散列表,由PID分配器管理空閒和已指派的PID,這樣可以提供通PID進行高效訪問進程結構的信息。LINUX裏共有四種類型的PID,因此就有四種HASH表相對應。*/
    vfs_caches_init_early(); /*前期虛擬文件系統(vfs)的緩存初始化*/
    sort_main_extable(); /*對內核異常表(exception table)按照異常向量號大小進行排序,以便加速訪問*/
    trap_init(); /*對內核陷阱異常進行初始化,在ARM系統裏是空函數,沒有任何的初始化。內有set_system_trap_gate(SYSCALL_VECTOR, &SYSTEM_CALL)*/
    mm_init(); 
/*標記哪些內存可以使用,並且告訴系統有多少內存可以使用,當然是除了內核使用的內存以外
(初始化內核內存分配器,包括六個子函數
1、page_cgroup_init_flatmem();獲取page_cgroup所需內存
2、mem_init;關閉並釋放bootmem分配器,打印內存信息,內核啓動時看到Virtual kernel memory layout:的信息就是這個函數的

3、kmem_cache_init();初始化slab分配器
4、percpu_init_late();PerCPU變量系統後期初始化
5、pgtable_cache_init();也表緩存初始化,arm中是個空函數 6、vmalloc_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(); /*對進程調度器的數據結構進行初始化,創建運行隊列,設置當前任務的空線程,當前任務的調度策略爲CFS調度器 */
    /*
     * 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(); /*爲IDR機制分配緩存,主要是爲 structidr_layer結構體分配空間*/
    rcu_init(); /*初始化直接讀拷貝更新的鎖機制。 Read-Copy Update (RCU主要提供在讀取數據機會比較多,但更新比較的少的場合,這樣減少讀取數據鎖的性能低下的問題。)*/
    context_tracking_init();
    radix_tree_init(); /*內核radis 樹算法初始化*/
    /* init some links before init_ISA_irqs() */
    early_irq_init(); /*前期外部中斷描述符初始化,主要初始化數據結構*/
    init_IRQ(); /*對應架構特定的中斷初始化函數,在ARM中就是machine_desc->init_irq(),就是運行設備描述結構體中的init_irq函數[arch/arm/mach-msm/board-xxx.c]*/
    tick_init(); /*初始化內核時鐘系統,tick control,調用clockevents_register_notifier,就是監聽時鐘變化事件 (這個函數主要作用是初始化時鐘事件管理器的回調函數,比如當時鍾設備添加時處理。在內核裏定義了時鐘事件管理器,主要用來管理所有需要週期性地執行任務的設備)*/
    rcu_init_nohz();
    init_timers(); /*初始化引導CPU的時鐘相關的數據結構,註冊時鐘的回調函數,當時鍾到達時可以回調時鐘處理函數,最後初始化時鐘軟件中斷處理*/
    hrtimers_init(); /*初始化高精度的定時器,並設置回調函數。*/
    softirq_init(); /*初始化軟件中斷,軟件中斷與硬件中斷區別就是中斷髮生時,軟件中斷是使用線程來監視中斷信號,而硬件中斷是使用CPU硬件來監視中斷。*/
    timekeeping_init(); /*初始化系統時鐘計時,並且初始化內核裏與時鐘計時相關的變量。*/
    time_init(); /*初始化系統時鐘。開啓一個硬件定時器,開始產生系統時鐘就是system_timer的初始化,arch/arm/mach-msm/board-*.c */
    sched_clock_postinit();
    perf_event_init(); /*CPU性能監視機制初始化,此機制包括CPU同一時間執行指令數,cache miss數,分支預測失敗次數等性能參數*/
    profile_init(); /*分配內核性能統計保存的內存,以便統計的性能變量可以保存到這裏*/
    call_function_init(); /*初始化所有CPU的call_single_queue,同時註冊CPU熱插拔通知函數到CPU通知鏈中*/
    WARN(!irqs_disabled(), "Interrupts were enabled early\n"); /*對應前面的local_irq_disable() (打開本CPU的中斷,也即允許本CPU處理中斷事件,在這裏打開引CPU的中斷處理。如果有多核心,別的CPU還沒有打開中斷處理。)*/
    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.
     */
    console_init(); /*初始化控制檯,從這個函數之後就可以輸出內容到控制檯了。在這個函數初化之前,都沒有辦法輸出內容,就是輸出,也是寫到輸出緩衝區裏,緩存起來,等到這個函數調用之後,就立即輸出內容*/
    if (panic_later)
        panic("Too many boot %s vars at `%s'", 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(); /*測試鎖的API是否使用正常,進行自我測試。比如測試自旋鎖、讀寫鎖、一般信號量和讀寫信號量。*/

#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 /*檢查initrd的位置是否符合要求,也就是判斷傳遞進來initrd_start對應的物理地址是否正常,如果有誤就打印錯誤信息,並清零initrd_start。*/
    page_cgroup_init(); /*初始化容器組的頁面內存分配,mem_cgroup是cgroup體系中提供的用於memory隔離的功能,*/
    debug_objects_mem_init(); /*這個函數是創建調試對象內存分配初始化,所以緊跟內存緩存初始化後面*/
    kmemleak_init(); /*內核內存泄漏檢測機制初始化;*/
    setup_per_cpu_pageset(); /*創建每個CPU的高速緩存集合數組並初始化,此前只有啓動頁組。因爲每個CPU都不定時需要使用一些頁面內存和釋放頁面內存,爲了提高效率,就預先創建一些內存頁面作爲每個CPU的頁面集合。*/
    numa_policy_init(); /*初始化NUMA的內存訪問策略。所謂NUMA,它是NonUniform Memory AccessAchitecture(非一致性內存訪問)的縮寫,主要用來提高多個CPU訪問內存的速度。因爲多個CPU訪問同一個節點的內存速度遠遠比訪問多個節點的速度來得快。*/
    if (late_time_init)
        late_time_init(); /*主要運行時鐘相關後期的初始化功能。*/
    sched_clock_init(); /*對每個CPU進行系統進程調度時鐘初始化*/
    calibrate_delay(); /*主要計算CPU需要校準的時間,這裏說的時間是CPU執行時間。如果是引導CPU,這個函數計算出來的校準時間是不需要使用的,主要使用在非引導CPU上,因爲非引導CPU執行的頻率不一樣,導致時間計算不準確。BogoMIPS值,也是衡量cpu性能的標誌*/
    pidmap_init(); /*進程位圖初始化,一般情況下使用一頁來表示所有進程佔用情況。*/
    anon_vma_init(); /*初始化反向映射的匿名內存,提供反向查找內存的結構指針位置,快速地回收內存。*/
    acpi_early_init(); /*這個函數是初始化ACPI電源管理。高級配置及電源接口(Advanced Configuration and Power Interface)ACPI規範介紹ACPI能使軟、硬件、操作系統(OS),主機板和外圍設備,依照一定的方式管理用電情況,系統硬件產生的Hot-Plug事件,讓操作系統從用戶的角度上直接支配即插即用設備,不同於以往直接通過基於BIOS 的方式的管理。*/
#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 /*初始化EFI的接口,並進入虛擬模式。EFI是ExtensibleFirmware Interface的縮寫,就是INTEL公司新開發的BIOS接口。*/
    thread_info_cache_init(); /*線程信息(thread_info)的緩存初始化。*/
    cred_init(); /*任務信用系統初始化 */
    fork_init(totalram_pages); /*根據當前物理內存計算出來可以創建進程(線程)的最大數量,並進行進程環境初始化,爲task_struct分配空間。*/
    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(); /*頁面寫機制初始化 */
    proc_root_init(); /*初始化系統進程文件系統,主要提供內核與用戶進行交互的平臺,方便用戶實時查看進程的信息。*/
    cgroup_init(); /*進程控制組正式初始化,主要用來爲進程和其子程提供性能控制。比如限定這組進程的CPU使用率爲20% */
    cpuset_init(); /*初始化CPUSET,CPUSET主要爲控制組提供CPU和內存節點的管理的結構。*/
    taskstats_init_early(); /*任務狀態早期初始化,爲結構體獲取高速緩存,並初始化互斥機制。任務狀態主要向用戶提供任務的狀態信息。*/
    delayacct_init(); /*任務延遲機制初始化,初始化每個任務延時計數。當一個任務等CPU運行,或者等IO同步時,都需要計算等待時間。*/

    check_bugs(); /*檢查CPU配置、FPU等是否非法使用不具備的功能,檢查CPU BUG,軟件規避BUG*/

    sfi_init_late(); /*SFI 初始程序晚期設置函數*/

    if (efi_enabled(EFI_RUNTIME_SERVICES)) {
        efi_late_init();
        efi_free_boot_services();
    }

    ftrace_init(); /*功能跟蹤調試機制初始化,初始化內核跟蹤模塊,ftrace的作用是幫助開發人員瞭解Linux 內核的運行時行爲,以便進行故障調試或性能分析 function trace.*/

    /* Do the rest non-__init'ed, we're now alive */
    rest_init(); /*剩餘的初始化,至此,內核已經開始工作了。在rest_init()中,有kernel_init(),通過調用run_init_process,產生了第一個用戶態進程,1號進程,默認在根目錄下。rest_init()中,cpu_starup_entry函數調用cpu_idle_loop使得init_task空閒爲idle進程,即0號進程*/
}

該代碼分析複製於如下博客
https://www.shiyanlou.com/courses/reports/982406
原文作者好厲害,我等弱雞隻有拜服

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章