kernel啟動流程第二階段
整個流程基本為從網上大牛的分享結合自己的理解所述,部分引用可能未貼上連結。
Kernel啟動流程中的Tips:
1、 Kernel一般會存在於儲存裝置上,比如FLASH\EMMC\SDCARD. 因此,需要先將kernel映象載入到RAM的位置上,CPU才可以去訪問到kernel。
但是注意,載入的位置是有要求的,一般是載入到物理RAM偏移0x8000的位置,也就是要在前面預留出32K的RAM。kernel會從載入的位置上開始解壓,而kernel前面的32K空閒RAM中,16K作為boot params,16K作為臨時頁表
2、 Arch/arm/kernel/head.S(kernel的入口函式)
3、 bootloader
Some標記:
1)asmlinkage:表示函式不從暫存器傳遞參數,而是從棧傳遞
2)__visible:Tell the optimizer thatsomething else uses this function or variable
由__init*修飾的函式或者變數,只在開機階段使用,佔用的空間在系統完成後自行釋放。
3)__init:標記核心啟動時使用的初始化程式碼,核心啟動完成後不再需要。以此標記的程式碼位於.init.text記憶體區域
4)__initdata:標記核心啟動時使用的初始化程式碼,核心啟動完成後不再需要。以此標記的程式碼位於
Start_Kernel():核心啟動第二階段入口 init/main.c
重要的點:
1、核心啟動引數的獲取和處理;
2、setup_arch(&command_line)函式;
3、記憶體管理的初化(從bootmem到slab);
4、rest_init()函式
asmlinkage __visible void__init start_kernel(void)
1、lockdep_init();kernel/kernel3.18/include/linux/lockdep.h
do{}while(0)什麼都沒做,作用暫未知。
2、set_task_stack_end_magic(&init_task);kernel/kernel3.18/kernel/fork.c
init_task:
1)為init_task_union結構體的成員,為系統的第一個程序,PID為0,唯一一個不用fork()建立的程序。其使用靜態建立的方式生成,start_kernel
之前的彙編程式碼到start_kernel
執行,這裡都會納入idle程序的上下文(之前的彙編程式碼就是為了idle程序的執行做準備)。
2) 由巨集“INIT_TASK(tsk)”初始化,方式:為task_struct結構體中的每個成員直接賦值
3)init程序,PID為1,在rest_init()中才被建立。
該函式主要用於指定init_task執行緒堆疊的結尾,設定標記STACK_END_MAGIC(防溢位操作)
3、smp_setup_processor_id();kernel/kernel3.18/arch/arm/kernel/setup.c
設定smp模型的處理器ID;SMP(多對稱處理模型),指多個CPU之間地位平等,共享所有匯流排、記憶體、I/O,但也因此會造成搶佔資源的問題。
4、boot_init_stack_canary();kernel/kernel3.18/arch/arm/asm/stackprotector.h
該函式也用於防止棧溢位(不是堆疊,是棧),該函式在區域性變數和儲存的指令指標之間設定一個標記位(canaryword)。當該位被修改後,即可探測到溢位,最後呼叫__stack_chk_fail函式,丟出錯誤退出程序。
參考:https://www.ibm.com/developerworks/cn/linux/l-cn-gccstack/
上圖2中的EBP(堆疊幀指標,ESP為棧頂指標)和返回地址已經被覆蓋,如果能在執行時檢測出這種破壞,就有可能對函式棧進行保護。目前的堆疊保護實現大多使用基於 “Canaries” 的探測技術來完成對這種破壞的檢測。詳細見上述地址。
5、cgroup_init_early();kernel/kernel3.18/kernel/cgoup.c
我們把每種資源叫做子系統,比如CPU子系統,記憶體子系統。為什麼叫做子系統呢,因為它是從整個作業系統的資源衍生出來的。然後我們建立一種虛擬的節點,叫做cgroup(controlgroup,一組程序的行為控制)。
程序分組css_set,不同層級中的節點cgroup也都有了。那麼,就要把節點cgroup和層級進行關聯,和資料庫中關係表一樣。這個事一個多對多的關係。為什麼呢?首先,一個節點可以隸屬於多個css_set,這就代表這這批css_set中的程序都擁有這個cgroup所代表的資源。其次,一個css_set需要多個cgroup。因為一個層級的cgroup只代表一種或者幾種資源,而一般程序是需要多種資源的集合體。
Cgroup機制結構圖如下:
結構體css_set中最重要的成員就是cgroup_subsys_statesubsys[]陣列以及structcgroup_subsys_state *subsys[CGROUP_SUBSYS_COUNT];
task_struct->css_set->cgroup_subsys_state->cgroup反映了程序與cgroup之間的連線關係。
即該函式就是初始化這個cgroup機制。
6、local_irq_enable();關閉中斷(底層調用匯編指令)
7、boot_cpu_init();初始化第一個CPU,並將當前CPU設定為啟用狀態
8、page_address_init();
頁地址初始化的作用,未定義高階記憶體,程式碼為do{}while(0),不需要做任何操作,具體參考上述網址。
9、setup_arch(&command_line);
核心啟動最重要的部分
主要完成四個工作:
A)取得machine和processor的資訊,然後賦值給kernel相應的全域性變數
B)對boot_command_line和tags進行解析
C)memory和cache的初始化
D)為kernel後續執行請求資源
1. /*核心通過machine_desc結構體來控制系統體系架構相關部分的初始化。
2. machine_desc結構體通過MACHINE_START巨集來初始化,在程式碼中,
3. 通過在start_kernel->setup_arch中呼叫setup_machine_fdt來獲取。*/
每種體系結構都有自己的setup_arch()函式,是體系結構相關的,具體編譯哪個體系結構的setup_arch()函式,由原始碼樹頂層目錄下的Makefile中的ARCH變數決定
{
1)setup_processor();
首先從暫存器中讀取cpuid,之後呼叫lookup_processor_type來取得proc_info_list
lookup_processor_type定義在head-common.S中,其實就是呼叫
__lookup_processor_type
2)mdesc =setup_machine_fdt(__atags_pointer);
主要工作:獲取machine_desc結構體以及uboot傳來的引數。
__atags_pointer即Uboot呼叫kernel時傳遞的第三個引數DTB首地址。如何指向的?
通過搜尋該關鍵字,可在head-common.S中找到其定義,在物件__mmap_switched_data中,該物件被__mmap_switched呼叫,具體操作是通過將r3表示地址(__mmap_switched_data)中的內容依次存入相應的暫存器(r4~r7)中,在程式碼中,__atags_pointer地址被存入r6,之後再將uboot傳來的第三個引數(第三個引數,所以在r2中)暫存器r2中的值賦值給r6,即實現__atags_pointer指向DTB首地址。
str r2, [r6]
驗證DTB的有效性,主要為FDT的magic(參考kernel啟動流程第一階段的__vet_args)
呼叫of_flat_dt_match_machine介面:先獲取DTB的根節點,通過迴圈遍歷後續的節點進行匹配,找出最接近machine_desc結構.
呼叫early_init_dt_scan_chosen介面:掃描“/chosen/”下的節點,包括獲取initrd相關的資訊;bootargs相關的資訊存入變數boot_command_line。
呼叫early_init_dt_scan_root介面:依據屬性“#size-cells”獲取相關資訊。
3)setup_machine_tags():
若步驟2返回的mdesc結構為空(只有在驗證時就失敗返回的情況),則以__machine_arch_type為依據重新進行一次查詢mdesc和boot_command_line。若仍未找到,則陷入死迴圈。
4)結構struct mm_struct管理程序的虛擬地址空間,所有核心執行緒都使用共同的地址空間,因為他們都是用相同的地址對映,這個地址空間由init_mm來描述。此處對init_mm結構體進行初始化,其中_text和_etext表示核心映象程式碼的起始位置和結束位置,_etext 到_edata之間是已初始化資料段。_edata到_end時未初始化資料段等。_end後便是堆區。ps:每一個任務都有一個mm_struct結構以管理記憶體空間,init_mm是核心自身的mm_struct
5)parse_early_param:
看程式碼parse_one()中param為“early option”而params為NULL,所以理應在parameq判斷條件時就返回了,最後直接呼叫傳進來的函式呼叫(do_early_param)。
parse_early_param===>parse_one===>do_early_param===>proc_info_list.setup_func()
內容及原理比較混亂。
6)early_paging_init:
核心就是呼叫之前找到的mdesc中的init_meminfo()成員。
7)setup_dma_zone:
設定DMA區域的大小
8)arm_memblock_init():
將所有記憶體塊新增到全域性變數memblock中,該變數為結構體structmemblock型別,結構體memblock中存在兩種型別的結構體,分別用於儲存已使用的記憶體區(型別為reserve)和未使用的記憶體區(型別為memory)。其中,將已使用的記憶體(長度:_end- _stext)新增到reserve區中。
Linux核心使用夥伴系統管理記憶體,那麼在夥伴系統工作前,如何管理記憶體?答案是memblock;
memblock在系統啟動階段進行簡單的記憶體管理,記錄實體記憶體的使用情況;
9)sanity_check_meminfo():
掃描各個記憶體塊,檢測低端記憶體的最大值arm_lowmem_limit,設定高階記憶體起始值的虛擬地址high_memory
10)paging_init(mdesc);
核心建立頁表,初始化自舉分配器。
11)request_standard_resources(mdesc);
核心中將許多物理資源用struct resource結構來管理,該函式就是將IO記憶體作為resource註冊到核心
12)unflatten_device_tree();
解析FDT建立裝置樹節點。
後續暫留
}
10、mm_init_cpumask(&init_mm)
初始化CPU遮蔽字
mm.owner = &init_task
11、setup_command_line();
對cmdline進行備份和儲存,均為分配的記憶體:
1、儲存未改變的comand_line到字元陣列static_command_line[] 中。
2、儲存 boot_command_line到字元陣列saved_command_line[]中
3、 初始化initcall_command_line變數用於後續使用
其中,command_line由boot_command_line拷貝得到,在setup_arch中進行此操作。
12、setup_nr_cpu_ids();
參考:http://blog.csdn.net/yin262/article/details/46778013
nr_cpu_ids全域性變數被宣告為__read_mostly屬性。
nr_cpu_ids儲存的是所有可處於聯機狀態的CPU總數。
nr_cpu_ids具有當前系統能具備的CPU數的資訊,預設值為NR_CPUS值,NR_CPUS是編譯時使用者可設定的常量值。NR_CPUS並非當前系統記憶體在的CPU的數值,而是Linux核心能支援的最大CPU數的最大值。
三種系統中UP(Uni-Processor)中是1,32位SMP中具有2~32的值,64位核心中具有2~64位的值。
其中:
cpu_possible_mask:系統內可安裝的最多CPU的點陣圖
cpu_online_mask:系統內安裝的CPU中,正在使用的CPU的點陣圖
cpu_present_mask:系統內安裝的CPU的點陣圖
cpu_active_mask:處於聯機狀態且可以動的(migration)的CPU的位圖
13、setup_per_cpu_areas();
setup_per_cpu_areas是為了對核心的記憶體管理(mm)進行初始化而呼叫的函式之一。只在SMP系統中呼叫,UP中不執行任何操作。
setup_per_cpu_areas函式為SMP的每個處理器生成per-cpu資料。
per-cpu資料按照不同的CPU類別使用,以將效能低下引發的快取一致性(cachecoherency)問題減小到最小。per-cpu資料由各cpu獨立使用,即使不鎖也可訪問,十分有效。
系統中維護的per_cpu資料是一個數據結構陣列,其中每一個元素即每一個數據結構僅供一個CPU使用,不用擔心競爭。
__per_cpu_offset陣列中記錄了每個CPU的percpu區域的開始地址。我們訪問每CPU變數就要依靠__per_cpu_offset中的地址。
14、smp_prepare_boot_cpu();do nothing
15、build_all_zonelists();
建立系統記憶體頁區(zone)連結串列
用於初始化結點和記憶體域。該函式首先呼叫__build_all_zonelists,此函式遍歷系統的每個記憶體結點(此處,遍歷時每個節點pgdat= NODE_DATA(nid),最終為pgdat = &contig_page_data,從哪裡體現出節點的不同,最終指向的都是同一個變數,why?),針對每個記憶體結點呼叫build_zonelists(),該函式的任務是在當前處理的結點和系統中其他結點的記憶體域之間建立一種等級次序。接下來,依據這種次序分配記憶體,如果在期望的結點記憶體域中,沒有空閒記憶體,就去查詢相鄰結點的記憶體域。核心定義了記憶體的一個層次結構關係,首先試圖分配廉價的記憶體,如果失敗,則根據訪問速度和容量,逐漸嘗試分配更昂貴的記憶體。
該函式接下來計算所有剩餘的記憶體頁,存放到全域性變數vm_total_pages中。接下來如果空閒記憶體頁太少,則關閉頁的可遷移性,即page_group_by_mobility_disabled置位。頁的可遷移性是核心為了避免記憶體碎片而在linux2.6.24中加入的新特性。該特性把記憶體頁分為不可移動記憶體頁、可回收頁和可移動頁三種類型來避免記憶體碎片。
16、page_alloc_init();
記憶體頁初始化
為系統設定一個page_alloc_cpu_notify回撥函式,該函式用來實現CPU的關閉與使能。在一個MPP結構的處理器系統或者大型伺服器中有大量的CPU,該函式可以臨時開啟或者關閉某些Core或者CPU,此時Linux系統會呼叫page_alloc_cpu_notify函式。
17、列印boot_command_line
18、parse_early_param();
解析早期格式的核心引數
19、parse_args("Bootingkernel", static_command_line, __start___param,
__stop___param - __start___param, -1, -1, &unknown_bootoption);
參考:http://blog.csdn.net/funkunho/article/details/51967137
函式對Linux啟動命令列引數進行再分析和處理,
當不能夠識別前面的命令時所呼叫的函式。
系統的所有引數都是由__setup(str,fn)和early_param(str,fn)巨集定義的,兩個巨集都由一個巨集實現:__setup_param(),實現如下:
#define __setup(str,fn) /
__setup_param(str, fn, fn, 0)
#define early_param(str,fn) /
__setup_param(str, fn, fn, 1)
可見唯一區別為最後的early標記是0還是1。為1則為early param,其表示需要在其他核心選項之前被處理(處理操作為呼叫18、 parse_early_param),若為0則由parse_args處理。
__setup()巨集用於定義一個結構體(obs_kernel_param),並指定其放入“.init.setup”段,段頭段尾分別為(__setup_start和__setup_end),因此解析引數的時候即可以通過從頭遍歷到尾,將其中的標記提取出來用來判斷是否是early引數,之後對符合的引數,提取其結構體中的函式(定義時傳入的fn)直接呼叫。
20、jump_label_init();
處理靜態定義在跳轉標號
21、setup_log_buf(0);
使用memblock_alloc分配一個啟動時log緩衝區
22、pidhash_init(); // 初始化pid散列表
設定Pid散列表的原因是:通過遍歷程序雙向連結串列來找到指定的程序效率太低,因此再維護一個hash表來提高查詢效率
第一:分配系統支援最大的hash_table
,以及長度,在此期間核心動態的為4個散列表分配空間,分別是PID(程序ID),TGID(執行緒組領頭程序的PID),PGID(程序組領頭程序的PID),SID(會話領頭程序的PID)。第二:根據得到的長度初始化pid_hash.
第三:hash表可能存在衝突的問題,因此在得到pid_hash後,初始化連結串列來解決衝突的問題。
23、vfs_caches_init_early(); // 初始化dentry和inode的hashtable
兩個hash表的初始化的方式和pidhash_init()如出一轍,都是先分配空間,初始化hash表,再將對應的連結串列初始化。此函式為前期步驟,後續初始化為函式68。
24、sort_main_extable(); // 對核心異常向量表進行排序
25、trap_init(); // 對核心陷阱異常進行初始化,arm沒有用到
26、mm_init(); // 初始化核心記憶體分配器,過度到夥伴系統,啟動
//slab機制,初始化非連續記憶體區
參考:http://blog.csdn.net/sunlei0625/article/details/58594542
全域性變數mem_map:描述系統中所有的實體記憶體採用的struct page結構的陣列的基指標。比如說,對於4GB的記憶體(2^32)來說,如果一個頁定義為4KB,即2^12位元組。那麼可想而知,總共這個mem_map陣列大小為2^20個。
這些頁都有一個具體的頁幀號與之對應。頁幀號一般用pfn來表示,那麼由於每個頁都有一個頁幀號,那最小的頁幀號和最大的頁幀號為多少呢?需要特別注意的是,頁幀號也是與mem_map陣列的index相對應。我們一般認為pfn_min為0,而最大pfn_max為mem_map陣列下標的最大值,這個最大值也就是max_pfn,這個值跟核心的max_mapnr相對應。
函式set_max_mapnr()就是用於計算max_mapnr。max_mapnr是在setup_arch的paging_init()中呼叫bootmem_init()來設定的。在成功設定max_mapnr後,我們要把啟動過程時所有的空閒記憶體釋放到夥伴系統,這裡需要注意三點:
一. bootmem記憶體管理或者nobootmem管理
二. memblock記憶體管理
三. 夥伴系統
27、sched_init(); // 初始化程序排程器
根據系統的配置情況,分配空間並初始化root_task_group中的排程實體或排程佇列指標。
將root_task_group新增到task_groups鏈表
。。。一系列初始化
init_idle(current,smp_processor_id()); // 將當前程序,即init_task設定為idle進程
current->sched_class = &fair_sched_class; // 設定當前程序,即init_task程序採用CFS排程策略
28、preempt_disable(); // 禁止核心搶佔
if (WARN(!irqs_disabled(), "Interrupts wereenabled *very* early, fixing it\n"))
local_irq_disable(); // 關閉本地中斷
29、idr_init_cache(); // 建立idr(整數id管理機制)快取記憶體
IDR機制原理:
IDR機制適用在那些需要把某個整數和特定指標關聯在一起的地方。例如,在IIC匯流排中,每個裝置都有自己的地址,要想在總線上找到特定的裝置,就必須要先發送裝置的地址。當介面卡要訪問總線上的IIC裝置時,首先要知道它們的ID號,同時要在核心中建立一個用於描述該裝置的結構體,和驅動程式
將ID號和裝置結構體結合起來,如果使用陣列進行索引,一旦ID號很大,則用陣列索引會佔據大量記憶體空間。這顯然不可能。或者用連結串列,但是,如果匯流排中實際存在的裝置很多,則連結串列的查詢效率會很低。
此時,IDR機制應運而生。該機制內部採用紅黑樹實現,可以很方便的將整數和指標關聯起來,並且具有很高的搜尋效率
30、rcu_init(); // 初始化rcu機制(讀-寫-拷貝)
31、trace_init(); //初始化系統的trace功能
32、context_tracking_init();//
33、radix_tree_init(); // 初始化核心基數樹
/* init some links before init_ISA_irqs() */
Linuxradix樹最廣泛的用途是用於記憶體管理,結構address_space通過radix樹跟蹤繫結到地址對映上的核心頁,該radix樹允許記憶體管理程式碼快速查詢標識為dirty或writeback的頁。
Linux基數樹(radix tree)是將指標與long整數鍵值相關聯的機制,它儲存有效率,並且可快速查詢,用於指標與整數值的對映(如:IDR機制)、記憶體管理等。
34、early_irq_init(); // arm64沒有用到
35、init_IRQ(); // 初始化中斷
參考:http://blog.chinaunix.net/uid-12567959-id-160975.html
start_kernel()函式呼叫trap_init()、early_irq_init()和init_IRQ()三個函式來初始化中斷管理系統。
1)trap_init()在arm平臺下為空。
2)early_irq_init()函式在kernel/handle.c檔案中根據核心配置時是否選擇了CONFIG_SPARSE_IRQ,而可以選擇兩個不同版本的該函式中的一個進行編譯。CONFIG_SPARSE_IRQ配置項,用於支援稀疏irq號,對於發行版的核心很有用,它允許定義一個高CONFIG_NR_CPUS值,但仍然不希望消耗太多記憶體的情況。
主要工作即為初始化用於管理中斷的irq_desc[NR_IRQS]陣列的每個元素,它主要設定陣列中每一個成員的中斷號,使得陣列中每一個元素的kstat_irqs欄位(irq stats per cpu),指向定義的二維陣列中的對應的行。
alloc_desc_masks(&desc[i], 0, true)和init_desc_masks(&desc[i])函式在非SMP平臺上為空函式。arch_early_irq_init()在主要用於x86平臺和PPC平臺,其他平臺上為空函式。
3)init_IRQ(void)函式是一個特定於體系結構的函式,這個函式將irq_desc[NR_IRQS]結構陣列各個元素的狀態欄位設定為IRQ_NOREQUEST| IRQ_NOPROBE,也就是未請求和未探測狀態。然後呼叫特定機器平臺的中斷初始化init_arch_irq()函式。
36、tick_init(); // 初始化時鐘滴答控制器
內部呼叫兩個函式:tick_broadcast_init()和tick_nohz_init()
1)Linux的broadcasttimer機制:防止系統休眠時(CPU深度休眠也會關閉local timer),localtimer也休眠導致無法喚醒本CPU,所以添加了一個broadcast timer機制,用於在cpu休眠時負責喚醒。
2)nohz時鐘機制:
Linux中的時鐘事件都是由一個週期時鐘提供,不管系統中的clock_event_device是工作於週期觸發模式,還是工作於單觸發模式,也不管定時器系統是工作於低解析度模式,還是高精度模式,核心都竭盡所能,用不同的方式提供週期時鐘,以產生定期的tick事件,tick事件或者用於全域性的時間管理(jiffies和時間的更新),或者用於本地cpu的程序統計、時間輪定時器框架等等。週期性時鐘雖然簡單有效,但是也帶來了一些缺點,尤其在系統的功耗上,因為就算系統目前無事可做,也必須定期地發出時鐘事件,啟用系統。為此,核心的開發者提出了動態時鐘這一概念,我們可以通過核心的配置項CONFIG_NO_HZ來啟用特性。有時候這一特性也被叫做tickless,不過還是把它稱呼為動態時鐘比較合適,因為並不是真的沒有tick事件了,只是在系統無事所做的idle階段,我們可以通過停止週期時鐘來達到降低系統功耗的目的,只要有程序處於活動狀態,時鐘事件依然會被週期性地發出。
36+ rcu_init_nohz();
37、init_timers(); // 初始化核心定時器
參考:http://blog.csdn.net/sunnybeike/article/details/7016322
(1)初始化本 CPU 上的軟體時鐘相關的資料結構;
(2)向 cpu_chain 通知鏈註冊元素 timers_nb ,該元素的回撥函式用於初始化
指定 CPU 上的軟體時鐘相關的資料結構;
(3)初始化時鐘的軟中斷處理函式。(會註冊中斷處理函式run_timer_softirq)
38、hrtimers_init(); // 初始化高精度時鐘
39、softirq_init(); // 初始化軟中斷
軟體中斷的資源是有限的,核心目前只實現了10種類型的軟體中斷。定義在 include/linux/interrupt.h
該函式會呼叫open_softirq()初始化softirq_vet陣列中的兩個中斷型別(每種中斷型別對應一個數組元素,故該陣列共有10個元素),分別為TASKLET_SOFTIRQ和HI_SOFTIRQ,指定softirq_vet[中斷型別]中的action即中斷處理函式。
補充:。核心為每個cpu都管理著一個待決軟中斷變數(pending),它就是irq_cpustat_t,每一位對應一箇中斷是否觸發。疑問:變數為unsigned int型別,如何指定10種中斷型別?
40、timekeeping_init(); // 初始化了大量的時鐘相關全域性變數
詳細見:http://www.wowotech.net/timer_subsystem/timekeeping.html
41、time_init(); // 時鐘初始化
與架構相關,其實就是呼叫板級初始化檔案(arch/arm/mach-*/board-*.c)中定義“裝置描述結構體”中的timer成員的初始化函式。
41+、sched_clock_postinit();
41++、perf_event_init();//軟體效能分析工具初始化
42、profile_init(); // 初始化核心profile子系統,核心效能除錯工具
43、call_function_init(); // smp下跨cpu的函式傳遞初始化
44、WARN(!irqs_disabled(), "Interrupts were enabledearly\n");
45、early_boot_irqs_disabled = false;
46、local_irq_enable(); // 使能當前cpu中斷
47、kmem_cache_init_late();//完善slab分配器的快取機制,對應於
//mm_init中的kmem_cache_init
後續可參考:http://blog.csdn.net/zdy0_2004/article/details/48852147
/*
* HACK ALERT! This is early. We're enablingthe console before
* we've done PCI setups etc, andconsole_init() must be aware of
* this. But we do want output early, in casesomething goes wrong.
*/
48、console_init();//初始化終端
依次呼叫__con_initcall_start到__con_initcall_end之間的函式,通過vmlinux.lds可以找到巨集console_initcall(fn),該巨集指定的函式呼叫register_console()介面實現console的初始化。
if (panic_later)
panic(panic_later, panic_param);
Linux核心在發生kernelpanic時會打印出Oops資訊(哎呦),會將暫存器狀態、堆疊內容和完整的Call trace都打印出來。
49、lockdep_info();//列印當前鎖的依賴資訊
/*
* Need to run this when irqs are enabled,because it wants
* to self-test [hard/soft]-irqs on/off lockinversion bugs
* too:
*/
50、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
檢查initrd的位置是否符合要求。min_low_pfn是系統可用的最小的頁幀號,即判斷傳遞進來的initrd_start對應的實體地址是否正常,如果有誤就清零。
51、page_cgroup_init();
52、debug_objects_mem_init();//Debug_objetcs機制的記憶體分配初始化
53、kmemleak_init();//核心記憶體洩漏檢測機制初始化
54、setup_per_cpu_pageset();//設定每個CPU的頁組,並初始化。此
//前只有啟動頁
55、numa_policy_init(); //非一致性記憶體訪問(NUMA)初始化
if (late_time_init)
late_time_init();
56、sched_clock_init();//對每個CPU,初始化排程時鐘
57、calibrate_delay(); //計算BogoMIPS值,他是衡量一個CPU效能的標識
58、pidmap_init(); //PID分配對映初始化
59、anon_vma_init(); //匿名虛擬記憶體域(anonymous VMA)初始化
60、acpi_early_init();
#ifdef CONFIG_X86
if (efi_enabled(EFI_RUNTIME_SERVICES))
efi_enter_virtual_mode();
#endif
61、thread_info_cache_init();//獲取thread_info快取空間,arm為空
62、cred_init();//任務信用系統初始化。詳見:Documentation/credentials.txt
63、fork_init(totalram_pages);//程序建立初始化。為核心“task_struct”分配空間,計算最大任務數
64、proc_caches_init();//初始化程序建立機制所需的其他資料結構,為其申請空間。
65、buffer_init();//快取系統初始化,建立快取頭空間,並檢查其大小限時。
66、key_init(); //核心金鑰管理系統初始化
67、security_init();//核心安全框架初始化
68、dbg_late_init();//核心除錯系統後期初始化
69、vfs_caches_init(totalram_pages);//虛擬檔案系統快取初始化
70、signals_init();//訊號管理系統初始化
/* rootfs populating might need page-writeback */
page_writeback_init();//頁回寫機制初始化
#ifdef CONFIG_PROC_FS
proc_root_init();//proc檔案系統初始化
#endif
71、cgroup_init();//control group的正式初始化
72、cpuset_init();//cpuset初始化
73、taskstats_init_early();//任務狀態早期初始化函式:為結構體獲
//取快取記憶體,並初始化互斥機制
74、delayacct_init();//任務延遲機制初始化
75、check_bugs();//檢查CPU BUG的函式,通過軟體規避BUG
76、acpi_subsystem_init();//ACPI(高階配置和電源介面)
77、sfi_init_late();
if (efi_enabled(EFI_RUNTIME_SERVICES)) {
efi_late_init();
efi_free_boot_services();
}
78、ftrace_init();
/* Do the rest non-__init'ed, we're now alive