天天看點

《作業系統真象還原》——2.2 軟體接力第一棒,BIOS

本節書摘來自異步社群《作業系統真象還原》一書中的第2章,第2.2節,作者:鄭鋼著,更多章節内容可以通路雲栖社群“異步社群”公衆号檢視

bios全稱叫base input & output system,即基本輸入輸出系統。

人們給任何事物起名字,肯定都不是亂起的,必然是根據該事物的特點,通過總結,精練出一些文字來辨別此事物,這個便是對一般事物取名的方法。通過名字,就能夠反應出該事物的特性。最符合特性的名字就是昵稱和外号了,比如抽油機是用來開采石油的一種機器,因為其工作時,就像“磕頭”一樣,是以大家給其起了更形象的名字—“磕頭機”。

回到bios上,輸入輸出我了解,命名中加上系統二字也明白,可為什麼還要用“基本”來修飾呢?不知道您是不是和我一樣喜歡咬文嚼字,我們必須得把它搞清楚。

2.2.1 實模式下的1mb記憶體布局

先來點背景知識,很久很久以前:

intel 8086有20條位址線,故其可以通路1mb的記憶體空間,即2的20次方=1048576=1mb,位址範圍若按十六進制來表示,是0x00000到0xfffff。不知道硬體工程師當時設計的初衷是什麼,總之人家有自己的理由,這1mb的記憶體空間被分成多個部分。

為了讓大家先有個印象,免得太抽象不容易了解,先把實模式下1mb記憶體給大家梳理一下,很辛苦的,各位看官要仔細看哈,是以感興趣或有強迫症的同學一定要背下來(玩笑),見表2-1。

《作業系統真象還原》——2.2 軟體接力第一棒,BIOS
《作業系統真象還原》——2.2 軟體接力第一棒,BIOS

先從低位址看,位址0~0x9ffff處是dram(dynamic random access memory),即動态随機通路記憶體,我們所裝的實體記憶體就是dram,如ddr、ddr2等。又要開始咬文嚼字了,動态是什麼意思?動态指此種存儲媒體由于本身電氣元件的性質,需要定期地重新整理。記憶體中的每一位都是由電容和半導體來組成的,您想,單條記憶體現在都到4gb,記憶體條的體積大小您也清楚,那麼小的面積得內建多少電容才能夠拼湊出4gb的記憶體容量,不包括相關電路元件,也得是4gb×8個電容了。如此小的電容,其缺點也是明顯的,漏電很快,是以漏電了就要及時把電補充上去,這樣資料才不至于丢失。這個補充電的過程就稱為重新整理。其實不僅是電容需要重新整理,就連電信号也是一樣的,不知道您注意了沒有,我們平時使用的網線,也是需要在每隔一定長度距離時接個中繼放大器,這個就是來放大電信号的,因為實體鍊路一長,信号衰減就特别嚴重,隻好通過這種“打氣”的方式來保持穩定了。終于把動态這一詞搞定了,不過我們最終要搞定的詞是bios中的“基本”,是以咱們還得接着看。

見表2-1,記憶體位址0~0x9ffff的空間範圍是640kb,這片位址對應到了dram,也就是插在主機闆上的記憶體條。有沒有人開始小聲嘀咕了:為什麼是對應到了dram,難道不是直接通路到我的實體記憶體dram嗎?難道我的記憶體條不是全部的記憶體?還可以通路到别處嗎?如果您有這樣的疑問,我除了回答是啊是啊之外,還是很欣慰的,終于有人和我之前想的一樣了。

一會再解釋這個,否則咱們離“基本”越來越遠了。表2-1,看頂部的0xf0000~0xfffff,這64kb的記憶體是rom。這裡面存的就是bios的代碼。bios的主要工作是檢測、初始化硬體,怎麼初始化的?硬體自己提供了一些初始化的功能調用,bios直接調用就好了。bios還做了一件偉大的事情,建立了中斷向量表,這樣就可以通過“int中斷号”來實作相關的硬體調用,當然bios建立的這些功能就是對硬體的io操作,也就是輸入輸出,但由于就64kb大小的空間,不可能把所有硬體的io操作實作得面面俱到,而且也沒必要實作那麼多,畢竟是在實模式之下,對硬體支援得再豐富也白搭,精彩的世界是在進入保護模式以後才開始,是以挑一些重要的、保證計算機能運作的那些硬體的基本io操作,就行了。這就是bios稱為基本輸入輸出系統的原因。

現在開始解釋另一個問題,在cpu眼裡,為什麼我們插在主機闆上的實體記憶體不是它眼裡“全部的記憶體”。

位址總線寬度決定了可以通路的記憶體空間大小,如16位機的位址總線為20位,其位址範圍是1mb,32位位址總線寬度是32位,其位址範圍是4gb。但以上的位址範圍是指位址總線可以觸及到的邊界,是指計算機在尋址上可以到達的疆域。可是人家并沒有說要尋哪裡,就拿16位機來說,并沒有說這1mb的尋址範圍必須得是實體記憶體(記憶體條),難道人家20位的位址總線就認得這一畝三分地?完全不是。

歸根結底的原因是這樣的:在計算機中,并不是隻有咱們插在主機闆上的記憶體條需要通過位址總線通路,還有一些外設同樣是需要通過位址總線來通路的,這類裝置還很多呢。若把全部的位址總線都指向實體記憶體,那其他裝置該如何通路呢?由于這個原因,隻好在位址總線上提前預留出來一些位址空間給這些外設用,這片連續的位址給顯存,這片連續的位址給硬碟控制器等。留夠了以後,位址總線上其餘的可用位址再指向dram,也就是指插在主機闆上的記憶體條、我們眼中的實體記憶體。示意如圖2-1所示。

《作業系統真象還原》——2.2 軟體接力第一棒,BIOS

實體記憶體多大都沒用,主要是看地線總線的寬度。還要看位址總線的設計,是不是全部用于通路dram。是以說,位址總線是決定我們通路哪裡、通路什麼,以及通路範圍的關鍵。我們平時用的機器一般是32位,上面的記憶體條并不是全部都用到了,按理說記憶體條大小超過4gb就沒意義了,超過了位址總線的勢力就是浪費。不過通過前面的介紹,即使記憶體條大小沒有超過位址總線的範圍,也不會全都能被通路到,畢竟要預留一些位址用來通路其他外設,是以最終還得看位址總線把位址指向哪塊記憶體了。這就是安裝了4gb記憶體,電腦中隻顯示3.8gb左右的原因。

總之,表示位址的那串數字是位址總線的輸入,相當于其參數,和記憶體條沒關系。cpu能夠通路一個位址,這是位址總線給做的映射,相當于給該位址配置設定了一個存儲單元,而該存儲單元要麼落在某個rom中,要麼落到了某個外設的記憶體中,要麼落到了實體記憶體條上。可以想像成,cpu給位址總線送出一個數字,在位址總線看來,這串數字就是位址。位址配置設定電路根據此位址的範圍,決定在哪個存儲媒體中配置設定一個存儲單元,最後将此位址與此存儲單元對應起來。當然事實上未必是這樣,我剛才說了,可以想像成這樣。我們學習新的知識,很多時候都是建立在原有的知識上,用原有的知識幫助學習新的知識,就像第一次聽說電動車的時候,我們潛意識裡是用車和蓄電池的概念在聯想電動車的形象。如果要學的是一種全新的知識,并且無從用舊的知識來輔助學習時,試圖靠想像力是非常有效的。對于知識的掌握,這并沒有什麼标準,每個人對知識的了解都是不同的,即使兩個人都考了滿分,其思考過程也是不同的。是以,對于一個新知識的掌握,本質上是給了一個能夠說服自己的理由,能夠自圓其說,這就夠了。

2.2.2 bios是如何蘇醒的

bios其實一直睡在某個地方,直到被喚醒……

前面熱火朝天地說了bios的功能和記憶體布局,似乎還沒說到正題上,bios是如何啟動的呢?因為bios是計算機上第一個運作的軟體,是以它不可能自己加載自己,由此可以知道,它是由硬體加載的。那這個硬體是誰呢?其實前面已經提到過了,相當于是隻讀存儲器rom,因為它一直就睡在那裡不動。

大家知道,隻讀存儲器中的内容是不可擦除的,也就是它不像動态随機通路存儲器dram那樣,掉電後,裡面的資料就會丢失。這種存儲媒體是用來存儲一成不變的資料的,當資料寫進去後,便與日月同輝,庭前坐看花開花落,不朽于天地萬物之間,哈哈,有點誇張了。

bios代碼所做的工作也是一成不變的,而且在正常情況下,其本身是不需要修改的,平時聽說的那些主機闆壞了要刷bios的情況屬于例外。于是bios順理成章地便被寫進此rom。rom也是塊記憶體,記憶體就需要被通路。此rom被映射在低端1mb記憶體的頂部,即位址0xf0000~0xfffff處,可以參考表2-1頂部的bios部分。隻要通路此處的位址便是通路了bios,這個映射是由硬體完成的。

bios本身是個程式,程式要執行,就要有個入口位址才行,此入口位址便是0xffff0。

最重要的一點來了,知道了bios在哪裡後,cpu如何去執行它,即cpu中的cs:ip值是如何組合成0xffff0的。

如果大家不了解記憶體的分段通路機制,可以參考第0章,裡面有講解cpu為什麼分段方式記憶體。說正事,cpu通路記憶體是用段位址+偏移位址來實作的,由于在實模式之下,段位址需要乘以16後才能與偏移位址相加,求出的和便是實體位址,cpu便拿此位址直接用了。這個“段基址:段内偏移位址”的組合是0xffff:0嗎?或者是0xf000:0xfff0?或者是更奇葩一點的組合:0xfeee:0x1110?或者您想出的組合比我的還奇葩,好啦,不折磨大家了,還是說正事要緊。既然作為第一個運作的程式都沒開始執行,自然就沒辦法用軟體搞定這件事了,還是得靠硬體支援才行。

在開機的一瞬間,也就是接電的一瞬間,cpu的cs:ip寄存器被強制初始化為0xf000:0xfff0。由于開機的時候處于實模式,再重複一遍加深印象,在實模式下的段基址要乘以16,也就是左移4位,于是0xf000:0xfff0的等效位址将是0xffff0。上面說過了,此位址便是bios的入口位址。

當我給出這個位址後,不知道大家意識到什麼沒有。bios是在實模式下運作的,而實模式隻能通路1mb空間(20位位址線,2的20次方是1mb)。而位址0xffff0距1mb隻有16個位元組了(見表2-1除标題外的第一行),這麼小的空間夠幹嗎?bios又要檢測硬體,做各種初始化工作,還要建立中斷向量表……16位元組的機器指令肯定幹不了這麼多事。也許有的同學會問,超過寄存器寬度會怎麼樣呢?比如0xffff0+16,這樣就溢出了,由于實模式下的寄存器寬度是16位,0xffff0+16已經超過了其最大值0xfffff。溢出的部分就會回卷到0,又會重新開始,即0xffff0+16等于0,0xffff0+17等于1。

既然此處隻有16位元組的空間了,這隻能說明bios真正的代碼不在這,那此處的代碼隻能是個跳轉指令才能解釋得通了。好,既然心裡有了推斷,那咱們就來證明這個推斷正确與否。

圖2-2是我在bochs中抓的圖,下面給大家分析一下這圖中的資訊都代表什麼。

《作業系統真象還原》——2.2 軟體接力第一棒,BIOS

首先得承認,這張圖有點超前了,這是在有了mbr後才能抓到的,否則會提示boot failed: not a bootable disk,而我們還沒有mbr,還沒有寫主引導記錄。先不管這張圖是怎麼來的啦,反正大家立即就能夠在自己的虛拟機裡看到這張圖了。大家先注意框框中的内容。一共有3個,最上面左邊第1個标有cs:ip的那個框,cs寄存器的值是0xf000,ip寄存器的值是0xfff0,也就是段基址0xf000,段内偏移位址0xfff0,這個組合出來的位址便是0xffff0,這是處理器下一條待執行指令的位址。這與上面所說的bios入口位址是吻合的。另外,因為cs和ip寄存器中存儲的是下一條要執行的指令,目前還沒有執行,也就是說,目前還沒有執行bios,這是機器剛開機的那一刻。這一刻還是值得慶祝的,因為即使是計算機行業的同學都很少看到這一刻,何況我們讓這一刻停了下來,成為永恒。

按理說,既然讓cpu去執行0xffff0處的内容(目前還不知道其是指令,還是資料),此内容應該是指令才行,否則這位址處的内容若是資料,而不是指令,cpu硬是把它當成指令來譯碼的話,一定會弄巧成拙鑄成大錯。現在咱們又有了新的推斷,實體位址0xffff0處應該是指令,繼續探索。

繼續看第二個框框,裡面有條指令jmp far f000:e05b,這是條跳轉指令,也就是證明了在記憶體實體位址0xffff0處的内容是一條跳轉指令,我們的判斷是正确的。那cpu的執行流是跳到哪裡了呢?段基址0xf000左移4位+0xe05b,即跳向了0xfe05b處,這是bios代碼真正開始的地方。

第三個框框cs:f000,其意義是cs寄存器的值是f000,與我們剛剛所說的加電時強制将cs置為f000是吻合的,正确。

接下來bios便馬不停蹄地檢測記憶體、顯示卡等外設資訊,當檢測通過,并初始化好硬體後,開始在記憶體中0x000~0x3ff處建立資料結構,中斷向量表ivt并填寫中斷例程。

好了,終于到了接力的時刻,這是這場接力賽的第一棒,它将交給誰呢?咱們下回再說。

2.2.3 為什麼是0x7c00

計算機執行到這份上,bios也即将完成自己的曆史使命了,完成之後,它又将睡去。想到這裡,心中不免一絲憂傷,甚至有些許挽留它的想法。可是,這就是它的命,它生來被設計成這樣,在它短暫的一生中已經為後人創造了足夠的精彩。何況,在下一次開機時,bios還會重複這段輪回,它并沒有消失。好了,讓傷感停止,讓夢想前行。

先說重點,bios最後一項工作校驗啟動盤中位于0盤0道1扇區的内容。

在此插播一段小告示:在計算機中是習慣以0作為起始索引的,因為人們已經習慣了偏移量的概念,無論是機器眼裡和程式員眼裡,用“相對”的概念,即偏移量來表示位置顯得很直覺,是以很多指令中的操作數都是用偏移量表示的。0盤0道1扇區本質上就相當于0盤0道0扇區。為什麼稱為1呢,因為硬碟扇區的表示法有兩種,我們描述0盤0道1扇區用的便是其中的一種:chs方法,即柱面cylinder 磁頭header 扇區sector(另外一種是lba方式,暫不關心),“0盤”說的是0磁頭,因為一張盤是有上下兩個盤面的,一個盤面上對應一個磁頭,是以用磁頭header來表示盤面。“0道”是指0柱面,柱面cylinder指的是所有盤面上、編号相同的磁道的集合,形象一點描述就是把很多環疊摞在一起的樣子,組合在一起之後是一個立體的管狀。“1扇區”才是我們要解釋的部分,将磁道等距劃分成一段段的小區間,由于磁道是圓形的,确切地說是圓環,這些被劃分出來的小區間便是扇形,是以稱為扇區。好了,背景交待完了,重點來了,在chs方式中扇區的編号是從1開始的,不是0,不是0,原諒我說了兩次,良苦用心你懂的,是以0盤0道1扇區其實就相當于0盤0道0扇區,它就是磁盤上最開始的那個扇區。而lba方式中,扇區編号是從0開始的。關于硬碟的知識我會在以後章節專門來講,這裡我若沒表達清楚,大家先不要着急,隻要知道mbr所在的位置是磁盤上最開始的那個扇區就行了。

繼續說,如果此扇區末尾的兩個位元組分别是魔數0x55和0xaa,bios便認為此扇區中确實存在可執行的程式(在此先劇透一下,此程式便是久聞大名的主引導記錄mbr),便加載到實體位址0x7c00,随後跳轉到此位址,繼續執行。

這裡有個小細節,bios跳轉到0x7c00是用jmp 0:0x7c00實作的,這是jmp指令的直接絕對遠轉移用法,段寄存器cs會被替換,這裡的段基址是0,即cs由之前的0xf000變成了0。

如果此扇區的最後2個不是0x55和0xaa,即使裡面有可執行代碼也無濟于事了,bios不認,它也許還認為此扇區是沒格幹淨呢。

不過,這就又抛出兩個問題。

(1)為什麼是0盤0道1扇區的内容?

(2)為什麼是實體位址0x7c00,而不是個好記或好看的其他位址?

先回答第1個,我想這個問題不用官方解釋了,因為官方确實沒什麼好說的,不過他們出于尊重客戶,還是會像我一樣說出類似下面的話。

我就個人觀點給大家一個理由,未經核實,僅是自己一面之詞,請大家提高警惕,小心謹慎^—^。

在計算機中處處充滿了協定、約定,是以,将0盤0道1扇區作為mbr的栖身之地,我完全可以了解為規定。我們反證一下,如果不存在這個“規定”,會發生什麼。當然,此扇區最初是給bios使用的,咱們設想一下bios的工作将變成怎樣。

主引導記mbr是段程式,無論位于軟碟、硬碟或者其他媒體,總該有個地方儲存它。ok,現在不告訴bios它存儲在哪個位置了。bios隻好将所有檢測到的儲存設備上的每一個存儲機關都翻一遍,挨個對比,如果發現該存儲機關最後的兩個位元組是0x55和0xaa,就認為它是mbr。這就好比查字典一樣,不用偏旁部首和拼音檢索的方法,隻能一頁一頁翻了。

幾經花開花落,找到mbr的那一刻,bios滿臉疲憊地說:“你是我找了好久好久的那個人”。mbr擡起經不起歲月等待的臉:“難得你還認得我,我等你等到花兒都謝了”。其實bios的心聲是:“看我手忙腳亂的樣子,你們這是要鬧哪樣啊。就那麼512位元組的内容,害我找遍全世界,我們是在跑接力賽啊,下一棒的選手我都不知道在哪裡……以後讓它站在固定的位置等我!”

由于0盤0道1扇區是磁盤的第一個扇區,mbr選擇了離bios最近的位置站好了,從此以後再也不擔心被bios罵了。

計算機中處處有固定寫死的東西,還用舉個例子嗎?不用了吧?因為任何一個魔數都是啊,有請下一個魔數0x7c00登場。

至于0x7c00,很久之前,比我好奇心大的人查遍了intel開發手冊都沒找到相關的說明。要想知道事情的來龍去脈,還是要從個人計算機的初始說起,同樣是很久很久以前……

1981年8月,ibm公司生産了世界上第一台個人計算機pc 5150,是以它就是現代x86個人計算機相容機的祖先。說到有關曆史的東西,不給來點真相就感覺氣場不足,圖2-3所示便是ibm pc 5150,有沒有感受到計算機文化底蘊呢?

《作業系統真象還原》——2.2 軟體接力第一棒,BIOS

既然intel開發手冊中沒有相關說明,那咱們就朝其他方向找答案,換句話說,既然不是cpu的硬性規定,那很可能就是代碼中寫死的。為了搞清楚0x7c00是哪裡來的,咱們先探索下“ibm pc 5150”的bios的秘密。請先深深呼吸一大口氣,“0x7c00”最早出現在ibm 公司出産的個人電腦pc5150的rom bios的 int19h中斷處理程式中,說了這麼多定語,感覺氣都喘不上來了。

通電開機之後,bios處理程式開始自檢,随後,調用bios中斷0x19h,即 call int 19h。在此中斷處理函數中,bios要檢測這台計算機有多少硬碟或軟碟,如果檢測到了任何可用的磁盤,bios就把它的第一個扇區加載到0x7c00。

現在應該搞清楚了為什麼在x86手冊裡找不到它的說明了,它是屬于bios中的規範。似乎這下好辦了,既然是bios中的規範,那肯定是ibm pc 5150 bios 開發團隊規定的這個數。

個人計算機肯定要運作作業系統,在這台計算機上,運作的作業系統是dos 1.0,不清楚此系統要求的最小記憶體是16kb,還是32kb,反正pc 5150 bios研發工程師就假定其是32kb的,是以此版本bios是按最小記憶體32kb研發的。

mbr不是随便放在哪裡都行的,首先不能覆寫已有的資料,其次,不能過早地被其他資料覆寫。不覆寫已有資料,這個好了解。說一下後面這個“其次”。通常,mbr的任務是加載某個程式(這個程式一般是核心加載器,很少有直接加載核心的)到指定位置,并将控制權交給它。所謂的交控制權就是jmp過去而已。之後mbr就沒用了,被覆寫也沒關系。我說的過早被覆寫,是指不能讓mbr破壞自己,比如被加載的程式,如核心加載器,其放置的記憶體位置若是mbr自己所在的範圍,這不就是破壞自己了嗎,這就是我所說的“過早”了,怎麼也得等mbr執行完才行。

重制一下當時的記憶體使用情況。

8086cpu要求實體位址0x0~0x3ff存放中斷向量表,是以此處不能動了,再選新的地方看看。

按dos 1.0要求的最小記憶體32kb來說,mbr希望給人家盡可能多的預留白間,這樣也是保全自己的作法,免得過早被覆寫。是以mbr隻能放在32kb的末尾。

mbr本身也是程式,是程式就要用到棧,棧也是在記憶體中的,mbr雖然本身隻有512位元組,但還要為其所用的棧配置設定點空間,是以其實際所用的記憶體空間要大于512位元組,估計1kb記憶體夠用了。

結合以上三點,選擇32kb中的最後1kb最為合适,那此位址是多少呢?32kb換算為十六進制為0x8000,減去1kb(0x400)的話,等于0x7c00。這就是倍受質疑的0x7c00的由來,這下清楚了。

可見,加載mbr的位置取決于作業系統本身所占記憶體大小和記憶體布局。

我想大家現在都心癢癢了吧,說了這麼久,cpu中運作的都是bios的代碼,連自己一句代碼都沒跑起來呢。事不宜遲,馬上寫一個mbr,先讓它跑起來再說。

繼續閱讀