天天看點

linux 核心移植(七)——rest_init函數分析

代碼在start_kernel函數運作的最後到了rest_init()函數中

1:rest_init()函數分析

  (1)rest_init中調用kernel_thread函數啟動了2個核心線程,分别是:kernel_init和kthreadd

  (2)調用schedule函數開啟了核心的排程系統,從此linux系統開始轉起來了。

  (3)rest_init最終調用cpu_idle函數結束了整個核心的啟動。也就是說linux核心最終結束了一個函數cpu_idle。這個函數裡面肯定是死循環。

  (4)簡單來說,linux核心最終的狀态是:有事幹的時候去執行有意義的工作(執行各個程序任務),實在沒活幹的時候就去死循環(實際上死循環也可以看成是一個任務)。

  (5)之前已經啟動了核心排程系統,排程系統會負責考評系統中所有的程序,這些程序裡面隻有有哪個需要被運作,排程系統就會終止cpu_idle死循環程序(空閑程序)轉而去執行有意義的幹活的程序。這樣作業系統就轉起來了。

2.1:什麼是核心線程

  (1)程序和線程。簡單來了解,一個運作的程式就是一個程序。是以程序就是任務、程序就是一個獨立的程式。

獨立的意思就是這個程式和别的程式是分開的,這個程式可以被核心單獨調用執行或者暫停。

  (2)在linux系統中,線程和程序非常相似,幾乎可以看成是一樣的。實際上我們目前講課用到的程序和線程的概念就是

一樣的。

  (3)程序/線程就是一個獨立的程式。應用層運作一個程式就構成一個使用者程序/線程,那麼核心中運作

一個函數(函數其實就是一個程式)就構成了一個核心程序/線程。

  (4)是以我們kernel_thead函數運作一個函數,其實就是把這個函數變成了一個核心線程去運作起來,然後他可以被核心排程系統去排程。說白了就是去排程器注冊了一下,以後人家排程的時候會考慮你。

2.2:程序0、程序1、程序2

 (1)作業系統是用一個數字來表示/記錄一個程序/線程的,這個數字就被稱為這個程序的程序号。這個号碼是從0開始配置設定的。是以這裡涉及到的三個程序分别是linux系統的程序0、程序1、程序2.

 (2)在linux指令行下,使用ps指令可以檢視目前linux系統中運作的程序情況。

(4)我們在ubuntu下ps -aux可以看到目前系統運作的所有程序,可以看出程序号是從1開始的。為什麼不從0開始,因為程序0不是一個使用者程序,而屬于核心程序。

  程序0:程序0其實就是剛才講過的idle程序,叫空閑程序,也就是死循環。

  程序1:kernel_init函數就是程序1,這個程序被稱為init程序。

  程序2:kthreadd函數就是程序2,這個程序是linux核心的守護程序。它的作用是管理排程其他核心程序這個程序是用來保證linux核心自己本身能正常工作的。

3:init程序分析

  需要注意的一點是這個程序剛開始運作的時候是核心态,是屬于核心程序,然後它自己運作了一個使用者太下面的程式後把自己強行轉成了使用者态,因為init程序自身完成了從核心态到使用者态的過渡,是以後續的其他程序都可以工作在使用者态下面了

3.1:init程序在核心态下做了什麼

  重要的點就挂載根檔案系統,并試圖找到使用者态下的那個init程式,原因是init程序要完成從核心态到使用者态的轉變就必須去運作一個使用者态的應用程式,而核心源代碼中的程式都是屬于核心态的,是以這個應用程式必須不屬于核心源代碼,這樣才能保證自己是使用者态,是以這個應用程式就的是由另外一份檔案提供,即根檔案系統

3.2: init程序在使用者态下做了什麼

  init程序大部分有意義的工作都是在使用者态下進行的,原因是使用者态下的所有程序都是直接或者間接由init程序生成的。

3.3:如何從核心态跳躍到使用者态?還能回來不?

  init程序在核心态下面時,通過調用kernel_execve函數來執行一個使用者空間編譯連結的應用程式就跳躍到了使用者态下面了,需要注意的是,這個跳躍的過程程序号并沒有改變還是程序1,并且這個跳躍是單向的,以後要從使用者态回到核心态隻有走API這一條路了

kernel_execve函數被調用的路徑start_kernel->rest_init->kernel_thread->kernel_init->init_post->run_init_process->kernel_execve

4:init程序在核心态下的分析(也就是kernel_init函數)

4.1:打開控制台,代碼如下:

/* Open the /dev/console on the rootfs, this should never fail */
if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)
printk(KERN_WARNING "Warning: unable to open an initial console.\n");
(void) sys_dup(0);
(void) sys_dup(0);      

  (1)linux系統中每個程序都有自己的一個檔案描述符表,表中存儲的是本程序打開的檔案。

  (2)linux系統中有一個設計理念:一切屆是檔案。是以裝置也是以檔案的方式來通路的。我們要通路一個裝置,就要去打開這個裝置對應的檔案描述符。譬如/dev/fb0這個裝置檔案就代表LCD顯示器裝置,/dev/buzzer代表蜂鳴器裝置,/dev/console代表控制台裝置。打開一個裝置的檔案就會得到這個裝置的檔案描述符(或者是檔案描述符的編号),這個編号就代表這個裝置,以後操作這個裝置就用這個檔案描述符來操作它

  (3)這裡我們打開了/dev/console檔案,并且複制了2次檔案描述符,一共得到了3個檔案描述符。這三個檔案描述符分别是0、1、2.這三個檔案描述符就是所謂的:标準輸入、标準輸出、标準錯誤。

  (4)程序1打開了三個标準輸出輸出錯誤檔案,是以後續的程序1衍生出來的所有的程序預設都具有這3個三件描述符

4.2:挂載根檔案系統,代碼如下

if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {
ramdisk_execute_command = NULL;
prepare_namespace();
}      

  (1)prepare_namespace函數中挂載根檔案系統

  (2)根檔案系統在哪裡?根檔案系統的檔案系統類型是什麼? uboot通過傳參來告訴核心這些資訊。uboot傳參中的root=/dev/mmcblk0p2 rw 這一句就是告訴核心根檔案系統在哪裡uboot傳參中的rootfstype=ext3這一句就是告訴核心rootfs的類型。

  (3)如果核心挂載根檔案系統成功,則會列印出:VFS: Mounted root (ext3 filesystem) on device 179:2.如果挂載根檔案系統失敗,則會列印:No filesystem could mount root, tried:  yaffs2

  (4)如果核心啟動時挂載rootfs失敗,則後面肯定沒法執行了。核心中設定了啟動失敗休息5s自動重新開機的機制,是以這裡會自動重新開機,是以有時候大家會看到反複重新開機的情況。

  (5)如果挂載rootfs失敗,可能的原因有:最常見的錯誤就是uboot的bootargs設定不對。rootfs燒錄失敗(fastboot燒錄不容易出錯,以前是手工燒錄很容易出錯)rootfs本身制作失敗的。(尤其是自己做的rootfs,或者别人給的第一次用)

5:執行使用者态下的程序1程式

  (1)上面一旦挂載rootfs成功,則進入rootfs中尋找應用程式的init程式,

這個程式就是使用者空間的程序1.找到後用run_init_process(裡面的kernel_execve函數)去執行他

  (2)我們如果确定init程式是誰?方法是:先從uboot傳參cmdline中看有沒有指定,如果有指定先執行cmdline中指定的程式。cmdline中的init=/linuxrc這個就是指定rootfs中哪個程式是init程式。這裡的指定方式就表示我們rootfs的根目錄下面有個名字叫linuxrc的程式,這個程式就是init程式。如果uboot傳參cmdline中沒有init=xx或者cmdline中指定的這個xx執行失敗,還有備用方案。

繼續閱讀