004-寫一個真正的啓動盤

       上次說到了使用軟盤啓動計算機的注意事項,那麼,我們就來實際的操作一下,寫一個真正可以用於啓動計算機的啓動盤。

       首先,我們打開BZ,創建一個文件,然後按照以下方式來書寫:

後面就全是0了,因爲還沒寫東西,注意要一直寫滿1440KB


       當然,這樣的話還不能真正啓動計算機,因爲指令不對,我們說,當計算機檢查了磁盤格式,以及510和511字節的內容後,會執行1到3字節的指令,所以我們的程序就從這裏開始。不過應該能看出來,這直接往下寫是不可能的,因爲從第4字節開始就是數據了。圖靈計算機有一大特點,就是指令和數據共同存儲,按照不同的解釋方式來決定執行什麼樣的動作,那這裏就顯示得淋漓盡致了。我們爲了能讓它運行更多的指令,所以只能在這一條寶貴的地方加一個跳轉語句,然後把我們的代碼寫到後面比較寬敞的位置。

       明白了這個道理以後,我們前面使用機器語言編程的部分就差不多了,如果繼續寫下去那真的有點非人類了,不過這也能體會到以前的程序員有多辛苦。

       那麼,要想讓我們更好的來書寫代碼,有能夠很方便的在計算機上運行,我們引入了編譯器。這裏我們使用的是nask編譯器(這也是川合先生寫的,nasm爲該編譯器的原型)。機器語言不方便人類閱讀和記憶,於是,針對於這些機器碼要做的事情,編寫了助記符,再通過添加一些輔助符號,使得程序更好理解,這樣就誕生了彙編語言,彙編語言絕大多數都是機器語言的直接助記符,所以非常容易被編譯成機器代碼,當然,也要配合一些其他的輔助命令來更方便我們使用,然後再由編譯器把它翻譯成機器指令。

       剛開始閱讀彙編代碼的時候非常不習慣,最主要的原因就在於,數據和指令的混合書寫,所以這裏必須明白,數據和指令原本沒有區別,只是我們解釋方法不同而已,如果要看指令,我們要摸清計算機執行指令的順序,而這個時候就不要被數據所幹擾。再次強調,指令和數據是混合書寫的,所以很多時候感覺比較亂,我們整理出以下彙編代碼:

     1                                          	org 0x7c00			; 指定程序的裝載位置
     2 00007C00 EB 4B                           	jmp entry			; 轉到entry標籤下(7c4d)
     3 00007C02 90                              	db 0x90
     4 00007C03 48 45 4C 4C 4F 49 50 4C         	db "HELLOIPL"
     5 00007C0B 0200                            	dw 512
     6 00007C0D 01                              	db 1
     7 00007C0E 0001                            	dw 1
     8 00007C10 02                              	db 2
     9 00007C11 00E0                            	dw 224
    10 00007C13 0B40                            	dw 2880
    11 00007C15 F0                              	db 0xf0
    12 00007C16 0009                            	dw 9
    13 00007C18 0012                            	dw 18
    14 00007C1A 0002                            	dw 2
    15 00007C1C 00000000                        	dd 0
    16 00007C20 00000B40                        	dd 2880
    17 00007C24 00 00 29                        	db 0, 0, 0x29
    18 00007C27 FF                              	db 0xffffffff
    19 00007C28 48 45 4C 4C 4F 2D 4F 53 20 20   	db "HELLO-OS   "
       00007C32 20 
    20 00007C33 46 41 54 31 32 20 20 20         	db "FAT12   "
    21 00007C3B 00 00 00 00 00 00 00 00 00 00   	resb 18
       00007C45 00 00 00 00 00 00 00 00 
    22 00007C4D                                 	
    23 00007C4D                                 entry:
    24 00007C4D B8 0000                         	mov ax, 0
    25 00007C50 8E D8                           	mov ds, ax
    26 00007C52 BE 7C6A                         	mov si, msg
    27 00007C55                                 putloop:
    28 00007C55 8A 04                           	mov al, [si]
    29 00007C57 83 C6 01                        	add si, 1
    30 00007C5A 3C 00                           	cmp al, 0
    31 00007C5C 74 09                           	je  fin
    32 00007C5E B4 0E                           	mov ah, 0x0e
    33 00007C60 BB 000F                         	mov bx, 15
    34 00007C63 CD 10                           	int 0x10
    35 00007C65 EB EE                           	jmp putloop
    36 00007C67                                 	
    37 00007C67                                 fin:
    38 00007C67 F4                              	hlt
    39 00007C68 EB FD                           	jmp fin
    40 00007C6A                                 	
    41 00007C6A                                 msg:
    42 00007C6A 0D 0A                           	db 0x0d, 0x0a
    43 00007C6C 48 65 6C 6C 6F 20 57 6F 72 6C   	db "Hello World!"
       00007C76 64 21 
    44 00007C78 0D 0A 00                        	db 0x0d, 0x0a, 0x00
    45 00007C7B                                 	
    46 00007C7B 00 00 00 00 00 00 00 00 00 00   	resb 0x7dfe-$
       00007C85 00 00 00 00 00 00 00 00 00 00 
       00007C8F 00 00 00 00 00 00 00 00 00 00 
       00007C99 00 00 00 00 00 00 00 00 00 00 
       00007CA3 00 00 00 00 00 00 00 00 00 00 
       00007CAD 00 00 00 00 00 00 00 00 00 00 
       00007CB7 00 00 00 00 00 00 00 00 00 00 
       00007CC1 00 00 00 00 00 00 00 00 00 00 
       00007CCB 00 00 00 00 00 00 00 00 00 00 
       00007CD5 00 00 00 00 00 00 00 00 00 00 
       00007CDF 00 00 00 00 00 00 00 00 00 00 
       00007CE9 00 00 00 00 00 00 00 00 00 00 
       00007CF3 00 00 00 00 00 00 00 00 00 00 
       00007CFD 00 00 00 00 00 00 00 00 00 00 
       00007D07 00 00 00 00 00 00 00 00 00 00 
       00007D11 00 00 00 00 00 00 00 00 00 00 
       00007D1B 00 00 00 00 00 00 00 00 00 00 
       00007D25 00 00 00 00 00 00 00 00 00 00 
       00007D2F 00 00 00 00 00 00 00 00 00 00 
       00007D39 00 00 00 00 00 00 00 00 00 00 
       00007D43 00 00 00 00 00 00 00 00 00 00 
       00007D4D 00 00 00 00 00 00 00 00 00 00 
       00007D57 00 00 00 00 00 00 00 00 00 00 
       00007D61 00 00 00 00 00 00 00 00 00 00 
       00007D6B 00 00 00 00 00 00 00 00 00 00 
       00007D75 00 00 00 00 00 00 00 00 00 00 
       00007D7F 00 00 00 00 00 00 00 00 00 00 
       00007D89 00 00 00 00 00 00 00 00 00 00 
       00007D93 00 00 00 00 00 00 00 00 00 00 
       00007D9D 00 00 00 00 00 00 00 00 00 00 
       00007DA7 00 00 00 00 00 00 00 00 00 00 
       00007DB1 00 00 00 00 00 00 00 00 00 00 
       00007DBB 00 00 00 00 00 00 00 00 00 00 
       00007DC5 00 00 00 00 00 00 00 00 00 00 
       00007DCF 00 00 00 00 00 00 00 00 00 00 
       00007DD9 00 00 00 00 00 00 00 00 00 00 
       00007DE3 00 00 00 00 00 00 00 00 00 00 
       00007DED 00 00 00 00 00 00 00 00 00 00 
       00007DF7 00 00 00 00 00 00 00 
    47 00007DFE 55 AA                           	db 0x55, 0xaa
    48 00007E00  

上面給出的lst文件,左半部分是把彙編語言編譯成的機器代碼,也就是是說,我們實際上寫的應該是左邊的部分,把左邊這些機器碼直接寫進img裏也是沒問題的。第1行表示該程序在內存中的加載位置,至於爲什麼選擇7c00,這是由於內存分佈規定的,由於計算機硬件的原因(包括BIOS的加載),規定啓動區就只能放到0x7c00-0x7dff這512字節的位置,這也就是爲什麼我們的啓動區部分只能寫512字節。第2行是一個跳轉指令,因爲緊接着的都是一些數據,這些數據是不能當做指令來執行的,所以這裏做一次跳轉,跳轉到0x7c50處,由於我們已經給這個地址跳轉位置起了名字,所以寫entry就可以了。第3行的這個數據其實是個廢棄的,因爲jmp指令只佔2個字節,留這一個字節是因爲,如果你寫的是一個3字節的指令,那就必須用到這一個字節了,所以現在的情況,寫什麼數據都隨便。24行和25行其實是爲了實現給ds寄存器賦值爲0,由於CPU電路設計的問題,不存在直接給ds賦值的機器指令,自然也就不存在類似於mov ds, 0這樣的彙編語句(所以,彙編語言雖然更加符合人類的語言習慣,但是,你還是必須用計算機的思路來思考問題,否則的話,很多問題是根本無解的),我們必須通過ax間接給ds賦值,這個ax是一個通用寄存器,雖然說剛誕生的時候,它叫做加法寄存器,與其他的三個通用寄存器(bx基址寄存器,cx計數寄存器,dx數據寄存器)都有着其獨特的用途,但是之後由於硬件的改進以及彙編語言的發展,現在很少再去對這4個寄存器的具體通途進行區分,尤其是在保護模式下更是如此(我們現在仍然是實模式),所以在絕大多數情況下就認爲它是一個通用寄存器就好了。ds是數據段寄存器,由於Intel 8086中,CPU擁有16位尋址線(雖然我們現在使用的386以上的32位CPU,但是在實模式下仍然只能使用16位),尋址範圍太小,於是添加了段的概念,使用20位尋址,可以支持1M的內存空間,而物理地址的計算方法就是段地址<<4+偏移地址,也就是用段的這16位來填充物理地址的高16位,在和偏移地址相加(個人認爲,在考慮二進制問題的時候,用非常簡單的左移右移去考慮問題是最好的方法,所以這裏就是左移4位,不要考慮什麼乘16的問題,不然容易被搞懵)。我們現在無論是數據還是指令,都是前0x0200(512)個,所以段地址只能爲0。由於半導體材料自身特點,在剛通電的時候,各觸發器的初始電位是雜亂的,所以初始情況下,寄存器裏的數值自然也就是亂的,所以,我們需要人爲給ds賦值爲0,否則後面的數據無法正常讀取。26行,把0x7c6a賦值給si寄存器,si寄存器是源變址寄存器,這是變址尋址方式需要使用的,變址尋址方式就是,我們給出一個基址,這個基址可能是某一段數據的開頭,或者是某一段指令的第一條的地址,然後以這個爲基礎依次向下讀取(或執行)。由於我們在41-44行存放了要輸出的數據,而這些數據的地址起始就是0x7c6a,所以,要把這個值(也就是msg表示的地址)賦值給si以方便接下來使用。27-35行實現的是文字的輸出。首先來看一下34行,int是調用中斷指令,這裏調用的是BIOS中斷,所謂的中斷,簡單解釋一下。CPU、內存等這些速度都非常快,但是,在他們執行過程中不可避免的會等待類似於鍵盤鼠標的輸入以及顯示器打印機等的輸出,這些設備的速度相比而言就非常慢了(CPU每秒怎麼也能進行上億次運算,鍵盤打字的吉尼斯世界紀錄也不過每分鐘803次,平均每秒13次,到底不是一個數量級的),所以,如果CPU一直在等待這些事情,那麼它等於說有很長時間都不能工作,因而有了中斷機制,CPU不會因爲需要輸入或者輸出就直接停下來等待,而是當設備需要進行輸入或輸出時,給CPU發出一個信號,CPU會停下手中的工作轉來處理,處理完之後繼續進行自己的工作,換句話說,CPU不需要實時監控,而是在需要他的時候“中斷”一下,所以就把這種機制叫做中斷機制。這裏的BIOS中斷就是這樣,CPU需要讓BIOS調用顯卡,然後把對應的內容打印到屏幕上,這一過程對於CPU來說就是對它的中斷。而要進行中斷,自然也就需要相應的數據傳遞,CPU先把要處理的數據放到相對應的寄存器上,然後其他的設備(這裏自然就是BIOS了)再讀取寄存器中的數據,進行對應的工作,然後再返還一定的數據到寄存器,CPU再通過寄存機來回去返回數據(其實這麼說起來,蠻像函數調用的,其實多數時候就可以理解成函數調用)。這裏就是調用了0x10號中斷,這個中斷需要BIOS響應,當ah(就是ax的高8位)是0xe的時候,BIOS會進行字符打印處理,它會調用顯卡,把al(就是ax的低8位)所表示的字符打印在屏幕上,與此同時,bx中的內容決定了文字的顏色(但是這個顏色在多數BIOS上是不支持的,打印出來的都會是白色)。28行就把si的值進行尋址,把對應內存地址處的數據賦值給al,32-33行把其他的寄存器的值賦好,然後34行調用0x10號中斷,就可以在屏幕上打印出一個字符了。但是,我們要的不是隻打印一個字符,是要打印一系列的字符(字符串),所以,就需要一個循環,而有循環就需要一個循環的結束條件(除非你確實想做一個死循環),35行的跳轉指令製造一個循環,而30和31行就是判斷是否跳出循環。這裏是將字符和0去比較(當然了,這玩意你自己定,你非要用別的字符當字符串結尾符自然也沒什麼問題,只是大家都很習慣0結尾的字符串,你看那些用匈牙利命名法明明的字符串不都是sz前綴嘛,s表示字符串,z表示0結尾,貌似也沒見過其他非主流的字符串),這樣的話,就會把msg標籤下的內容作爲數據,一個字符一個字符打印到屏幕上,直到找到'\0'字符爲止。

       後面的fin標籤下就製造了一個死循環,而hlt命令是讓CPU待機,這樣減少能耗,是一個好的變成習慣,由於之後我們不做什麼事情了,所以就讓CPU一直待機就可以了。

       當然了,這僅僅有前512字節,要製作一個img,還需要把後面全部補0直至1440KB,用BZ也行,在彙編代碼後面添加resb命令也可以。把製作好的img放到VMware上跑一下,順利地看到了這一行字。


       我們已經成功的把一臺什麼都沒有的裸機運行起來了,還輸出了一行字。由於一開始只能加載512字節,所以這512字節裏的程序一般都用來加載磁盤,然後轉到別的程序,基本上起了一個加載和引導的作用,我們把這一段內容就叫做ipl。下次將繼續完善ipl程序並實現更多的功能。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章