本文大量內容引用自孟寧老師在《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
原文作者好厲害,我等弱雞隻有拜服