當Power on PC時,BIOS的代碼開始執行,然後是Linux初始化的代碼,這其中大約很長一段時間Linux都沒有程序這一概念,但是這不影響CPU執行它的二進制代碼。如果不是多任務以及程序排程的需要,Linux核心可以一直這樣走下去。
但是因為多任務的需求,Linux必須能支援任務這一特性,任務即程序,或者更簡單地說由task_struct對象執行個體所代表的一段代碼的集合,用以完成特定的任務。是以Linux核心初始化過程中必須為程序以及程序排程做準備。
init_task程序在Linux中屬于一個比較特殊的程序,它是核心開發者人為制造出來的,而不是其他程序通過do_fork來完成。init_task對象的初始化在核心代碼中由下面代碼來完成:
struct task_struct init_task = INIT_TASK(init_task);
如果仔細考察INIT_TASK宏的細節,會發現很多有趣的東西,比如inti_task所對應的核心棧,在INIT_TASK宏中由下列代碼指定:
.stack = &init_thread_info
可以猜想init_task程序的核心棧一定是通過靜态方式配置設定的,事實上也的确如此:
arch/x86/kernel/init_task.c>
union thread_union init_thread_union __init_task_data =
{ INIT_THREAD_INFO(init_task) };
init_thread_info定義中的__init_task_data表明該核心棧所在的區域位于核心映像的init data區,我們可以通過編譯完核心後所産生的System.map來看到該變量及其對應的邏輯位址:
root@build-server:/boot# cat System.map-3.1.6 | grep init_thread_union
ffffffff81a00000 D init_thread_union
這意味着init_task.stack = 0xffffffff81a00000.
Linux在無程序概念的情況下将一直從初始化部分的代碼執行到start_kernel,然後再到其最後一個函數調用rest_init。
從rest_init開始,Linux開始産生程序,因為init_task是靜态制造出來的,pid=0,它試圖将從最早的彙編代碼一直到start_kernel的執行都納入到init_task程序上下文中。在rest_init函數中,核心将通過下面的代碼産生第一個真正的程序(pid=1):
static noinline void __init_refok rest_init(void)
{
...
kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
cpu_idle();
}
kernel_init函數最有意思的地方在于它會通過調用kernel_execve來執行根檔案系統下的/sbin/init檔案(是以此前系統根檔案系統必須已經就緒),kernel_execve對使用者空間程式/sbin/init的調用發起自int $0x80,這是個從核心空間發起的系統調用,與call_usermodehelper函數本質上是完全一樣的。
而此時init_task的任務基本上已經完全結束了,它将淪落為一個idle task,事實上在更早前的sched_init()函數中,通過init_idle(current, smp_processor_id())函數的調用就已經把init_task初始化成了一個idle task,init_idle函數的第一個參數current就是&init_task,在init_idle中将會把init_task加入到cpu的運作隊列中,這樣當運作隊列中沒有别的就緒程序時,init_task(也就是idle task)将會被調用,它的核心是一個while(1)循環,在循環中它将會調用schedule函數以便在運作隊列中有新程序加入時切換到該新程序上。