意思是跳轉到物理地址 0xfe05b 處開始執行(回憶下前面說的實模式下的地址計算方式)。地址 0xfe05b 處開始,便是 BIOS 真正發揮作用的代碼了,這塊代碼會檢測一些外設信息,並初始化好硬體,建立中斷向量表並填寫中斷例程。這裡的部分不要展開,這只是一段寫死的程序而已,而且對理解開機啟動過程無幫助,我們看後面精彩的部分,也就是 BIOS 的最後一項工作:加載啟動區。六、0x7c00 是啥該較真的地方就是要較真,我絕對不會讓加載這種魔幻的詞出現在這裡,我們現在就來把它拆解成人話。其實這個詞也並不魔幻,加載在計算機領域就是指,把某設備上(比如硬碟)的程序複製到內存中的過程。那加載啟動區這個過程,翻譯過來就是,BIOS 程序把啟動區的內容複製到了內存中的某個區域。好了,問題又自然出來了,啟動區是哪裡?被複製到了內存的哪個位置?然後呢?我們一個個來回答。什麼是啟動區呢?即使你不知道,你也應該能夠猜到,一定是符合某種特徵的一塊區域,於是人們把它就叫做啟動區了,那要符合什麼特徵呢?先不急,不知道你有沒有過設置 BIOS 啟動順序的經歷,通常有 U 盤啟動、硬碟啟動、軟盤啟動、光碟啟動等等,BIOS 會按照順序,讀取這些啟動盤中位於 0 盤 0 道 1 扇區的內容。至於磁碟格式的劃分,本篇就不做講解了,總之對於內存,我們給出一個數字地址就能獲取到該地址的數據,而對於磁碟,我們需要給出磁頭、柱面、扇區這三個信息才能定位某個位置的數據,都是描述位置的一種方式而已。接著說, 這 0 盤 0 道 1 扇區的內容一共有 512 個字節,如果末尾的兩個字節分別是 0x55 和 0xaa,那麼 BIOS 就會認為它是個啟動區。如果不是,那麼按順序繼續向下個設備中尋找位於 0 盤 0 道 1 扇區的內容。如果最後發現都沒找到符合條件的,那直接報出一個無啟動區的錯誤。BIOS 找到了這個啟動區之後幹嘛呢?哦,前面說過了是加載,就是把這 512 個字節的內容,一個比特都不少的全部複製到內存的 0x7c00 這個位置。怎麼複製的?當然是指令啦。哪些指令呢?這裡我只能簡單說指令集中是有 in 和 out 的,用來將外設中的數據複製到內存,或者將內存中的數據複製到外設,用這兩個指令,以及外設給我們提供的讀取方式,就能做到這一點啦。啟動區內容此時已經被 BIOS 程序複製到了內存的 0x7c00 這個位置,然後呢?這個其實也不難猜測,啟動區的內容就是我們自己寫的代碼了,複製到這裡之後,就開始執行唄,之後我們的程序就接管了接下來的流程,BIOS 的使命也就結束啦。所以複製完之後,接下來應該是一個跳轉指令吧!沒錯,正是這樣,PC 寄存器的值變為 0x7c00,指令開始從這裡執行。咦?不知道你有沒有發現,我們似乎不知不覺又把之前的一句魔法語言翻譯成人話了,開頭我們說:所以這句話是什麼意思呢?就是 BIOS 把啟動區的 512 字節複製到內存的 0x7c00 位置,並且用一條跳轉指令將 pc 寄存器的值指向 0x7c00。你看,這不是也沒多幾個字嘛,就把這個問題說得明明白白,簡簡單單。哦,對了,現在似乎就剩下一個問題了,為什麼非要是 0x7c00 呢?好問題,當然答案也很簡單,那就是人家 BIOS 開發團隊就是這樣定的,之後也不好改了,不然不兼容。為什麼不好改?我們看一個簡單的啟動區 512 字節的代碼。(代碼摘抄自《30 天自製作業系統》); hello-os
; TAB=4
ORG 0x7c00 ;程序加載到內存的 0x7c00 這個位置
;程序主體
entry:
MOV AX,0 ;初始化寄存器
MOV SS,AX
MOV SP,0x7c00
MOV DS,AX ;段寄存器初始化為 0
MOV ES,AX
MOV SI,msg
putloop:
MOV AL,[SI]
ADD SI,1
CMP AL,0 ;如果遇到 0 結尾的,就跳出循環不再列印新字符
JE fin
MOV AH,0x0e ;指定文字
MOV BX,15 ;指定顏色
INT 0x10 ;調用 BIOS 顯示字符函數
JMP putloop
fin:
HLT
JMP fin
msg:
DB 0x0a,0x0a ;換行、換行
DB "hello-os"
DB 0x0a ;換行
DB 0 ;0 結尾
RESB 0x7dfe-$ ;填充0到512位元組
DB 0x55, 0xaa ;可啟動設備標識這個數字就是剛剛說的啟動區加載位置,這行彙編代碼簡單說就表示把下面的地址統統加上 0x7c00。正因為 BIOS 將啟動區的代碼加載到了這裡,因此有了一個偏移量,所以所有寫啟動區代碼的人就需要在開頭寫死一個這樣的代碼,不然全都串位了。然後正因為所有寫作業系統的,啟動區的第一行彙編代碼都寫死了這個數字,那 BIOS 開發者最初定的這個數字就不好改了,否則它得挨個聯繫各個作業系統的開發廠商,說唉我這個地址改一下哈,你們跟著改改。在公司推動另一個團隊改個代碼都得大費周折,想想看這樣的推動得耗費多大人力。況且即使改了,之前的代碼也都不兼容了,這不得被人們罵死啊。這也驗證了我們之前說的這 512 字節的最後兩個字節得是 0x55 0xaa,BIOS 才會認為它是一個啟動區,才會去加載它,僅此而已。回過頭來說 0x7c00 這個值,它其實就是一個規定死的值,但還是會有人問,那必然有它的合理性吧。其實,我的解釋也只能說是人家規定了這個值,後人們替他們解釋這個合理性,並不是說當初人家就一定是這樣想的,就好比我們做語文的閱讀理解題一樣。第一個 BIOS 開發團隊是 IBM PC 5150 BIOS,當時被認為的第一個作業系統是 DOS 1.0 作業系統,BIOS 團隊就假設是為它服務的。但作業系統還沒出,BIOS 團隊假設其作業系統需要的最小內存為 32 KB。BIOS 希望自己所加載的啟動區代碼儘量靠後,這樣比較「安全」,不至於過早的被其他程序覆蓋掉。可是如果僅僅留 512 字節又感覺太懸了,還有一些棧空間需要預留,那擴大到 1 KB 吧。這樣 32 KB 的末尾是 0x8000,減去 1KB(0x400) ,剛好等於 0x7c00。哇塞,太精準了,這可以是一種解釋方式。七、啟動區裡的代碼寫了啥其實寫到這,我這篇文章就應該戛然而止了,因為最初的那個問題已經解決了,CPU 已經開始馬不停蹄地從我們預期的位置跑起來了,萬事開頭難,剩下的內容,就是作業系統想怎麼玩就怎麼玩了。但我覺得還不夠味,似乎還有些問題縈繞在你腦海裡。比如說這個問題:啟動區裡的代碼寫了啥?就 512 字節就是全部作業系統內容了?這是一個好問題,512 個字節確實幹不了啥,現在的作業系統怎麼也得按 M 為單位算吧,512 個字節遠遠不夠呢,那是怎麼回事呢?其實我們可以按照之前的思路猜測,BIOS 用很少的代碼就把 512 字節的啟動區內容加載到了內存,並跳轉過去開始執行。那按照這個套路,這 512 字節的啟動區代碼,是不是也可以把更多磁碟中存儲的作業系統程序,加載到內存的某個位置,然後跳轉過去呢?沒錯,就是這個套路。所以 BIOS 負責加載了啟動區,而啟動區又負責加載真正的作業系統內核,這配合默契吧?由於用於啟動盤的磁碟是人家寫作業系統的廠商製作的,俗稱製作啟動盤,所以他也肯定知道作業系統的核心代碼存儲在磁碟的哪個扇區,因此啟動區就把這個扇區,以及之後的好多好多扇區(具體取決於作業系統有多大)都讀到內存中,然後跳轉到開始的程序開始的位置。跳轉到哪裡呢?這個就不像 0x7c00 這個數那麼經典了,不同的作業系統肯定也不一樣,也不用事先規定好,反正寫作業系統的人給自己定一個就好了,別覆蓋其他關鍵設備用到的區域就好。八、作業系統內核寫了啥好了現在經過好幾輪跳跳跳,終於跳到內核代碼啦,我們來一起回顧一下:按下開機鍵,CPU 將 PC 寄存器的值強制初始化為 0xffff0,這個位置是 BIOS 程序的入口地址(一跳)該入口地址處是一個跳轉指令,跳轉到 0xfe05b 位置,開始執行(二跳)執行了一些硬體檢測工作後,最後一步將啟動區內容加載到內存 0x7c00,並跳轉到這裡(三跳)啟動區代碼主要是加載作業系統內核,並跳轉到加載處(四跳)經過這連續的四次跳躍,終於來到了作業系統的世界了,剩下的內容,可以說是整個作業系統課程所講述的原理,分段、分頁、建立中斷、設備驅動、內存管理、進程管理、文件系統、用戶態接口等等。這些名詞在作業系統的課程中你可能都或多或少聽過,如果你好好學了的話也一定知道大概的原理,不過像筆者這樣從頭到尾研讀過 linux 內核源碼的硬核狗來說,這些概念不只是書本上枯燥無味的概念,而是活靈活現在作業系統的每一行代碼上,有的展現了作者無比的智慧,有的讓我看到了作者由於硬體設定不得已做出的屈服。如果這篇文章提起了你對作業系統的好奇心,建議你也找時間讀一讀,和我一起入坑,你會發現一個新世界的大門向你打開了九、參考資料好了,這回我真的要結束了,相信如果你真的看完了全文,計算機的啟動過程,可以說有了比較具象的了解。如果你想深入細節,也就是了解整個過程的每一點,那可要下功夫了。