Linux內核分析(三)內核啓動過程分析——構造一個簡單的Linux系統

禹曉博+ 原創作品轉載請註明出處 + 歡迎加入《Linux內核分析》MOOC網易雲課堂學習

一、系統的啓動(各歷史節點)

        在最開始的時候,計算機的啓動實際上依靠一段二進制碼,可以這麼理解,他並不是一個真正的計算機啓動一道程序。計算機在開始加電的時候幾乎是沒有任何用處的,因爲RAM芯片中包括的都是一些沒有意義的隨機數據,此時沒有操作系統在運行。在開始啓動的時候,一個特殊的硬件電路在CPU的引腳上產生一個RESET復位信號,就是那個復位信號,就好比我們重啓老式電子詞典時候後面那個小按鍵要用到筆芯去按一下。在這個信號產生之後,就會把處理器的一些寄存器設置成固定的值,並執行物理地址0xfffffff0那個地方的代碼。硬件把這個地址映射獨到某個只讀,持久的存儲芯片上,這個芯片就是我們通常說的ROM。ROM中所存放的程序集實際上在80x86體系中叫做基本輸入/輸出系統(Basic Input/Output System——BIOS)因爲它包括了幾個中斷的低級過程。所有的操作系統在啓動時,都要通過這些程序對計算機硬件設備進行初始化。一些操作系統,如微軟的MS-DOS,依賴於BIOS實現大部分系統調用。

        Linux中BIOS使用的是一種叫做實模式的地址(還是那句話名字不重要),以爲這是在一通電的時候就加載的所以不能用一些邏輯描述(你可以把它理解爲變量名字和他的地址相比就是一個邏輯描述)。實模式的地址用一個段地址和一個偏移量表示(seg+offset 類似 ebp+ip這樣的)所以物理地址就是seg*16+offset。所以CPU這裏面就可以直接的找到內存地址,顯然這在沒建立一張邏輯表示和物理地址對應表是是必要的(這個表實際上就是全局描述符表,嗯名字不重要)

        Linux在啓動階段必須使用BIOS,此時Linux必須從磁盤或者其他的外部存儲介質上獲得系統的內核鏡像(kernel Image)BOIS啓動過程實際上分爲四個過程:1、對硬件的檢測2、硬件初始化3、索索一個操作系統來啓動4、找到一個有效的設備。下面我們看一下早期的BOIS部分的結構:


        後來就有了bootloader——引導裝入程序。在之後就是setup()在之後就是startup_32(),現在他就變成了start_kernel()函數。就是今天我們要分析的。那麼其他的我們日後在分析。浸提我麼就是來分析start_kernel。

二、實驗過程簡述及啓動過程分析

        首先我們先來構建一個簡單的Linux內核。大體上是分爲兩個步驟,首先是現在內核元代碼編譯內核,然後製作根文件系統,具體步驟過程如下(詳細參見雲課堂第三週實驗指導)


        如果是在“實驗樓”提供的實驗環境下,我們已經搭建了只需要打開shell然後輸入如下語句:


       下圖是具體實驗過程截圖:


        然後回車之後就會看到下面這樣的界面,MenuOS正在啓動:


        然後一會兒他就會完全啓動,看到下面的界面就說明啓動成功了,然後後我們可以試試他的幾個命令,help之後會有相應的提示:

 

        到此爲止我們就完成了一個簡單的內核搭建。然後我們開始使用GDB調試,再重新打開一個終端我們可以進行如下步驟:


        他的意思就是在開始的時候就讓CPU停止在啓動的那一刻,我們可以看到如下的界面:


        這個時候我們可以在剛纔新建的那個終端窗口輸入gdb進入調試模式:


        然後我們可以看到如下的過程:


        然後我們看到系統停止在了我們設置的斷點上,就是start_kernel。


        然後我們可以使用list命令常看停止斷點的源代碼:如下圖所示就是start_kernel()部分的代碼:


        我們可以在雲課堂的主頁上產看到3.18.6內核部分的源碼,其中比較主要就是arch下面的x86目錄。由於Linux是支持很多不同的處理器的所以在這個srch就是architecture對應了不同的體系結構deCPU的啓動和內核代碼。我們就是關心X86下的這些。從某種意義上,函數start_kernel就好像一般可執行程序中的主函數main,系統進入這個函數之前已經進行了一些最低限度的初始化,再往前研究就涉及很多硬件相關及編程語言了,這裏是較高層次的初始化,基本是C代碼,一直想搞清楚內核的初始化流程,好對整個linux內核有更深理解。分析程序習慣性的找main函數,那麼就從這個start_kernel看看。  這個函數在init/main.c,我們首先開一下start_kernel()的源代碼:


        這裏我們看到了一個void lockdep_init(void) 函數,點開就可以查看它的源代碼了(代碼的網站就在課程的首頁上)然後我麼看到它的註釋就是用於初始化hash表。那麼問題來了這個表幹嘛的?我們看到源代碼上是這麼說的: We keep a global list of all lock classes. The list only grows,  never shrinks. The list is only accessed with the lockdep  spinlock lock held.實際上就是這個hash表是個全局的鎖鏈表,lock dependency哈希表。個人理解是鎖的初始化(歡迎留言補充)。

        然後我們看到了init_task。看名字就知道是初始化任務的(恩恩源碼就是要看名字猜想然後驗證個人感覺)因爲多任務的需求,Linux必須能支持任務這一特性,任務即進程,或者更簡單地說由task_struct對象實例所代表的一段代碼的集合,用以完成特定的任務。所以Linux內核初始化過程中必須爲進程以及進程調度做準備。init_task進程在Linux中屬於一個比較特殊的進程,它是內核開發者人爲製造出來的,而不是其他進程通過do_fork來完成。我們觀察這個東西的初始化就是看到它實際上使用靜態的方式分配的。【在<arch/x86/kernel/init_task.c>有struct task_struct init_task = INIT_TASK(init_task);如果仔細考察INIT_TASK宏的細節,會發現很多有趣的東西,比如inti_task所對應的內核棧,在INIT_TASK宏中由下列代碼指定:.stack  = &init_thread_info可以猜想init_task進程的內核棧一定是通過靜態方式分配的,事實上也的確】init_thread_info定義中的__init_task_data表明該內核棧所在的區域位於內核映像的init data區,我們可以通過編譯完內核後所產生的System.map來看到該變量及其對應的邏輯地址。Linux在無進程概念的情況下將一直從初始化部分的代碼執行到start_kernel,然後再到其最後一個函數調用rest_init。

        從rest_init開始,Linux開始產生進程,因爲init_task是靜態製造出來的,pid=0,它試圖將從最早的彙編代碼一直到start_kernel的執行都納入到init_task進程上下文中。在rest_init函數中,內核將通過下面的代碼產生第一個真正的進程(pid=1):我們也可以開一下他的源碼:


         實際上我們看到了它實際上是完成了一些初始化的操作,和設置一些必要的參數。然後最後一句就是入口了,到此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函數以便在運行隊列中有新進程加入時切換到該新進程上。

        下面這張圖希望對大家有參考。


三、Linux內核啓動的一些綜述補充

        start_kernel( )函數完成了Linux內核的初始化工作。幾乎每天內核部件都是用這個函數進行初始化的,我們只是說道了其中的一小部分:

1.調用sched_init()函數來初始化調度程序

2.調用build_all_zonelists()函數倆初始化內存管理

3.調用page_alloc_init()函數來初始化夥伴系統分配程序

4.調用trap_init()函數和init_IRQ()函數以初始化IDT

5.調用softing_init()函數初始化TASKLET_SOFTIRQ和HI_SOFTIRQ(軟中斷)

6.調用time_init()初始化系統日期時間

7.調用kmem_cache_init()函數初始化slab分配器(普通和高速緩存)

8.調用calibrate_delay()函數用於確定CPU時鐘(延遲函數)

9.調用kernel_thread()函數爲進程1創建內個線程,這個內核線程又會創建其他的內核線程並執行/sbin/init程序

        在start_kernel()開始執行之後會顯示linux版本西悉尼,除此之外,在init程序和內核線程執行的最後階段還會顯示很多其他信息。最後,就會在控制檯上出現熟悉的登陸提示,通知用戶Linux內核已經啓動正在運行。


















發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章