作者:Sandy 原創作品轉載請註明出處
《Linux核心分析》MOOC課程http://mooc.study.163.com/course/USTC-1000029000 ”
實驗環境:c+Linux64位 (32位系統可能結果會不同)
依照學術誠信條款,我保證此回答為本人原創,所有回答中引用的外部材料已經做了出處標記。
實驗環境:ubuntu14.04作業系統,x86體繫結構
實驗要求:使用gdb跟蹤調試核心從start_kernel到init進程啟動
第一步,Linux核心代碼結構
本課程提供了一個Linux的核心源碼,其結構如下圖:
核心地址地址:http://codelab.shiyanlou.com/xref/linux-3.18.6/
不同的檔案夾代表了核心的不同模組,其含義是: arch/ 是體繫結構相關的代碼,其中的/x86 檔案夾下的內容是x86體繫結構相關代碼,是核心分析的重要分析目標。 init/ 是核心啟動相關的代碼,是本文的重點分析對象。/init/main.c 檔案是核心啟動的起點,是分析核心啟動流程的首要分析對象。 fs/ 檔案系統(file system?) kernel/ 核心相關的代碼,一些核心中使用到的結構體、函數等重要對象的定義都在這裡面。 mm/ 記憶體管理的相關代碼(memory managment?)
第二步,Linux核心啟動過程分析
/init/main.c 檔案中的start_kernel()函數是一切的起點,在這個函數被調用之前都是系統的初始化工作(組合語言),所以對核心的啟動分析一般都從這個函數開始;main.c中沒有main函數,start_kernel()這個函數就相當於是c程式中的main函數。下面從這個函數開始對核心的啟動流程進行分析。
由於寫博文時沒有隨時儲存的習慣,再加上手賤的原因,所以這是重新寫的博文,因為時間的原因實驗截圖就不放上了,直接寫一點自己的理會吧
start_kernel()函數的原型是:
這個函數在執行的過程中初始化、定義了核心中一些十分重要的內容,其執行過程幾乎涉及到了核心的所有模組;首先,是init_task
init_task的定義在/linux-3.18.6/init/init_task.c
struct task_struct init_task = INIT_TASK(init_task);
它其實就是一個task_struct,與使用者進程的task_struct一樣, task_struct中儲存了一個進程的所有基本資料,如進程狀態,棧起始地址,進程號pid等;init_task的特殊之處在於它的pid=0,也就是通常所說的0號進程(當然最終會進化成為idle進程,在下面會分析);關於0號進程的重要意義在下面會繼續分析,在這裡只要記得它是被start_kernel()函數建立的就可以啦。
在建立了0號進程之後,start_kernel()函數繼續調用各個系統模組進行各種初始化之類的工作,比如:
trap_init()是中斷向量的相關設定 mm_init()是記憶體管理的設定 sched_init()是調度模組的初始化
至於調用的多少模組以及其相關的功能在這裡就不仔細寫了(其實因為弄丟了原來的部落格來不及重寫啊),因為我看到了同樣學習了這門課的盧晅同學的部落格對這個問題分析的很詳細,所以就轉載一下吧:
上面這張圖片轉載自盧晅同學的部落格:http://blog.csdn.net/myfather103/article/details/44337461
在執行了上面的各項工作之後,是start_kernel()函數的最後一行代碼:
這樣子就調用了另一個非常重要的函數rest_init(),它的位置在/linux-3.18.6/init/main.c,其代碼如下:
393static noinline void __init_refok rest_init(void)394{395 int pid;396397 rcu_scheduler_starting();398 /*399 * We need to spawn init first so that it obtains pid 1, however400 * the init task will end up wanting to create kthreads, which, if401 * we schedule it before we create kthreadd, will OOPS.402 */403 kernel_thread(kernel_init, NULL, CLONE_FS);404 numa_default_policy();405 pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);406 rcu_read_lock();407 kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);408 rcu_read_unlock();409 complete(&kthreadd_done);410411 /*412 * The boot idle thread must execute schedule()413 * at least once to get things moving:414 */415 init_idle_bootup_task(current);416 schedule_preempt_disabled();417 /* Call into cpu_idle with preempt disabled */418 cpu_startup_entry(CPUHP_ONLINE);419}
rest_init()函數中這行代碼:
kernel_thread(kernel_init, NULL, CLONE_FS);
kernel_thread這個函數(/linux-3.18.6/kernel/fork.c)的源碼是:
從注釋部分就可以看出來這個函數的功能是建立一個核心線程(應該是進程吧喂。。不然哪裡會有pid啊喂。。)
kernel_thread函數中第一個參數是一個函數指標,也就是說核心此時fork出了一個新進程來執行kernel_init函數(低版本核心中這個函數名為init,為了區分init進程所以將其改為了kernel_init);在kernel_init函數(/linux-3.18.6/init/main.c)正式啟動了init進程:
至此rest_init()函數啟動了另一個大名鼎鼎的init進程,也就是1號進程,關於它的重要性以及功能同樣放在下面分析;接下來是rest_init()函數的這一行代碼:
pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
由上面的分析可以知道這個行代碼folk了一個新的進程來執行函數kthreadd,這個函數的源碼在這裡/linux-3.18.6/kernel/kthread.c,上面這一行代碼實現的功能是建立一個pid=2的核心進程,來管理核心的一些資源。
rest_init()在建立了1號、2號進程之後,我們忽略其餘的部分(其實是因為看不懂吧喂。。)直接來分析其最後一行代碼:
cpu_startup_entry(CPUHP_ONLINE);
調用了cpu_startup_entry( /linux-3.18.6/kernel/sched/idle.c )函數,其源碼:
256void cpu_startup_entry(enum cpuhp_state state)257{258 /*259 * This #ifdef needs to die, but it's too late in the cycle to260 * make this generic (arm and sh have never invoked the canary261 * init for the non boot cpus!). Will be fixed in 3.11262 */263#ifdef CONFIG_X86264 /*265 * If we're the non-boot CPU, nothing set the stack canary up266 * for us. The boot CPU already has it initialized but no harm267 * in doing it again. This is a good place for updating it, as268 * we wont ever return from this function (so the invalid269 * canaries already on the stack wont ever trigger).270 */271 boot_init_stack_canary();272#endif273 arch_cpu_idle_prepare();274 cpu_idle_loop();275}276
而cpu_idle_loop中其實就是進入了一個無限迴圈:
189static void cpu_idle_loop(void)190{191 while (1) {192 /*193 * If the arch has a polling bit, we maintain an invariant:194 *195 * Our polling bit is clear if we're not scheduled (i.e. if196 * rq->curr != rq->idle). This means that, if rq->idle has197 * the polling bit set, then setting need_resched is198 * guaranteed to cause the cpu to reschedule.199 */200201 __current_set_polling();202 tick_nohz_idle_enter();203204 while (!need_resched()) {205 check_pgt_cache();206 rmb();207208 if (cpu_is_offline(smp_processor_id()))209 arch_cpu_idle_dead();210211 local_irq_disable();212 arch_cpu_idle_enter();213214 /*215 * In poll mode we reenable interrupts and spin.216 *217 * Also if we detected in the wakeup from idle218 * path that the tick broadcast device expired219 * for us, we don't want to go deep idle as we220 * know that the IPI is going to arrive right221 * away222 */223 if (cpu_idle_force_poll || tick_check_broadcast_expired())224 cpu_idle_poll();225 else226 cpuidle_idle_call();227228 arch_cpu_idle_exit();229 }230231 /*232 * Since we fell out of the loop above, we know233 * TIF_NEED_RESCHED must be set, propagate it into234 * PREEMPT_NEED_RESCHED.235 *236 * This is required because for polling idle loops we will237 * not have had an IPI to fold the state for us.238 */239 preempt_set_need_resched();240 tick_nohz_idle_exit();241 __current_clr_polling();242243 /*244 * We promise to call sched_ttwu_pending and reschedule245 * if need_resched is set while polling is set. That246 * means that clearing polling needs to be visible247 * before doing these things.248 */249 smp_mb__after_atomic();250251 sched_ttwu_pending();252 schedule_preempt_disabled();253 }254}255
也就是說,0號進程在fold了1號進程並且做了其餘的啟動工作之後,最後”進化“成為了idle進程。
至此,由start_kernel()函數所開始的核心啟動告一段落,系統此時已經可以”正常“的接受任務進行工作了。
上述啟動流程的圖示:
由於只是一次簡單的實驗,對於複雜的核心啟動流程自然不可能僅僅用上面一張圖片總結,本文的參考文獻中提供了大量的核心啟動相關流程,有興趣的話請參閱
第三,0號進程與1號進程
0號進程:所有進程的”祖先“,由start_kernel()函數在核心啟動過程中”手動“建立的一個核心進程,始終處於核心態。在核心啟動的之後0號進程完全“進化”成idle進程,系統開始無限迴圈
1號進程:
補充:init可執行檔是可以在/sbin/init,/etc/init,/bin/init,/bin/sh之中的
上面兩張圖片截圖自課程提供的文檔:
http://blog.csdn.net/hardy_2009/article/details/7383815
http://teamtrac.ustcsz.edu.cn/raw-attachment/wiki/Linux2012/Linux-init-process-analyse.pdf 參考文獻:
對於複雜的核心啟動流程自然不能用一片文章就解釋清楚,雖然在實驗中做了一些跟蹤分析,不過為了完成此文還是參考了不少優秀文獻,許多文章中對核心的啟動分析都比本文要詳細透徹的多,在此對作者表示感謝。
http://blog.csdn.net/titer1/article/details/44423031
http://www.linuxidc.com/Linux/2014-10/108033p5.htm
http://itdreamerchen.com/從源碼中跟蹤linux-kernel的啟動過程/
http://blog.csdn.net/hlchou/article/details/6663994
http://blog.csdn.net/myfather103/article/details/44337461