歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> Linux教程 >> Linux 的初始化與啟動過程

Linux 的初始化與啟動過程

日期:2017/2/28 14:01:04   编辑:Linux教程

我們運行程序只需要點擊應用程序的圖標就可以了,但在這之前,我們必須啟動我們的系統。在一切之前,我們必須有某些程序去引導我們系統的內核,這些程序就是內核引導程序了,例如LILO、GRUB、U-Boot、RedBoot。而這些引導程序同樣需要被其他程序加載和運行,這樣說下去,茫茫人生何處才是盡頭啊?想必大家可以想到的----硬件!這麼長的過程復雜、崎岖!正所謂萬事開頭難,但不怕,我們來一起走過去吧!

X86的引導過程如圖:

cpu自身的初始化:這是引導的第一步,如果在多處理器系統上,那麼每個cpu都要自身初始化。cpu初始化後,cpu從某個固定的位置(應該是0Xfffffff0)取指,這條指令是跳轉指令,目的地是BIOS的首部代碼,但是cpu並不在乎BIOS是否存在,它僅僅只是執行這個地址的指令而已!

BIOS:BIOS是只讀存儲器(ROM),被固化於主板上。其工作主要有兩個,就是上圖的加電自檢即是POST(post on self test)與加載內核引導程序。

那麼他們是具體完成什麼工作的呢?

1) 加電自檢:完成系統的硬件檢測,其中包括內存檢測、系統總線檢測等工作。

2) 加載內核引導程序:在POST完成後,就要加載內核引導程序了,那它保存在哪裡呢?磁盤裡!哈哈,BIOS會讀取0磁頭,0磁道,一扇區的512個字節,這個扇區有叫做MBR(主引導記錄),MBR中保存了內核引導程序的開始部分,BIOS將其裝入內存執行。512個字節的MBR有些什麼呢?這裡有必要說說MBR!MBR分區表以80為起始,以55AA為結束,共64個字節。具體的MBR知識自己百度!

MBR:1) 446個字節的引導程序代碼

2) 64個字節的分區表,有多少個分區呢。。?這還真不知道!分為4個分區表,一個可啟動分區和三個不可啟動分區。

3) 2個字節的0XAA55,用於檢查MBR是否有效。

需要注意的是,內核引導程序被加載完後,POST部分的代碼會被從內存中清理,只留部分在內存中留給目標操作系統使用。

內核引導程序:內核引導程序分兩部分:主、次引導程序。主引導程序的主要工作就是收索,尋找活動的分區,將活動的分區引導記錄中的次引導程序加載到內存中並且執行。而這個次引導程序就是負責加載內核的並且將控制權交給內核。上面提過內核引導程序有LILO、GRUB、U-Boot、RedBoot。其中前面兩個為pc中的,而後面兩個是嵌入式的。

內核:內核以壓縮的形式存在,不是一個可執行的內核!所以內核階段首先要做的是自解壓內核映像。這裡說說編譯內核後形成的內核壓縮的映像vmlinuz。編譯生成vmlinux後,一般會對其進行壓縮為vmlinuz,使其成為zImage--小於512KB的小內核,或者成為bzImage--大於512KB的大內核。

vmlinuz結構如圖:

做了這麼多工作終於把linux的內核給引導出來了。!!下面我們來初始化這個千呼萬喚始出來的linux內核!

內核初始化:內核會調用一系列的初始化函數去對所有的內核組件進行初始化,由start_kernel()---.....---> rest_init() ----..----> kernel_init() ----....--> init_post() ------到---> 第一個用戶init進程結束。

start_kernel():其完成大部分內核初始化的工作。相關的代碼去查閱內核的源代碼吧!www.kernel.org

rest_init():start_kernel() 調用rest_init() 進行後面的初始化工作。

kernel_init():此函數主要完成設備驅動程序的初始化,並且調用 init_post() 啟動用戶空間的init進程。

init_post():初始化的尾聲,第一個用戶空間的init 橫空出世!其PID始終為1。

init: 內核會在過去曾使用過init的幾個地方查找它,它的正確位置(對Linux系統來說)是/sbin/init。如果內核找不到init,它就會試著運行/bin/sh,如果運行失敗,系統的啟動也會失敗。找到/sbin/init 後init會根據/etc/inittab (網上的資料都這樣說的,我在Ubuntu3.8的內核裡找不到,但在Fedora中可以找到!是發行版本不同吧?(求指教)這裡附上圖片!)文件完成其他一些工作,例如:getty進程接受用戶的登錄,設置網絡等。這裡詳細說說吧。

fedora19的etc有inittab文件:

系統中所有的進程形成樹型結構,而這棵樹的根就是在內核態形成的,系統自動構造的0號進程,它是所有的進程的祖先。大致是在vmlinux的入口 startup_32(head.S)中為pid號為0的原始進程設置了執行環境,然後原是進程開始執行start_kernel()完成Linux內核的初始化工作。包括初始化頁表,初始化中斷向量表,初始化系統時間等。繼而調用 fork(),創建第內核init進程:

kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND); // 參數CLONE_FS | CLONE_SIGHAND表示0號線程和1號線程分別共享文件系統(CLONE_FS)、打開的文件(CLONE_FILES)和信號處理程序 (CLONE_SIGHAND)。

這個進程就是著名的pid為1的init進程(內核態的),它會繼續完成剩下的初始化工作比且創建若干個用於高速緩存和虛擬主存管理的內核線程,如kswapd和bdflush等,然後execve(/sbin/init)(生成用戶態的init進程pid=1,因為沒有調用fork(),所以pid還是1!), 成為系統中的其他所有進程的祖先。回過頭來看pid=0的進程,在創建了init進程後(內核態的),pid=0的進程調用 cpu_idle()在主cpu中演變成了idle進程。而內核態pid=1的init進程同樣會在各個從cpu上生成idel進程。 init在演變成/sbin/init之前,會執行一部分初始化工作,其中一個就是smp_prepare_cpus(),初始化SMP處理器,在這過程中會在處理每個從處理器時調用

task =copy_process(CLONE_VM, 0, idle_regs(&regs), 0, NULL, NULL, 0); init_idle(task, cpu);

即從init中復制出一個進程,並把它初始化為idle進程(pid仍然為0)。從處理器上的idle進程會進行一Activate工作,然後執行cpu_idle()。

執行/sbin/init,這樣從內核太過度到用戶態,按照配置文件/etc/inittab 要求完成啟動的工作,並且創建若干個不編號為1,2,3...號的終端注冊進程getty,其作用就是設置其進程組的標識號,監視配置到系統終端的接口電路,當有信號來到的時候,getty會執行execve()生成注冊進程login,用戶可以注冊登錄了,如果登錄成功,則login會演化為shell進程,若login不成功則關閉打開的終端線路,用戶1號進程會創建新的getty。到這裡init的流程基本完成了。奉上大圖一張!

init的過程:

init 的流程講完了,這裡粗略說說init還做了什麼事。先來認識下運行級別。

運行級別: linux可以在不同的場合啟動不同的開機啟動程序,這就叫做運行級別。根據不同的運行級別啟動不同的程序。例如在用作服務器的時候要開啟Apache,而桌面就不需要。

linux預先設置了7種運行級別(0--6)。ubuntu有8種(0--6、S),這裡主要以ubuntu來說。0:關閉系統,1:系統進入單用戶模式,S:單用戶恢復模式,文本登錄界面,只運行少數的系統服務。2:多用戶模式(系統默認的級別),圖形登錄界面,運行所有預定的系統服務。3--5:多用戶模式,圖形登錄界面,運行所有預定的系統服務(對於系統定制而言,運行級別2-5的作用等同),6:重啟系統。

對於每個運行級別,在/etc/都有對應的子錄目---/etc/rcN.d 用來指定要加載的程序。

細心看的話可以發現,除README外其他的文件都是“S開頭+兩位數字+程序名”的形式。這代表什麼呢?S代表Start啟動。如果是K的話則代表關閉kill,如果從其他的運行級別切換過來的話則要關閉程序。之後的數字為處理的順序,越小則越早執行,如果數字相同,按字母的順序啟動。

上圖可以看出這裡的文件都是鏈接文件,為什麼呢?上面說過各種運行級別有各自的一個錄目用來存放各自的開機程序,如果有多個運行級別要啟動同一個程序,那麼這個程序的腳本會被拷貝到每一個錄目裡,這樣做的話,如果要修改啟動腳本就要修改每一個錄目,這樣不科學啊!!!!所以這些文件都是鏈接文件指向/etc/init.d。啟動時就是運行這些腳本的。

子系統的初始化:

內核選項:linux 允許用戶傳遞內核配置選項給內核,內核在初始化的過程中調用parse_args()函數對這些選項進行解析,之後調用相應的函數進行處理。對於parse_args()函數,其能夠解析形如“變量名=值”的字符串,在模塊加載的時候也會被調用來解析模塊的參數。

子系統的初始化:在完成內核選項的解析後,就進入初始化的函數調用。在kernel_init()函數中調用do_basic_setup()函數再去調用do_initcalls()函數來完成。各個函數具體實現請查閱源代碼!

登錄:登錄有三種方式。

1) 命令行登錄

init創建getty,等用戶輸入用戶名和密碼,輸入完成後調用login程序進行核對密碼,如果正確就讀取/etc/passwd文件,讀取這個用戶的指定的shell並啟動它。

2) ssh登錄

系統調用sshd程序,取代getty和login,之後啟動shell。

3) 圖形界面登錄

init進程調用顯示管理器,Gnome圖形界面對應為gdm,然後輸入用戶名、密碼,如果密碼正確就啟動用戶會話。

到這裡系統就啟動起來了!說了這麼多,現在我們來玩點好玩的!!!!!!簡單熟悉一下linux的啟動!!!!!!!!!!!首先要准備一個ubuntu系統最好13.04或者12.04都得,可以是虛擬機(最好是在虛擬機上操作!因為我因為這個小實驗而重裝了一遍真機系統!當時不懂啊!慘。。。。。。。。。)。

1:進入系統,在主文件夾(方便)新建一個c語言文件並且命名為init.c,輸入---->最簡單的c語言程序helloworld,不這是最偉大的c語言程序!

main(){

printf("helloworld!\n");

}

之後不用說就是編譯啦。打開終端(ctrl + alt + t)執行這條指令:gcc --static -o init init.c 這樣init文件就准備好了!猜到我想做什麼了嗎。。?哈哈我們繼續!!

2:將上文提到過的/sbin/init 文件備份執行這條指令:sudo cp /sbin/init /sbin/init.bak 備份成init.bak 文件

將原來的init 文件刪除,執行指令:sudo rm /sbin/init

好了,下一步就將我們的helloworld init復制到/sbin/錄目下!執行指令: sudo mv init /sbin/ 這條指令之前要注意你終端當前的路徑與init的路徑是不是相同,要不不能成功的!!

3:這樣就做好了!!!我們果斷重啟!這樣在啟動中我們看到了我們的 helloworld!唉。。它停哪裡了!!那是肯定的,上文介紹了init的作用,你換了,不能啟動是正常的啦。。思考下,到這裡內核處於什麼狀態。?這裡其實內核基本已經初始化完成了!就剩下進程的生成與子系統的初始化了。在init_post()函數的最尾會查找init的路徑,如果找不到就崩潰。而且會試圖建立一個交互式的shell(/bin/sh)來代替找不到的init,讓用戶可以修復這種錯誤、重新啟動。

4:現在我們來恢復我們的系統!剛才在 2 中的步驟不會讓你白做的!重新啟動系統,並在一開始就按著左Shift (我以前的系統是不用的,不知道以前對grub.cfg做了什麼壞事!!),系統會自動檢測到這個信號的,這樣會出現下面圖中的界面。(建議去看看grub2的特性!):

按下e,進行編輯,按鍵盤右下角 向下的按鍵,在linux 這一行的最尾(quiet)前面加入----> init=/sbin/init.bak ,按下 ctrl + x 啟動系統。如圖:

如果一切沒有錯,那麼你肯定能成功地重新啟動系統!如果看到登錄界面,那麼恭喜你,你已經在漫漫人生路上走了一趟了!

好了,到這裡本文要講的知識已經講完了。下面我們一起來思考一下我在這其中遇到的沒有解決的問題,請大神們指教!

問題: 首先我的實驗是在虛擬機上做的,原本我以為init是跟內核的壓縮文件有關,所以用3.8.0-19 內核進去系統將init刪了,這樣3.8.0-19內核肯定啟動不了內核。我用3.8.0-27內核進去系統,同樣不能進去。。還是出現偉大的helloworld!這樣看 init跟vmlinux等 在boot裡的文件無關了!那麼init關機後會被保存在哪裡!?還有一個最重要的!我重新裝了一遍系統(虛擬機的),這是沒有3.8.0-27內核的,我將我真機的3.8.0-27的內核文件(/boot 裡的vmlinuz文件等)放到虛擬機的/boot/文件夾裡,重新啟動系統,發現分辨率變了,光標不靈活(可能是分辨率的關系!),屏幕大小感覺是原來的兩倍。這是為什麼呢?

ps:本文挺長!如果你有疑問請提出來,交流交流,學習學習!如果你知道上面的問題的原因,那麼請大神留幾句話共小弟學習學習!

Copyright © Linux教程網 All Rights Reserved