作者:曹達(dá)士 全國(guó)Linux領(lǐng)域貢獻(xiàn)最多的十大工程師之一 X86架構(gòu)服務(wù)器資深專家,30年IT領(lǐng)域經(jīng)驗(yàn) 文章摘自聯(lián)想超級(jí)課 今天我們講一下X86Linux的啟動(dòng)。 一個(gè)計(jì)算機(jī)的啟動(dòng),最主要就是CPU的啟動(dòng)。CPU啟動(dòng)就是把這個(gè)CPU的里邊的各個(gè)寄存器設(shè)置到一個(gè)已知的、固定的狀態(tài),并且從這里開(kāi)始執(zhí)行。 01 指針啟動(dòng) 如下圖的右邊這有幾個(gè)就是 X86 的話的一些比較清晰可見(jiàn)的寄存器,其中最重要的是 EIP,即指令指針。 指令指針就表示它要執(zhí)行的下一個(gè)指令,當(dāng)我們 CPU 一發(fā)生 Reset 以后,這個(gè)EIP作為一個(gè)固定的值,就會(huì)從這里開(kāi)始執(zhí)行,這里通常是 BIOS 的程序。 X86 的 CPU 有三種啟動(dòng)方式:
加電啟動(dòng)和 Reset 非常類似,不但把右邊的這些值都設(shè)到一個(gè)固定的狀態(tài),我們還把CPU其它的很多寄存器都設(shè)置到一個(gè)已知的固定的狀態(tài)。 那么INIT不同呢?它僅僅是把右邊的這些設(shè)置到一個(gè)固定的已知的狀態(tài),其它的寄存器它基本上保持不動(dòng)。目的就是為了你啟動(dòng)完之后還能讀原來(lái)寄存器里的東西。這些寄存器主要是記錄了一些上一次啟動(dòng)的CPU錯(cuò)誤狀態(tài),這樣我們可以以此來(lái)做診斷。 現(xiàn)在說(shuō)Power On 通常叫做冷啟動(dòng),Reset 我們通常叫做熱啟動(dòng),它倆是基本一致,但是 INIT 就很不同,但是總體上來(lái)說(shuō),它還是從CPU一個(gè)固定的狀態(tài)開(kāi)始執(zhí)行程序,X86 Reset之后,它就處于一個(gè) Real Address 模式。 這個(gè)講起來(lái)話長(zhǎng),因?yàn)?X86 的歷史比較悠久,從80、86、80、88 ,它現(xiàn)在有Real Address模式有protect模式,還有現(xiàn)在X86 64模式,Reset 之后它一定是處于 Real Address 模式,每個(gè)CPU被Reset之后,它從一個(gè)固定的地方開(kāi)始執(zhí)行。 第一個(gè)通常是它要執(zhí)行一個(gè)協(xié)議,選取一個(gè)CPU來(lái)執(zhí)行,其他CPU都等待,我們要選取一個(gè)所謂主CPU(其實(shí)它的標(biāo)準(zhǔn)術(shù)語(yǔ)叫Bootstrapping CPU),就是說(shuō)選完了之后,只有主CPU 繼續(xù)執(zhí)行,其它都進(jìn)入一個(gè)等待狀態(tài),就是等待所有的其它CPU都等待這個(gè)BSP給它發(fā)指令,它再開(kāi)始繼續(xù)執(zhí)行。 BSP選出來(lái)之后,它就開(kāi)始執(zhí)行BIOS代碼,這個(gè)BIOS代碼通常我們又叫它POST過(guò)程,它相當(dāng)于機(jī)器一個(gè)自檢,當(dāng)機(jī)器自檢完了 ,BIOS走完了之后,它要做的一件事情就是要找到一個(gè)文件,并且把它讀到內(nèi)存里,然后開(kāi)始執(zhí)行它。 這個(gè)時(shí)候我們認(rèn)為post自檢完了,要開(kāi)始讀取并執(zhí)行第一個(gè)外部程序,一般叫它Boot Load,那么它這個(gè)如何找到并讀取并執(zhí)行它呢? 第一個(gè)問(wèn)題主要是找到并讀取它,這里又分幾種,比如
Legacy BIOS 啟動(dòng) Legacy BIOS一般都從某個(gè)硬盤上啟動(dòng),通常在過(guò)去這個(gè)叫C盤,因?yàn)檫^(guò)去只有一個(gè)盤叫C盤,現(xiàn)在當(dāng)然磁盤很多了,你還是要指定一個(gè)啟動(dòng)盤,從哪個(gè)卡上的哪個(gè)盤來(lái)啟動(dòng)。 如果是指定了,它就讀你指定的盤,這個(gè)硬盤的同一個(gè)扇區(qū),512字節(jié)。讀進(jìn)來(lái)之后,首先檢查扇區(qū)的標(biāo)記,標(biāo)記這塊盤是不是可以啟動(dòng)的,如果標(biāo)記它不是可以啟動(dòng)的,那么它會(huì)報(bào)告一個(gè)錯(cuò)誤,大概就是找不到啟動(dòng)盤之類。 如果標(biāo)記的它是可以啟動(dòng)的一個(gè)硬盤,那么它就會(huì)開(kāi)始執(zhí)行,當(dāng)然了這時(shí)候這個(gè)扇區(qū)的內(nèi)容已經(jīng)在內(nèi)存里了,它就開(kāi)始執(zhí)行從0開(kāi)始一個(gè)446字節(jié)長(zhǎng)度的這個(gè)小程序,當(dāng)然了我們看這個(gè)程序很小,所以它其實(shí)它還要想辦法,讀到另外一個(gè)更大的程序,才能夠把我們說(shuō)的Boot Load,比如說(shuō)Grub完完全全加載起來(lái),光446字節(jié)肯定是不行的。 UEFI BIOS 啟動(dòng) UEFI BIOS會(huì)好一點(diǎn),它主板上記錄的是一個(gè)啟動(dòng)文件路徑,這個(gè)啟動(dòng)文件路徑就是某個(gè)盤某個(gè)分區(qū)里面的某個(gè)文件,這個(gè)分區(qū)是什么文件系統(tǒng)呢?通常它是FAT32文件系統(tǒng),又叫ESP文件系統(tǒng)。 如果UEFI想啟動(dòng),一定存在這么一個(gè)分區(qū),它有這么一個(gè)FAT文件系統(tǒng),然后在里面有某一個(gè)程序,都是寫在NVRAM里頭,我們叫做啟動(dòng)項(xiàng),它把那個(gè)程序讀進(jìn)來(lái),執(zhí)行它。 這樣看UEFI肯定比Legacy要好多了。Legacy第一步你必須是446字節(jié)的那個(gè),UEFI一下子可以把整個(gè)文件讀進(jìn)來(lái),不需要分幾步來(lái)做。 光盤啟動(dòng) 從光盤啟動(dòng)是怎么啟動(dòng)呢?光盤在Legacy模式下,它有一個(gè)讀一個(gè)特殊的記錄,這個(gè)記錄標(biāo)注它是啟動(dòng)記錄,這個(gè)啟動(dòng)記錄可以任意的,沒(méi)有過(guò)去光盤446字節(jié)的限制了。 所以說(shuō)其實(shí)盡管在Legacy模式下,從CD上啟動(dòng)它也要容易一些,因?yàn)樗褪亲x固定的記錄,記錄一個(gè)任意大的文件,讀進(jìn)來(lái)直接執(zhí)行。 如果在UEFI下它也是一個(gè)啟動(dòng)記錄,但是這個(gè)記錄本身,并不是一個(gè)文件,它是一個(gè)文件系統(tǒng)的鏡像。這個(gè)文件系統(tǒng)有啥東西呢?可能主要有啟動(dòng)文件。 為什么UEFI跟Legacy啟動(dòng)模式的不一樣呢,在理論上來(lái)說(shuō)它需要區(qū)分,你是Legacy BIOS,你就讀那一個(gè)記錄,那個(gè)記錄就是個(gè)文件,直接讀取來(lái)執(zhí)行它。那如果你是UEFI,那你就找到FAT32的鏡像,這里面有文件,然后你把它讀進(jìn)來(lái)執(zhí)行。 不管是Legacy還是UEFI BIOS,如果都不行,還可以從網(wǎng)絡(luò)啟動(dòng),從網(wǎng)絡(luò)啟動(dòng)也是要首先要找到并且把一個(gè)啟動(dòng)文件讀進(jìn)來(lái),那么這里它就稍微費(fèi)勁一點(diǎn)。 網(wǎng)絡(luò)啟動(dòng) 網(wǎng)絡(luò)啟動(dòng)最早功能全部寫在網(wǎng)卡里,也就是說(shuō)每個(gè)網(wǎng)卡廠商都自己寫了一段從網(wǎng)絡(luò)的啟動(dòng)的程序,然后把它燒在網(wǎng)卡里,后來(lái)有UEFI之后,Intel就主導(dǎo)制定了一個(gè)PXE協(xié)議,這個(gè)PXE其實(shí)就是把這些網(wǎng)絡(luò)啟動(dòng)要用的公共的東西,納入到了BIOS功能里。 比如說(shuō)TCP-IP、TFTP、DHCP,這些東西跟網(wǎng)卡沒(méi)有關(guān)系,網(wǎng)卡要做的就是收發(fā)報(bào)而已。 PXE協(xié)議分四步: 第一步,必須有DHCP,先發(fā)DHCP包,我要取得IP地址,才能做后面的事情,如果你是想啟動(dòng)的話,在DHCP包里面還附加了很多東西,附加了一些標(biāo)記。 第二步,在包發(fā)出去之后,看到這個(gè)標(biāo)記的人,如果他能提供PXE服務(wù),也會(huì)響應(yīng)關(guān)于PXE的東西。 PXE響應(yīng)它什么呢?有幾個(gè)IP地址,這幾個(gè)地址是可以提供啟動(dòng)服務(wù)的,而且每個(gè)IP地址它什么文件,都可以告訴你。 最后一步,有了啟動(dòng)服務(wù)器的地址和我要啟動(dòng)的文件,那么我就通過(guò)TFTP從這個(gè)服務(wù)器下載這個(gè)文件就可以了。 下面這是一檔例子,在標(biāo)準(zhǔn)的DHCP服務(wù)里,附帶提供PXE支持,它支持在哪呢:首先它檢視DHCP包里面是不是包含這個(gè)字段,如果包含這個(gè)字段,就告訴你啟動(dòng)服務(wù)器是誰(shuí),你去跟他找啟動(dòng)文件。 光告訴你啟動(dòng)服務(wù)器是誰(shuí)還不行,還必須告訴你啟動(dòng)文件是啥,啟動(dòng)文件根據(jù)機(jī)型不同它是有區(qū)別的,比如你是啟動(dòng)Power PC,還要啟動(dòng)mips,還要啟動(dòng)SUN的SPARC。 02 啟動(dòng)Linux需要什么 光是X86就有三種架構(gòu),一種是64位的UEFI,一種是32位的UEFI,另外一種就是老的i386的PC模式,叫做Legacy BIOS模式。 所以根據(jù)包里面告訴我的架構(gòu)不同,就反饋你文件名,不同的文件名給你,等于交互完之后就得到兩個(gè):一個(gè)是不同IP地址,一個(gè)是文件名。通過(guò)TFTP給它下載下來(lái)開(kāi)始執(zhí)行就好了。后面的就跟從硬盤上是一樣的了。 如果是啟動(dòng)Linux呢?Linux現(xiàn)在需要什么東西? Linux 只需要兩個(gè)東西(當(dāng)然了其實(shí)它是需要三個(gè)東西),但文件只需要兩個(gè):一個(gè)是內(nèi)核文件,一個(gè)叫做INITRAMFS文件。 我們都知道內(nèi)核文件肯定需要,任何一個(gè)操作系統(tǒng)起來(lái)都必須先把內(nèi)核設(shè)置好,啟動(dòng)好。 INITRAMFS是一個(gè)給Linux用作根的一個(gè)文件,大家知道Unix啟動(dòng)一定需要有一個(gè)根文件系統(tǒng),這個(gè)Unix才能夠運(yùn)行。如果在內(nèi)核啟動(dòng)的時(shí)候,到最后找不到這個(gè)根文件系統(tǒng)就會(huì)啟動(dòng)失敗。 有了INITRAMFS文件之后,Linux內(nèi)核的啟動(dòng)過(guò)程中會(huì)展開(kāi)INITRAMFS文件。這個(gè)文件其實(shí)就是一個(gè)CPIO包,它把這個(gè)包展開(kāi)到它的內(nèi)存里,在展開(kāi)的內(nèi)存里會(huì)看到目錄架構(gòu),會(huì)有文件,它就到這一塊區(qū)域當(dāng)作它的根。 也就是說(shuō)無(wú)論如何,內(nèi)核文件+INITRAMFS文件一定能夠啟動(dòng)一個(gè)小Linux。 什么叫小Linux,因?yàn)镮NITRAMFS不可能很大,一般幾十兆,所以它啟動(dòng)起來(lái)之后,它就是一個(gè)完完全全放在內(nèi)存里的Linux,它小是因?yàn)檫@個(gè)文件大小受到了限制,那么內(nèi)核文件加上INITRAMFS展開(kāi)后,把它作為一個(gè)根文件系統(tǒng),這就是一個(gè)小的全部在內(nèi)存里的Linux。 麻雀雖小,五臟俱全,有內(nèi)存和輸入輸出,就可以做任何事情,所以它適合做一些工具類的東西,安裝肯定是也通過(guò)它就可以繼續(xù)安裝,比如往硬盤上裝東西或者做外設(shè)的配置更改、FW升級(jí)等,它非常適合做一次性的工作。 我們通常用一個(gè)Live Image來(lái)啟動(dòng)這樣一個(gè)Linux,當(dāng)然它稍微比這個(gè)還復(fù)雜一點(diǎn),但是基本上還是一個(gè)完完全全在內(nèi)存里的小Linux,啟動(dòng)完后把U盤拿走也沒(méi)有任何問(wèn)題。 當(dāng)然我們大部分的情況下啟動(dòng)可能不僅僅是想啟動(dòng)一個(gè)內(nèi)核里的小Linux。 比如當(dāng)前我有一個(gè)硬盤,硬盤上放了一個(gè)很大的Linux,幾十個(gè)G,想啟動(dòng)這個(gè)系統(tǒng)就根據(jù)啟動(dòng)Grub的參數(shù),叫root=什么什么,這時(shí)候這個(gè)小Linux就會(huì)找這個(gè)root,root找到了之后(通常它是一個(gè)硬盤上的一個(gè)分區(qū)的一個(gè)文件系統(tǒng)),我們給mount到一個(gè)目錄下面,因?yàn)楫?dāng)前已有根,只不過(guò)這個(gè)根完完全全是個(gè)內(nèi)存文件系統(tǒng),我們可能會(huì)創(chuàng)建一個(gè)目錄叫newroot,然后把你制定的這個(gè)根mount到這個(gè)newroot下面。 mount好后把其它的進(jìn)程都終止掉,然后把當(dāng)前這個(gè)根里面的基本上所有其它文件都刪掉釋放內(nèi)存。但newroot必須保留,因?yàn)镹ewroot掛載的新的根文件系統(tǒng),到最后就執(zhí)行一個(gè)操作叫switch_root,把這個(gè)newroot變?yōu)楦賵?zhí)行根里面的INIT,就完成拉起來(lái)了一個(gè)新的Linux,即硬盤上的Linux。 在這里內(nèi)核啟動(dòng)的時(shí)候已經(jīng)固定了,也就是說(shuō)我們拉起來(lái)的這個(gè)硬盤上的Linux,一定要跟我當(dāng)前的內(nèi)核是兼容的,那么安裝完系統(tǒng)以后,剛開(kāi)始啟動(dòng)的內(nèi)核到最后切換到根上,拉起來(lái)的這個(gè)大的Linux一定是相同內(nèi)核的,否則就會(huì)有兼容性的問(wèn)題。 這就是如何啟動(dòng)一個(gè)硬盤上的大Linux或在網(wǎng)絡(luò)上的一個(gè)大Linux,或者是任何其它的比如U盤上的Linux環(huán)境你也可以給它拉起來(lái)。 當(dāng)然如果在U盤上的Linux我建議你直接用全部在內(nèi)存里的Linux,沒(méi)必要再拉一次在網(wǎng)絡(luò)上的或者是在硬盤上的。INITRAMFS不可能放得下所有東西。 一般還有一個(gè)二次的,就是拉起來(lái)Linux在中間的過(guò)程,可以說(shuō)基本上除了內(nèi)核沒(méi)變,其它的環(huán)境都變掉了,這就是啟動(dòng)一個(gè)完整的Linux。 03 另類的啟動(dòng):KEXEC 下面我們介紹一下另類的啟動(dòng)。 Linux還有一種啟動(dòng)方式叫做KEXEC,KEXEC本來(lái)是一個(gè)Linux的內(nèi)核的這個(gè)開(kāi)發(fā)者,因?yàn)樗麄兘?jīng)常修改內(nèi)核,修改完了之后要重啟看看,每一次重啟都要經(jīng)過(guò)一個(gè)CPU的Reset的冷啟動(dòng)也好,熱啟動(dòng)也好,那是很痛苦的事情。 因?yàn)镽eset這個(gè)CPU之后一定先走BIOS,BIOS走完了最后走Grub,最后有個(gè)Boot Load,Grub走完了才啟動(dòng)Linux,因?yàn)橄舆@個(gè)很煩,他們就開(kāi)發(fā)了一個(gè)東西,就是在當(dāng)前的Linux下,我突發(fā)奇想,想換一個(gè)新內(nèi)核,他就用這個(gè)KEXEC先把一個(gè)內(nèi)核加進(jìn)來(lái)然后跳轉(zhuǎn)到新內(nèi)核里,相當(dāng)于執(zhí)行了一次Linux重啟,但是注意跳過(guò)了前面的POST和Grub。 這就是那位開(kāi)發(fā)人員懶就開(kāi)發(fā)了一個(gè)這個(gè)功能,這個(gè)功能確實(shí)能省很多時(shí)間。 如果你要想讓內(nèi)核能夠這么加載的話就有一個(gè)要求,要求Position Independent Code這段代碼可以再任意加載到任意地址,如果不能加載到任意地址的話,就要求固定地址。 那在當(dāng)前內(nèi)核如何加載另外一個(gè)內(nèi)核?跟冷啟動(dòng)一樣其實(shí)有三樣?xùn)|西,一個(gè)是內(nèi)核文件,先把它加進(jìn)來(lái),然后INITRAMFS文件也加進(jìn)來(lái),然后啟動(dòng)參數(shù)主要是指根,總的來(lái)說(shuō)這個(gè)KEXEC是一個(gè)非常奇妙的啟動(dòng)Linux方式。大家可以試一下在自己Linux上用它來(lái)試一下。 04 Linux kdump模式 最后我們要講一下Linux kdump模式,緊密依賴KEXEC的模式的dump非常重要。 dump是什么? 就是應(yīng)用CRASH掉的時(shí)候,應(yīng)用當(dāng)時(shí)占的內(nèi)存的所有的內(nèi)容,然后分析應(yīng)用在哪里CRASH掉了。系統(tǒng)也一樣,當(dāng)Linux系統(tǒng)出了某種錯(cuò)誤,哪怕是硬件出了某種錯(cuò)誤進(jìn)行不下去的時(shí)候怎么診斷它,我們需要把它當(dāng)前的內(nèi)存的內(nèi)容全拷貝下來(lái)。 如何拷貝呢? 在CRASH的時(shí)候,如果沒(méi)有kdump來(lái)支持,CRASH就hung在那了,如果啟動(dòng)了kdump,在啟動(dòng)內(nèi)核,生產(chǎn)系統(tǒng)內(nèi)核的時(shí)候,有一個(gè)參數(shù)叫 Crashkernel,Crashkernel等于多少多少,就是預(yù)留一塊內(nèi)存給這個(gè)kdump的kernel dump內(nèi)核轉(zhuǎn)儲(chǔ)用。預(yù)留完后,當(dāng)生產(chǎn)系統(tǒng)起來(lái),它的一個(gè)服務(wù)就是把kdump所用的內(nèi)核加載到這塊預(yù)留的區(qū)域里。 為什么要這樣做呢? 因?yàn)橄到y(tǒng)CRASH的時(shí)候再加載內(nèi)核好像有問(wèn)題,其實(shí)CRASH這些問(wèn)題啥都做不了,內(nèi)核已經(jīng)沒(méi)法加載了,動(dòng)都動(dòng)不了了,就一定要在生產(chǎn)系統(tǒng)剛起的時(shí)候預(yù)留一塊區(qū)域,這塊區(qū)域誰(shuí)都不能使用,然后把一個(gè)內(nèi)核加在里面,INITRAMFS加在里面,那些參數(shù)也加在里面,這樣當(dāng)這個(gè)生產(chǎn)內(nèi)核跑著跑著要CRASH了,如果這個(gè)區(qū)域都設(shè)置好了它就會(huì)直接跳轉(zhuǎn)到這個(gè)區(qū)域里新的內(nèi)核里,開(kāi)始新的內(nèi)核新的一個(gè)Linux環(huán)境的啟動(dòng)。 這個(gè)Linux環(huán)境會(huì)限制在預(yù)留的區(qū)域里,在預(yù)留的區(qū)域里啟動(dòng)完之后,它要做的事情就是第一,把前一個(gè)環(huán)境的比如說(shuō)100個(gè)G也好200個(gè)G也好的內(nèi)容拷貝出來(lái),拷貝到指定的某一個(gè)磁盤的目錄下,這就是kdump。 Kdump確實(shí)是非常好的想法,它依賴于KEXEC,KEXEC是一個(gè)快速的Linux啟動(dòng),很多程序員不耐煩那個(gè)POST的過(guò)程太長(zhǎng),Kdump很好的解決了這個(gè)問(wèn)題。 |
|
來(lái)自: 清風(fēng)劍舞 > 《知識(shí)文庫(kù)》