當(dāng)Power on PC時,BIOS的代碼開始執(zhí)行,然后是Linux初始化的代碼,這其中大約很長一段時間Linux都沒有進(jìn)程這一概念,但是這不影響CPU執(zhí)行它的二進(jìn)制代碼。如果不是多任務(wù)以及進(jìn)程調(diào)度的需要,Linux內(nèi)核可以一直這樣走下去
但是因為多任務(wù)的需求,Linux必須能支持任務(wù)這一特性,任務(wù)即進(jìn)程,或者更簡單地說由task_struct對象實例所代表的一段代碼的集合,用以完成特定的任務(wù)。所以Linux內(nèi)核初始化過程中必須為進(jìn)程以及進(jìn)程調(diào)度做準(zhǔn)備
追蹤操作系統(tǒng) 內(nèi)核態(tài) 的初始化過程,從 init/main.c 中的 start_kernel() 開始
使用gdb跟蹤調(diào)試內(nèi)核
qemu?-kernel?linux-3.18.6/arch/x86/boot/bzImage -S -s
# 關(guān)于-S和-s選項的說明:
# -S freeze CPU at startup (use ’c’ to start execution)
# -s shorthand for -gdb tcp::1234
# 若不想使用1234端口,則可以使用-gdb tcp:xxxx來取代-s選項

另開一個shell窗口
gdb
? (gdb) file linux-3.18.6/vmlinux # 在gdb界面中targe remote之前加載符號表
? (gdb) target remote:1234 # 建立gdb和gdbserver之間的連接,按c讓qemu上的Linux繼續(xù)運行
? (gdb) break start_kernel # 斷點的設(shè)置可以在target remote之前,也可以在之后

詳細(xì)分析從start_kernel到init進(jìn)程啟動的過程
start_kernel()????? /linux-3.18.6/init/main.c(500行)
start_kernel()是內(nèi)核的匯編與C語言的交接點,在該函數(shù)以前,內(nèi)核的代碼都是用匯編寫的,完成一些最基本的初始化與環(huán)境設(shè)置工作,比如內(nèi)核代碼載入內(nèi)存并解壓縮(現(xiàn)在的內(nèi)核一般都經(jīng)過壓縮),CPU的最基本初始化,為C代碼的運行設(shè)置環(huán)境(C代碼的運行是有一定環(huán)境要求的,比如stack的設(shè)置等,具體見 C語言函數(shù)調(diào)用堆??蚣?/a>?)

全局變量init_task,即手工創(chuàng)建的(0號進(jìn)程的)PCB,0號進(jìn)程即最終的idle進(jìn)程

init_task進(jìn)程在Linux中屬于一個比較特殊的進(jìn)程,它是內(nèi)核開發(fā)者人為制造出來的,而不是其他進(jìn)程通過do_fork來完成,init_task進(jìn)程的內(nèi)核棧通過靜態(tài)方式分配
所有的模塊在初始化的時候都是通過調(diào)用 start_kernel() 進(jìn)行初始化,例如中斷模塊(trap_init)、內(nèi)存管理模塊(mm_init)、調(diào)度模塊(sched_init)等等,研究特定的內(nèi)核的模塊,都需要了解 main.c 中的 start_kernel(),不管分析內(nèi)核的哪一部分都會涉及到 start_kernel()
trap_init()????? /linux-3.18.6/arch/x86/kernel/traps.c(792行)
涉及一些中斷,初始化一些中斷向量

set_intr_gate,設(shè)置了很多中斷門

set_system_trap_gate,設(shè)置系統(tǒng)陷阱門,系統(tǒng)調(diào)用

分析中斷的時候也主要是分析系統(tǒng)調(diào)用,因為硬件中斷不好模擬,而系統(tǒng)調(diào)用也是一種中斷,和中斷的機(jī)制是一樣的,它只是用指令的方式來觸發(fā)一個中斷
Linux在無進(jìn)程概念的情況下將一直從初始化部分的代碼執(zhí)行到start_kernel(),在start_kernel()中Linux將完成整個系統(tǒng)的內(nèi)核初始化。內(nèi)核初始化的最后一步就是調(diào)用rest_init(),啟動init進(jìn)程這個所有進(jìn)程的祖先
rest_init():Linux內(nèi)核初始化的尾聲
從rest_init開始,Linux開始產(chǎn)生進(jìn)程,因為init_task是靜態(tài)制造出來的,pid=0,它試圖將從最早的匯編代碼一直到start_kernel的執(zhí)行都納入到init_task進(jìn)程上下文中。在rest_init函數(shù)中,內(nèi)核將通過下面的代碼產(chǎn)生第一個真正的進(jìn)程(pid=1):

kernel_thread():創(chuàng)建一個內(nèi)核線程,實際上就是內(nèi)核進(jìn)程,Linux內(nèi)核是不支持類似Windows NT一樣的線程概念的。Linux本質(zhì)上只支持進(jìn)程。這里的kernel_init只是一個函數(shù)
kernel_init():會通過調(diào)用do_execve來執(zhí)行根文件系統(tǒng)下的/sbin/init文件(所以此前根文件系統(tǒng)必須已經(jīng)就緒),do_execve對用戶空間程序/sbin/init的調(diào)用發(fā)起自int $0x80,這是個從內(nèi)核空間發(fā)起的系統(tǒng)調(diào)用



run_init_process():實際上是通過嵌入?yún)R編構(gòu)建一個類似用戶態(tài)代碼一樣的do_execve()調(diào)用,其參數(shù)就是要執(zhí)行的可執(zhí)行文件名,也就是這里的init進(jìn)程在磁盤上的文件
這里的run_init_process就是通過execve()來運行init程序。這里首先運行“/sbin/init”,如果失敗再運行“/etc/init”,然后是 “/bin/init”,然后是“/bin/sh”(也就是說,init可執(zhí)行文件可以放在上面代碼中尋找的4個目錄中都可以),如果都失敗,則可以通過在系統(tǒng)啟動時再添加的啟動參數(shù)來指定init,比如init=/home/rootfs/init。這里是內(nèi)核初始化結(jié)束并開始用戶態(tài)初始化的陰陽界
init進(jìn)程是Linux系統(tǒng)的第一個用戶態(tài)進(jìn)程,為1號進(jìn)程,沒有父進(jìn)程,由Linux內(nèi)核直接啟動
接下來還創(chuàng)建了一個kthreadd內(nèi)核線程,來管理系統(tǒng)的資源

此時init_task的任務(wù)基本上已經(jīng)完全結(jié)束了,它將淪落為一個idle task,事實上在更早前的sched_init()函數(shù)中,通過init_idle(current, smp_processor_id())函數(shù)的調(diào)用就已經(jīng)把init_task初始化成了一個idle task,init_idle函數(shù)的第一個參數(shù)current就是&init_task,在init_idle中將會把init_task加入到cpu的運行隊列中,這樣當(dāng)運行隊列中沒有別的就緒進(jìn)程時,init_task(也就是idle task)將會被調(diào)用,它的核心是一個while(1)循環(huán),在循環(huán)中它將會調(diào)用schedule函數(shù)以便在運行隊列中有新進(jìn)程加入時切換到該新進(jìn)程上
Summary:
內(nèi)核啟動過程包括start_kernel之前和之后,之前全部是做初始化的匯編指令(硬件平臺相關(guān)),之后開始C代碼的操作系統(tǒng)初始化(硬件平臺無關(guān)),最后執(zhí)行第一個用戶態(tài)進(jìn)程init
結(jié)合中國傳統(tǒng)文化的角度看,道生一(start_kernel....cpu_idle),一生二(kernel_init和kthreadd),二生三(即前面0、1和2三個進(jìn)程),三生萬物(1號進(jìn)程是所有用戶態(tài)進(jìn)程的祖先,2號進(jìn)程是所有內(nèi)核線程的祖先)
(完)