? ? ? ?本文側重點在于BIOS查找“啟動順序”(Boot Sequence)之前,也就是從按下電源到BIOS移交權限之間的這一段
我們從按下電源開始。
? ? ? ?首先,是CPU Reset。主板加電之后在電壓尚未穩定之前,其北橋控制芯片會向CPU發出重置信號(Reset),此時CPU進行初始化。當電壓穩定后,控制芯片會撤銷Reset信號,CPU開始工作。我們要探討的第一個問題就是CPU執行的第一條指令的位置
。
? ? ? ?現在網上流傳的資料基本上是8086 CPU的資料,給出的說法一般是這樣:CS寄存器初始化為0xF000,IP寄存器初始化為0xFFF0,所以按照CPU實模式地址計算法則,CPU執行的第一條指令地址是CS*10h+IP,即0xFFFF0處。8086CPU確實如此,但是80386及其以上的CPU
其計算地址法則卻不是這樣。
? ? ? ?第一點,80386及其以上的現代CPU(以下簡稱CPU)加電Reset之后并不是直接進入實模式;
? ? ? ?第二點,CPU在合成地址的時候不區分實模式和保護模式。
? ? ? ?我們知道,CPU進入保護模式的方法是CR0寄存器的PE位置為1。而在CPU剛加電的時候,CR0寄存器的PE位確實沒有置1,那么,此時是實模式嗎?暫時還不是,Intel并沒有給給出表示此時CPU狀態的術語名詞,我們姑且稱之為混沌模式吧。自從80386以來,因為增加了保護模式的緣故,CS等段寄存器不再是簡簡單單的段寄存器了,而是一個包含了段選擇器(segment selector)、段基址(segment base),以及段限制(segment limit)的一組復雜寄存器。顯然段基址決定著內存段的基地址。不過需要說明的是作為程序員只能操作CS寄存器中的“段選擇器”這16位的大小,其它的區域作為隱藏區域對程序員不可見,我們無法訪問。
? ? ? ?當CPU處于段尋址模式的時候,假設段選擇器(我們能訪問的那16位)裝入了0xF000,那么CPU會先將F000 * 10h也就是F0000h裝入段基址里。之后需要合成地址的時候不考慮別的,而是直接從之前合成好的段基址里讀出基地址F0000h加上IP寄存器里的偏移生成地址。如果CS寄存器的值不發生改變,段基址部分就不會發生改變。所以我們說,CPU在合成地址的時候不區分實模式和保護模式,CPU只是機械的從隱藏區域讀出來段基址和IP寄存器的數值相加。
? ? ? ?Intel這樣做的目的何在?當然是為了效率,也許實模式的地址計算很快,但是保護模式計算一個地址還要有去內存中尋找段描述符等工作,這會大大影響CPU的效率,而我們知道,程序具有訪問局部區域里的數據和代碼的趨勢(局部性原理)。所以在CS寄存器沒有發生變化的時候,直接從之前隱藏區域獲取段基址豈不是更快?當CS寄存器被修改呢?那CPU就再進行一次查找段描述符的操作,然后更新隱藏區域。
? ? ? ?順便說一句,利用CPU的這個特性,我們可以先進入CPU的保護模式配置好某個起始地址為0,段限長為4G的段描述符并加載到除過CS的其他任意寄存器(CS寄存器改變太過頻繁),然后退出保護模式進入實模式去執行指令,利用80386之后長達32位的ESI或EDI寄存器和那個段寄存器配合尋址就可以在實模式下訪問全部地址空間了。(中途不可以修改那個載入過段描述符的寄存器,否則其隱藏區域會被更新)。這種方式是一種稱之為Big Real Mode的模式,其區別于實模式和保護模式。
? ? ? ?說了這么多,我們找張圖片大概說明一下CS寄存器。下圖來自Intel的“CPU使用說明書”,著名的那三卷開發文檔的第三卷《Intel? 64 and IA-32 Architectures Software Developer’s Manual Volume 3A: System Programming Guide》P91中的插圖,算是對上文的證明,至于隱藏區域有多大,Intel并沒有說,不過不會短于一個段描述符的大小,也就是至少有8字節(64位)。(純屬猜測)
? ? ? ?CPU在Reset之后,IP寄存器被置為0x0000FFFF,CS寄存器的段選擇器默認值是0xF000,而隱藏區域中的段基址卻沒有按照實模式的標準去裝入,而是被置為0xFFFF0000。那么CPU生成的第一條訪問地址是什么呢?顯而易見,80383之后的CPU合成的是0xFFFFFFF0這個地址,這也符合Intel文檔的說法。也有資料顯示從80286之后的CPU就是這個地址了,暫時沒有考證。即便是有其它文檔有不同見解,還是覺得Intel的說法更靠譜,畢竟還是人家造的處理器不是。
? ? ? ?再來一張圖,說明下80386以上CPU在保護模式下的尋址圖,同樣來自那個PDF。
? ? ? ?所以我們說CPU執行第一條指令的時候不是實模式也不是保護模式,而是一種怪異的中間模式。
? ? ? ? 問題又來了,這一條指令在哪?我們知道計算機開機后首先讀取哪里呢?BIOS!對就是它,這一條指令會被指向BIOS。
? ? ? ?我們雖然已經假定過讀者知道線性地址空間的大致意思,但我覺得還是有必要簡單說一下,IBM PC一部分的端口采用獨立編址,而另一部分采用端口統一編址,傳統PC機使用0x000~0x3FF共1024個端口地址。現代PC則有多達64KB的 I/O 端口提供編址。不過顯存等一些硬件的地址還有所有BIOS的編址卻在線性地址空間里。這也是我們所謂的32位操作系統沒辦法完全利用4G內存的原因,盡管尋址能力有4G(共2的32次方個地址),但是并不是所有的地址都能分配給內存使用。
? ? ? ?對于傳統的CPU+北橋+南橋類型的主板來說,CPU的地址請求通過FSB(Front Side BUS前端總線)到達北橋,北橋將這個請求送到南橋。而對于最新的主板芯片組來說,北橋和CPU封裝在一顆芯片里面,所以會看到這個請求通過DMI/QPI(Quick Path Interconnect,即快速通道互聯,是Intel用來取代FSB的新一代高速總線,CPU與CPU之間或者CPU與北橋芯片之間都可以使用QPI相連。在民用級的i7+X58平臺,i7處理器與X58北橋芯片之間就通過QPI總線相連)被送到南橋。請求到達南橋后,南橋根據目前的地址映射表的設置決定是否將請求轉發到SPI(Serial Peripheral Interface)或者LPC(Low Pin Count)。
? ? ? ? 這里貌似說的過于底層了,簡單說就是南橋芯片擁有一張地址映射表,當有地址解析的請求到來時,南橋查看這張表決定將地址解析到何處去。這張表里有兩個特殊區域,一個是從地址空間4G向下,大小從4MB到16MB不等的一個區域,我們以4MB為例,地址空間從FFFC00000h~FFFFFFFFh。稱之為Range 4G。第二個區域一般是是從1MB向下128KB的范圍,即E Segment和F Segment,從E0000~FFFFF,稱之為Legacy Range,也就是說,FFFC00000hFFFFFFFFh之間和E0000FFFFF之間的尋址請求都會被導向到SPI/LPC,最終指向了BIOS。
? ? ? ?呼~說了這么多,這個地址總算是指向了BIOS了。解決了第一條指令,接下呢?廠商們有分歧了,Intel設計的EFI(Extensible Firmware Interface)的做法和傳統的Legacy BIOS就不一樣了。
? ? ? ?就Legacy BIOS來說,放在0xFFFFFFF0的第一條指令一般是一個遠跳轉指令(far jump),也就是說CPU在執行Legacy BIOS時,會直接從0xFFFFFFF0跳回F Segment,回到1MB以下這個Legacy BIOS的老巢里去。而EFI BIOS的第一條指令是wbinvd(清洗CPU高速緩存),之后做一些設定之后,會直接進入保護模式。所以EFI BIOS是從南橋Region 4G通過,并不需要Legacy Region。
? ? ? ?必須說明,這里提到的一些說法參考自一些國外論文及其譯文,我只能考證其說法而沒有辦法考證原作者。雖然在博文內容上我要求自己按照論文來寫,但是引文上沒有辦法考證的我就只考證說法的正確性而不注明出處了,大家見諒