經(jīng)過對 Linux 系統(tǒng)有了一定了解和熟悉后,想對其更深層次的東西做進一步探究。這當中就包括系統(tǒng)的啟動流程、文件系統(tǒng)的組成結(jié)構(gòu)、基于動態(tài)庫和靜態(tài)庫的程序在執(zhí)行時的異同、協(xié)議棧的架構(gòu)和原理、驅(qū)動程序的機制等等。
作者在綜合了現(xiàn)有網(wǎng)上大家智慧的基礎(chǔ)上,結(jié)合對 Linux 2.6.32 的內(nèi)核代碼的研讀,基于CentOS 6.0系統(tǒng)對Linux的啟動流程做了些分析。由于才疏學淺,知識所限,有些地方分析不妥之處還請各位高手不吝賜教。
OK,我們言歸正傳。對于一臺安裝了Linux系統(tǒng)的主機來說,當用戶按下開機按鈕時,一共要經(jīng)歷以下幾個過程,如圖:
其中,每個過程都執(zhí)行了自己該做的初始化部分的事情,有些過程又可分為好幾個子過程。接下來,我們就對每個階段做一個詳細分析和講解。
BIOS自檢
稍有計算機基礎(chǔ)的人都應該聽過BIOS(Basic Input / Output System),又稱基本輸入輸出系統(tǒng),可以視為是一個永久地記錄在ROM中的一個軟件,是操作系統(tǒng)輸入輸出管理系統(tǒng)的一部分。早期的BIOS芯片確實是”只讀”的,里面的內(nèi)容是用一種燒錄器寫入的,一旦寫入就不能更改,除非更換芯片?,F(xiàn)在的主機板都使用一種叫Flash EPROM的芯片來存儲系統(tǒng)BIOS,里面的內(nèi)容可通過使用主板廠商提供的擦寫程序擦除后重新寫入,這樣就給用戶升級BIOS提供了極大的方便。
BIOS的功能由兩部分組成,分別是POST碼和Runtime服務(wù)。POST階段完成后它將從存儲器中被清除,而Runtime服務(wù)會被一直保留,用于目標操作系統(tǒng)的啟動。BIOS兩個階段所做的詳細工作如下:
步驟1:上電自檢POST(Power-on self test),主要負責檢測系統(tǒng)外圍關(guān)鍵設(shè)備(如:CPU、內(nèi)存、顯卡、I/O、鍵盤鼠標等)是否正常。例如,最常見的是內(nèi)存松動的情況,BIOS自檢階段會報錯,系統(tǒng)就無法啟動起來;
步驟2:步驟1成功后,便會執(zhí)行一段小程序用來枚舉本地設(shè)備并對其初始化。這一步主要是根據(jù)我們在BIOS中設(shè)置的系統(tǒng)啟動順序來搜索用于啟動系統(tǒng)的驅(qū)動器,如硬盤、光盤、U盤、軟盤和網(wǎng)絡(luò)等。我們以硬盤啟動為例,BIOS此時去讀取硬盤驅(qū)動器的第一個扇區(qū)(MBR,512字節(jié)),然后執(zhí)行里面的代碼。實際上這里BIOS并不關(guān)心啟動設(shè)備第一個扇區(qū)中是什么內(nèi)容,它只是負責讀取該扇區(qū)內(nèi)容、并執(zhí)行。
至此,BIOS的任務(wù)就完成了,此后將系統(tǒng)啟動的控制權(quán)移交到MBR部分的代碼。
PS: 在個人電腦中,Linux的啟動是從0xFFFF0地址開始的。
系統(tǒng)引導
我們首先來了解一下MBR,它是Master Boot Record的縮寫。硬盤的0柱面、0磁頭、1扇區(qū)稱為主引導扇區(qū)。它由三個部分組成,主引導程序(Bootloader)、 硬盤分區(qū)表DPT(Disk Partition table)和硬盤有效標志(55AA),其結(jié)構(gòu)圖如下所示:
磁盤分區(qū)表包含以下三部分:
1)、Partition ID (5:延申 82:Swap 83:Linux 8e:LVM fd:RAID)
2)、Partition起始磁柱
3)、Partition的磁柱數(shù)量
通常情況下,諸如lilo、grub這些常見的引導程序都直接安裝在MBR中。我們以grub為例來分析這個引導過程。
grub引導也分為兩個階段stage1階段和stage2階段(有些較新的grub又定義了stage1.5階段)。
1)、stage1:stage1是直接被寫入到MBR中去的,這樣機器一啟動檢測完硬件后,就將控制權(quán)交給了GRUB的代碼。也就是上圖所看到的前446個字節(jié)空間中存放的是stage1的代碼。BIOS將stage1載入內(nèi)存中0x7c00處并跳轉(zhuǎn)執(zhí)行。
stage1(/stage1/start.S)的任務(wù)非常單純,僅僅是將硬盤0頭0道2扇區(qū)讀入內(nèi)存。而0頭0道2扇區(qū)內(nèi)容是源代碼中的/stage2/start.S,編譯后512字節(jié),它是stage2或者stage1_5的入口。而此時,stage1是沒有識別文件系統(tǒng)的能力的。如果感覺腦子有些暈了,那么下面的過程就直接跳過,去看stage2吧!
【外傳】定位硬盤的0頭0道2扇區(qū)的過程:
BIOS將stage1載入內(nèi)存0x7c00處并執(zhí)行,然后調(diào)用BIOS INIT13中斷,將硬盤0頭0道2扇區(qū)內(nèi)容載入內(nèi)存0x7000處,然后調(diào)用copy_buffer將其轉(zhuǎn)移到內(nèi)存0x8000處。在定位0頭0道2扇區(qū)時通常有兩種尋址方式:LBA和CHS。如果你是刨根問底兒型的愛好者,那么此時去找谷哥打聽打聽這兩種方式的來龍去脈吧。
2)、stage2:嚴格來說這里還應該再區(qū)分個stage1.5的,就一并把stage1.5放在這里一起介紹了,免得大家看得心里亂哄哄的。好的,我們繼續(xù)說0頭0到2扇區(qū)的/stage2/start.S文件,當它的內(nèi)容被讀入到內(nèi)存之后,它的主要作用就是負責將stage2或stage1.5從硬盤讀到內(nèi)存中。如果是stage2,它將被載入到0x820處;如果是stage1.5,它將被載入到0x2200處。這里的stage2或者stage1_5不是/boot分區(qū)/boot/grub目錄下的文件,因為這個時候grub還沒有能力識別任何文件系統(tǒng)。
如果start.S加載stage1.5:stage1.5它存放在硬盤0頭0道3扇區(qū)向后的位置,stage1_5作為stage1和stage2中間的橋梁,stage1_5有識別文件系統(tǒng)的能力,此后grub才有能力去訪問/boot分區(qū)/boot/grub目錄下的 stage2文件,將stage2載入內(nèi)存并執(zhí)行。
如果start.S加載stage2:同樣,這個stage2也不是/boot分區(qū)/boot/grub目錄下的stage2,這個時候start.S讀取的是存放在/boot分區(qū)Boot Sector的stage2。這種情況下就有一個限制:因為start.S通過BIOS中斷方式直接對硬盤尋址(而非通過訪問具體的文件系統(tǒng)),其尋址范圍有限,限制在8GB以內(nèi)。因此這種情況需要將/boot分區(qū)分在硬盤8GB尋址空間之前。
假如是情形2,我們將/boot/grub目錄下的內(nèi)容清空,依然能成功啟動grub;假如是情形1,將/boot/grub目錄下stage2刪除后,則系統(tǒng)啟動過程中g(shù)rub會啟動失敗。
啟動內(nèi)核
當stage2被載入內(nèi)存執(zhí)行時,它首先會去解析grub的配置文件/boot/grub/grub.conf,然后加載內(nèi)核鏡像到內(nèi)存中,并將控制權(quán)轉(zhuǎn)交給內(nèi)核。而內(nèi)核會立即初始化系統(tǒng)中各設(shè)備并做相關(guān)的配置工作,其中包括CPU、I/O、存儲設(shè)備等。
關(guān)于Linux的設(shè)備驅(qū)動程序的加載,有一部分驅(qū)動程序直接被編譯進內(nèi)核鏡像中,另一部分驅(qū)動程序則是以模塊的形式放在initrd(ramdisk)中。
Linux內(nèi)核需要適應多種不同的硬件架構(gòu),但是將所有的硬件驅(qū)動編入內(nèi)核又是不實際的,而且內(nèi)核也不可能每新出一種硬件結(jié)構(gòu),就將該硬件的設(shè)備驅(qū)動寫入內(nèi)核。實際上Linux的內(nèi)核鏡像僅是包含了基本的硬件驅(qū)動,在系統(tǒng)安裝過程中會檢測系統(tǒng)硬件信息,根據(jù)安裝信息和系統(tǒng)硬件信息將一部分設(shè)備驅(qū)動寫入 initrd 。這樣在以后啟動系統(tǒng)時,一部分設(shè)備驅(qū)動就放在initrd中來加載。這里有必要給大家再多介紹一下initrd這個東東:
initrd 的英文含義是 bootloader initialized RAM disk,就是由 boot loader 初始化的內(nèi)存盤。在 linu2.6內(nèi)核啟動前,boot loader 會將存儲介質(zhì)中的 initrd 文件加載到內(nèi)存,內(nèi)核啟動時會在訪問真正的根文件系統(tǒng)前先訪問該內(nèi)存中的 initrd 文件系統(tǒng)。在 boot loader 配置了 initrd 的情況下,內(nèi)核啟動被分成了兩個階段,第一階段先執(zhí)行 initrd 文件系統(tǒng)中的init,完成加載驅(qū)動模塊等任務(wù),第二階段才會執(zhí)行真正的根文件系統(tǒng)中的 /sbin/init 進程。
另外一個概念:initramfs
initramfs 是在 kernel 2.5中引入的技術(shù),實際上它的含義就是:在內(nèi)核鏡像中附加一個cpio包,這個cpio包中包含了一個小型的文件系統(tǒng),當內(nèi)核啟動時,內(nèi)核將這個 cpio包解開,并且將其中包含的文件系統(tǒng)釋放到rootfs中,內(nèi)核中的一部分初始化代碼會放到這個文件系統(tǒng)中,作為用戶層進程來執(zhí)行。這樣帶來的明顯的好處是精簡了內(nèi)核的初始化代碼,而且使得內(nèi)核的初始化過程更容易定制。
疑惑的是:我的內(nèi)核是2.6.32-71.el6.i686版本,但在我的/boot分區(qū)下面卻存在的是/boot/initramfs-2.6.32-71.el6.i686.img類型的文件,沒搞明白,還望高人解惑。我只知道在2.6內(nèi)核中支持兩種格式的initrd,一種是2.4內(nèi)核的文件系統(tǒng)鏡像image-initrd,一種是cpio格式。接下來我們就來探究一下initramfs-2.6.32-71.el6.i686.img里到底放了那些東西。
在tmp文件夾中解壓initrd.img里的內(nèi)容:
如果initrd.img文件的格式顯示為“initrd.img:ISO 9660 CD-ROM filesystem data”,則可直接輸入命令“mount -o loop initrd.img /mnt/test”進行掛載。
通過上的分析和我們的驗證,我們確實得到了這樣的結(jié)論:
grub的stage2將initrd加載到內(nèi)存里,讓后將其中的內(nèi)容釋放到內(nèi)容中,內(nèi)核便去執(zhí)行initrd中的init腳本,這時內(nèi)核將控制權(quán)交給了init文件處理。我們簡單瀏覽一下init腳本的內(nèi)容,發(fā)現(xiàn)它也主要是加載各種存儲介質(zhì)相關(guān)的設(shè)備驅(qū)動程序。當所需的驅(qū)動程序加載完后,會創(chuàng)建一個根設(shè)備,然后將根文件系統(tǒng)rootfs以只讀的方式掛載。這一步結(jié)束后,釋放未使用的內(nèi)存,轉(zhuǎn)換到真正的根文件系統(tǒng)上面去,同時運行/sbin/init程序,執(zhí)行系統(tǒng)的1號進程。此后系統(tǒng)的控制權(quán)就全權(quán)交給/sbin/init進程了。
初始化系統(tǒng)
經(jīng)過千辛萬苦的跋涉,我們終于接近黎明的曙光了。接下來就是最后一步了:初始化系統(tǒng)。/sbin/init進程是系統(tǒng)其他所有進程的父進程,當它接管了系統(tǒng)的控制權(quán)先之后,它首先會去讀取/etc/inittab文件來執(zhí)行相應的腳本進行系統(tǒng)初始化,如設(shè)置鍵盤、字體,裝載模塊,設(shè)置網(wǎng)絡(luò)等。主要包括以下工作:
1)、執(zhí)行系統(tǒng)初始化腳本(/etc/rc.d/rc.sysinit),對系統(tǒng)進行基本的配置,以讀寫方式掛載根文件系統(tǒng)及其它文件系統(tǒng),到此系統(tǒng)算是基本運行起來了,后面需要進行運行級別的確定及相應服務(wù)的啟動。rc.sysinit所做的事情(不同的Linux發(fā)行版,該文件可能有些差異)如下:
(1)獲取網(wǎng)絡(luò)環(huán)境與主機類型。首先會讀取網(wǎng)絡(luò)環(huán)境設(shè)置文件”/etc/sysconfig/network”,獲取主機名稱與默認網(wǎng)關(guān)等網(wǎng)絡(luò)環(huán)境。
(2)測試與載入內(nèi)存設(shè)備/proc及usb設(shè)備/sys。除了/proc外,系統(tǒng)會主動檢測是否有usb設(shè)備,并主動加載usb驅(qū)動,嘗試載入usb文件系統(tǒng)。
(3)決定是否啟動SELinux。
(4)接口設(shè)備的檢測與即插即用(pnp)參數(shù)的測試。
(5)用戶自定義模塊的加載。用戶可以再”/etc/sysconfig/modules/*.modules”加入自定義的模塊,此時會加載到系統(tǒng)中。
(6)加載核心的相關(guān)設(shè)置。按”/etc/sysctl.conf”這個文件的設(shè)置值配置功能。
(7)設(shè)置系統(tǒng)時間(clock)。
(8)設(shè)置終端的控制臺的字形。
(9)設(shè)置raid及LVM等硬盤功能。
(10)以方式查看檢驗磁盤文件系統(tǒng)。
(11)進行磁盤配額quota的轉(zhuǎn)換。
(12)重新以讀取模式載入系統(tǒng)磁盤。
(13)啟動quota功能。
(14)啟動系統(tǒng)隨機數(shù)設(shè)備(產(chǎn)生隨機數(shù)功能)。
(15)清楚啟動過程中的臨時文件。
(16)將啟動信息加載到”/var/log/dmesg”文件中。
當/etc/rc.d/rc.sysinit執(zhí)行完后,系統(tǒng)就可以順利工作了,只是還需要啟動系統(tǒng)所需要的各種服務(wù),這樣主機才可以提供相關(guān)的網(wǎng)絡(luò)和主機功能,因此便會執(zhí)行下面的腳本。
2)、執(zhí)行/etc/rc.d/rc腳本。該文件定義了服務(wù)啟動的順序是先K后S,而具體的每個運行級別的服務(wù)狀態(tài)是放在/etc/rc.d/rc*.d(*=0~6)目錄下,所有的文件均是指向/etc/init.d下相應文件的符號鏈接。rc.sysinit通過分析/etc/inittab文件來確定系統(tǒng)的啟動級別,然后才去執(zhí)行/etc/rc.d/rc*.d下的文件。
/etc/init.d-> /etc/rc.d/init.d
/etc/rc ->/etc/rc.d/rc
/etc/rc*.d ->/etc/rc.d/rc*.d
/etc/rc.local-> /etc/rc.d/rc.local
/etc/rc.sysinit-> /etc/rc.d/rc.sysinit
也就是說,/etc目錄下的init.d、rc、rc*.d、rc.local和rc.sysinit均是指向/etc/rc.d目錄下相應文件和文件夾的符號鏈接。我們以啟動級別3為例來簡要說明一下。
/etc/rc.d/rc3.d目錄,該目錄下的內(nèi)容全部都是以 S 或 K 開頭的鏈接文件,都鏈接到”/etc/rc.d/init.d”目錄下的各種shell腳本。S表示的是啟動時需要start的服務(wù)內(nèi)容,K表示關(guān)機時需要關(guān)閉的服務(wù)內(nèi)容。/etc/rc.d/rc*.d中的系統(tǒng)服務(wù)會在系統(tǒng)后臺啟動,如果要對某個運行級別中的服務(wù)進行更具體的定制,通過chkconfig命令來操作,或者通過setup、ntsys、system-config-services來進行定制。如果我們需要自己增加啟動的內(nèi)容,可以在init.d目錄中增加相關(guān)的shell腳本,然后在rc*.d目錄中建立鏈接文件指向該shell腳本。這些shell腳本的啟動或結(jié)束順序是由S或K字母后面的數(shù)字決定,數(shù)字越小的腳本越先執(zhí)行。例如,/etc/rc.d/rc3.d /S01sysstat就比/etc/rc.d/rc3.d /S99local先執(zhí)行。
3)、執(zhí)行用戶自定義引導程序/etc/rc.d/rc.local。其實當執(zhí)行/etc/rc.d/rc3.d/S99local時,它就是在執(zhí)行/etc/rc.d/rc.local。S99local是指向rc.local的符號鏈接。就是一般來說,自定義的程序不需要執(zhí)行上面所說的繁瑣的建立shell增加鏈接文件的步驟,只需要將命令放在rc.local里面就可以了,這個shell腳本就是保留給用戶自定義啟動內(nèi)容的。
4)、完成了系統(tǒng)所有的啟動任務(wù)后,linux會啟動終端或X-Window來等待用戶登錄。tty1,tty2,tty3…這表示在運行等級1,2,3,4的時候,都會執(zhí)行”/sbin/mingetty”,而且執(zhí)行了6個,所以linux會有6個純文本終端,mingetty就是啟動終端的命令。
除了這6個之外還會執(zhí)行”/etc/X11/prefdm-nodaemon”這個主要啟動X-Window
至此,系統(tǒng)就啟動完畢了。以上分析不到的地方還請各位大蝦不吝指正。
分享到微信 ×
打開微信,點擊底部的“發(fā)現(xiàn)”,
使用“掃一掃”即可將網(wǎng)頁分享至朋友圈。