第3天 進入32位模式并導入C語言
1 制作真正的IPL
到昨天為止我們講到的啟動區,雖然也稱為IPL(Initial Program Loader,啟動程序裝載器),但它實質上并沒有裝載任何程序。而從今天起,我們要真的用它來裝載程序了。
把我們的操作系統叫作hello-os很不給勁,干脆改個名字吧。我們就叫它“紙娃娃操作系統”。所謂紙娃娃,意思就是說那是用紙糊起來的,虛有其表,不是真娃娃,就像拍電影時用的巖石等道具,其實都是中間空空的冒牌貨。也就是說我們現在要開發的操作系統,只是看上去像操作系統,而其實是個沒有內容的紙娃娃,所以大家不用想得太困難,輕輕松松來做就好了。
雖然今后我們要一直稱它為“紙娃娃操作系統”,而且在相當長的一段時間里也只是把它當作一種演示程序,但到最后我們肯定能開發出一個像模像樣的操作系統,這一點請大家放心。
稍微扯遠點,其實仔細想一想,這種虛有其表的“紙娃娃”又何止是操作系統呢。就說CPU吧,其實它根本就不懂什么“數”的概念,只是我們設計了一個電路,只要同時傳給它電信號0011和0110,它就能輸出結果為1001的電信號,而這種電路我們就稱之為加法電路。只有人才會把這個結果解讀為3+6=9, CPU只是處理這些電信號。換句話說,雖然CPU根本就不懂什么數字,但卻能給出正確的計算結果,這就是筆者所謂的“紙娃娃”。
開發過游戲程序的人就會明白,比如我們和計算機下象棋的時候,可能會覺得計算機水平很高,但實際上計算機對象棋規則一竅不通,僅僅是在執行一個程序而已。就算計算機走出了一著妙棋,也根本不是因為它下手毫不留情啊,或者聰明啊,或者求勝心切什么的,這些都是表象,其實它只是按部就班地執行程序而已。也就是說它本身沒有內涵,只有一個唬人的外殼,所以才叫“紙娃娃”。“紙娃娃”太厲害了!……操作系統就算是虛有其表、虛張聲勢又怎樣?沒什么不好的,這樣就可以了!
■■■■■
那么我們先從簡單的程序開始吧。因為磁盤最初的512字節是啟動區,所以要裝載下一個512字節的內容。我們來修改一下程序。改好的程序就是projects/03_day下的harib00a,像以前一樣,我們把它復制到tolset里來。
這次添加的內容大致如下。
本次添加的部分
MOV AX,0x0820 MOV ES, AX MOV CH,0 ; 柱面0 MOV DH,0 ; 磁頭0 MOV CL,2 ; 扇區2 MOV AH,0x02 ; AH=0x02 : 讀盤 MOV AL,1 ; 1個扇區 MOV BX,0 MOV DL,0x00 ; A驅動器 INT 0x13 ; 調用磁盤BIOS JC error
新出現的指令只有JC。真好,講起來也輕松了。所謂JC,是“jump if carry”的縮寫,意思是如果進位標志(carry flag)是1的話,就跳轉。這里突然冒出來“進位標志”這么個新詞,不過大家不用擔心,很快就會明白的。
■■■■■
至于“INT 0x13”這個指令,我們雖然知道這是要調用BIOS的0x13號函數,但還不明白它到底是干什么用的,那就來查一下吧。
我們可以找到如下的內容:
? 磁盤讀、寫,扇區校驗(verify),以及尋道(seek)
AH=0x02;(讀盤)
AH=0x03;(寫盤)
AH=0x04;(校驗)
AH=0x0c;(尋道)
AL=處理對象的扇區數;(只能同時處理連續的扇區)
CH=柱面號 &0xff;
CL=扇區號(0-5位)|(柱面號&0x300)>>2;
DH=磁頭號;
DL=驅動器號;
ES:BX=緩沖地址;(校驗及尋道時不使用)
返回值:
FLACS.CF==0:沒有錯誤,AH==0
FLAGS.CF==1:有錯誤,錯誤號碼存入AH內(與重置(reset)功能一樣)
我們這次用的是AH=0x02,哦,原來是“讀盤”的意思。
■■■■■
返回值那一欄里的FLAGS.CF又是什么意思呢?這就是我們剛才講到的進位標志。也就是說,調用這個函數之后,如果沒有錯,進位標志就是0;如果有錯,進位標志就是1。這樣我們就能明白剛才為什么要用JC指令了。
進位標志是一個只能存儲1位信息的寄存器,除此之外,CPU還有其他幾個只有1位的寄存器。像這種1位寄存器我們稱之為標志。標志在英文中為flag,是旗幟的意思。標志之所以叫flag是因為它的開和關就像升旗降旗的狀態一樣。
所謂進位標志,本來是用來表示有沒有進位(carry)的,但在CPU的標志中,它是最簡單易用的,所以在其他地方也經常用到。這次就是用來報告BIOS函數調用是否有錯的。
其他幾個寄存器我們也來依次看一下吧。CH、CL、DH、DL分別是柱面號、扇區號、磁頭號、驅動器號,一定不要搞錯。在上面的程序中,柱面號是0,磁頭號是0,扇區號是2,磁盤號是0。
■■■■■
在有多個軟盤驅動器的時候,用磁盤驅動器號來指定從哪個驅動器的軟盤上讀取數據。現在的電腦,基本都只有1個軟盤驅動器,而以前一般都是2個。既然現在只有一個,那不用多想,指定0號就行了。
知道了從哪個軟盤驅動器讀取數據之后,我們接著看從那個軟盤的什么地方來讀取數據。

如果手頭有不用的軟盤,希望大家能把它拆開看看。拆開后可以看到,中間有一個8厘米的黑色圓盤,那是一層薄薄的磁性膠片。從外向內,一圈一圈圓環狀的區域,分別稱為柱面0,柱面1, ……,柱面79。一共有80個柱面。這并不是說工廠就是這樣一圈一圈地生產軟盤的,只是我們將它作為一個數據存儲媒體,是這樣組織它的數據存儲方式的。柱面在英文中是cylinder,原意是圓筒。磁盤的柱面,盡管高度非常低,但我們可以把它看成是一個套一個的同心圓筒,它正是因此得名的。
下面講一下磁頭。磁頭是個針狀的磁性設備,既可以從軟盤正面接觸磁盤,也可以從軟盤背面接觸磁盤。與光盤不同,軟盤磁盤是兩面都能記錄數據的。因此我們有正面和反面兩個磁頭,分別是磁頭0號和磁頭1號。
最后我們看一下扇區。指定了柱面和磁頭后,在磁盤的這個圓環上,還能記錄很多位信息,按照整個圓環為單位讀寫的話,實在有點多,所以我們又把這個圓環均等地分成了幾份。軟盤分為18份,每一份稱為一個扇區。一個圓環有18個扇區,分別稱為扇區1、扇區2、……扇區18。扇區在英文中是sector,意思是指領域、扇形。
綜上所述,1張軟盤有80個柱面,2個磁頭,18個扇區,且一個扇區有512字節。所以,一張軟盤的容量是:
80×2×18×512=1474 560 Byte=1440KB
含有IPL的啟動區,位于C0-H0-S1(柱面0,磁頭0,扇區1的縮寫),下一個扇區是C0-H0-S2。這次我們想要裝載的就是這個扇區。
■■■■■
剩下的大家還不明白的就是緩沖區地址了吧。這是個內存地址,表明我們要把從軟盤上讀出的數據裝載到內存的哪個位置。一般說來,如果能用一個寄存器來表示內存地址的話,當然會很方便,但一個BX只能表示0~0xffff的值,也就是只有0~65535,最大才64K。大家的電腦起碼也都有64M內存,或者更多,只用一個寄存器來表示內存地址的話,就只能用64K的內存,這太可惜了。
于是為了解決這個問題,就增加了一個叫EBX的寄存器,這樣就能處理4G內存了。這是CPU能處理的最大內存量,沒有任何問題。但EBX的導入是很久以后的事情,在設計BIOS的時代,CPU甚至還沒有32位寄存器,所以當時只好設計了一個起輔助作用的段寄存器(segment register)。在指定內存地址的時候,可以使用這個段寄存器。
我們使用段寄存器時,以ES:BX這種方式來表示地址,寫成“MOV AL, [ES:BX]”,它代表ES×16+BX的內存地址。我們可以把它理解成先用ES寄存器指定一個大致的地址,然后再用BX來指定其中一個具體地址。
這樣如果在ES里代入0xffff,在BX里也代入0xffff,就是1114 095字節,也就是說可以指定1M以內的內存地址了。雖然這也還是遠遠不到64M,但當時英特爾公司的大叔們,好像覺得這就足夠了。在最初設計BIOS的時代,這種配置已經很能滿足當時的需要了,所以我們現在也還是要遵從這一規則。因此,大家就先忍耐一下這1MB內存的限制吧。
這次,我們指定了ES=0x0820, BX=0,所以軟盤的數據將被裝載到內存中0x8200到0x83ff的地方。可能有人會想,怎么也不弄個整點的數,比如0x8000什么的,那多好。但0x8000~0x81ff這512字節是留給啟動區的,要將啟動區的內容讀到那里,所以就這樣吧。
那為什么使用0x8000以后的內存呢?這倒也沒什么特別的理由,只是因為從內存分布圖上看,這一塊領域沒人使用,于是筆者就決定將我們的“紙娃娃操作系統”裝載到這一區域。0x7c00~0x7dff用于啟動區,0x7e00以后直到0x9fbff為止的區域都沒有特別的用途,操作系統可以隨便使用。
■■■■■
到目前為止我們開發的程序完全沒有考慮段寄存器,但事實上,不管我們要指定內存的什么地址,都必須同時指定段寄存器,這是規定。一般如果省略的話就會把“DS:”作為默認的段寄存器。
以前我們用的“MOV CX, [1234]”,其實是“MOV CX, [DS:1234]”的意思。“MOV AL, [SI]”,也就是“MOV AL, [DS:SI]”的意思。在匯編語言中,如果每回都這樣寫就太麻煩了,所以可以省略默認的段寄存器DS。
因為有這樣的規則,所以DS必須預先指定為0,否則地址的值就要加上這個數的16倍,就會讀寫到其他的地方,引起混亂。

好,我們來執行這個程序看看吧。如果程序有什么錯,它就會顯示錯誤信息。但估計不會出什么問題吧。沒錯的話,它就什么都不做(笑)。所以,如果屏幕上不顯示任何錯誤信息的話,我們就成功了。
哎呀,有件事差點忘了,Makefile中可以使用簡單的變量,于是筆者用變量改寫了這次的Makefile。怎么樣,是不是比之前稍微容易理解一些?
- 30天自制操作系統
- 每天5分鐘玩轉Kubernetes
- 網絡操作系統:Windows Server 2003管理與應用
- WindowsServer2012Hyper-V虛擬化部署與管理指南
- SharePoint 2013 WCM Advanced Cookbook
- 嵌入式Linux驅動程序和系統開發實例精講
- SharePoint 2013 應用開發實戰
- 混沌工程實戰:手把手教你實現系統穩定性
- Learning Bootstrap
- NetDevOps入門與實踐
- Vim 8文本處理實戰
- Python UNIX和Linux系統管理指南
- Learn CUDA Programming
- Linux網絡配置與安全管理
- iOS 10快速開發:18天零基礎開發一個商業應用