1、讀盤
昨天寫的IPL並沒有裝載程序,今天我們來寫一個可以真正裝載程序的IPL。
我們先來看軟盤結構。
如圖所示。一張軟件有正反兩面,對應讀取用的磁頭(0,1),而從外到內又分爲80個環(0~79),稱爲柱面。每個柱面又分爲18個扇區(1~18)。因爲軟盤的第一個扇區(正面的第0個柱面的第1個扇區)爲啓動區,所以我們讀軟盤的時候應該從第2個扇區開始讀。
org 0x7c00
jmp init
DB 0x90
DB "HARIBOTE"
DW 512
DB 1
DW 1
DB 2
DW 224
DW 2880
DB 0xf0
DW 9
DW 18
DW 2
DD 0
DD 2880
DB 0,0,0x29
DD 0xffffffff
DB "HARIBOTEOS "
DB "FAT12 "
RESB 18
init:
mov bx,0
mov ax,0x0820
mov es,ax ;es:bx爲緩衝地址,即讀取的數據將存取在這裏
mov dl,0 ;驅動器號0(即光驅號,現在一般只有一個光驅)
mov dh,0 ;磁頭號0~1
mov ch,0 ;柱面號0~79
mov cl,2 ;扇區號1~18
mov al,1 ;要讀的扇區數
mov si,0 ;讀盤錯誤次數
read:
mov ah,0x02 ;讀盤
int 0x13 ;中斷,BIOS的0x13號函數
jnc finish ;jnc指令(jump if not carry),如果上一步沒錯誤,則cf標誌位爲0,當cf = 0時,跳轉到fin
inc si ;錯誤次數加1
cmp si,5
jae error ;jae指令(jump if above or equal)
;重置驅動器
mov ah,0
mov dl,0
int 0x13 ;重置驅動器
jmp read
finish:
hlt
jmp finish
error:
mov ax,0
mov ds,ax
mov si,msg
mov di,msg
mov ah,0x0e
mov bx,15
show:
mov al,[di]
inc di
cmp al,0
je finish
int 0x10
jmp show
msg:
db 0x0a,0x0a
db "5 times,failed!!"
db 0x0a
db 0
resb 0x7dfe - $
db 0x55,0xaa
讀軟盤時所需要設置的寄存器如下:
ah=0x02(讀盤)
al=n(表示要讀的扇區的個數)
ch=柱面號
cl=扇區號
dh=磁頭號
dl=驅動器號(現在一般只有一個驅動器,故只要爲0就OK了~)
es:bx=緩衝地址(即將講到的數據存至內存的這個位置)
int 0x13的返回值:cf = 0 或cf = 1,有錯誤則cf標誌位爲1;無錯誤則爲0。
至於我們設定的將數據裝載到es=0x0820,bx=0,即內存0x8200處,是我們隨意設定的,因爲0x7c00~0x7dff爲啓動區,而在0x7e00後到0x9fbff都是沒有特別用途的,我們可以隨便使用。
因爲軟件這東西太不靠譜了,所以很容易出錯,我們設定最多讀5次,否則就提示錯誤。
好了,現在我們已經讀了一個扇區了,接下來,我們將程序改寫成能讀入10個柱面。
cyls equ 10 ;#define cyls 10
org 0x7c00
jmp init
DB 0x90
DB "HARIBOTE"
DW 512
DB 1
DW 1
DB 2
DW 224
DW 2880
DB 0xf0
DW 9
DW 18
DW 2
DD 0
DD 2880
DB 0,0,0x29
DD 0xffffffff
DB "HARIBOTEOS "
DB "FAT12 "
RESB 18
init:
mov ax,0
mov ss,ax
mov sp,0x7c00
mov ds,ax
mov bx,0
mov ax,0x0820
mov es,ax ;es:bx爲緩衝地址,即讀取的數據將存取在這裏
mov dl,0 ;驅動器號0(即光驅號,現在一般只有一個光驅)
mov dh,0 ;磁頭號0~1
mov ch,0 ;柱面號0~79
mov cl,2 ;扇區號1~18
resetsi:
mov si,0 ;讀盤錯誤次數
read:
mov al,1 ;要讀的扇區數
mov bx,0
mov dl,0
mov ah,0x02 ;讀盤
int 0x13 ;中斷,BIOS的0x13號函數
jnc setnext ;沒有出錯則設置下一個要讀的地方
add si,1 ;錯誤次數加1
cmp si,5
jae error ;jae指令(jump if above or equal)
;重置驅動器
mov ah,0
mov dl,0
int 0x13 ;重置驅動器
jmp read
setnext:
mov ax,es
add ax,0x0020
mov es,ax ;es加512字節
add cl,1
cmp cl,18
jbe resetsi ;jbe:jump if below or equal
mov cl,1
add dh,1
cmp dh,2
jb resetsi ;jb:jump if below
mov dh,0
add ch,1
cmp ch,cyls
jb resetsi
finish:
hlt
jmp finish
error:
mov ax,0
mov ds,ax
mov si,msg
mov di,msg
mov ah,0x0e
mov bx,15
show:
mov al,[di]
inc di
cmp al,0
je finish
int 0x10
jmp show
msg:
db 0x0a,0x0a
db "5 times,failed!!"
db 0x0a
db 0
resb 0x7dfe - $
db 0x55,0xaa
現在,由程序中我們讀軟盤的順序是:柱面->正反面->扇區。2、從啓動區執行操作系統
啓動區現在我們已經寫好了,那麼操作系統呢?我們可以寫個最簡單的.nas文件
finish:
hlt
jmp finish
將其保存成os.nas文件,然後將它編譯後成os.sys文件,最後將其保存到映像裏。一般向一個空軟盤保存文件時,
(1)文件名會寫在0x002600以後的地方;
(2)文件的內容會寫在0x004200以後的地方。
知道了這個,我們就可以在啓動區執行這個操作系統了。假設我們把磁盤上的內容裝載到內存0x8000,那麼磁盤0x4200處的內容就應該位於內存0x8000 + 0x4200 = 0xc200處,所以我們在os.nas里加上org 0xc200,在ipl.nas最後加上jmp 0xc200,那麼,這個最簡單的操作系統就會執行了。
3、32位模式前期準備
所謂32位模式,指的是CPU的模式。CPU有16位和32位兩種模式,在32位模式下不能啓用BIOS,這是因爲BIOS是用16位機器語言寫的,如果我們有什麼事情想要用BIOS來做,那就將其全部放在開頭先做。我們將會用到int 0x13,表示使用VGA圖形模式的320 * 200 * 8位彩色模式。即會有200行,320列的像素,每位像素可以在256種顏色中選 一種。另外,如果要在畫面上顯示東西,那麼就要往顯卡內存裏寫數據。在VGA模式下,顯卡內存爲0xa0000 ~ 0xaffff的64kb,每個地址都對應着畫面上的像素。
4、導入C語言
導入C語言其實就是將.c文件編譯成.obj文件並鏈接後,將其於我們寫好的彙編文件合併。具體的我們就不關注了。
5、C語言調用匯編函數
; naskfunc
; TAB=4
[FORMAT "WCOFF"] ;將輸出格式設定爲WCOFF模式
[INSTRSET "i486p"] ;告訴編譯器此程序給486用
[BITS 32] ;製作32位模式用的機器語言
[FILE "naskfunc.nas"] ;源文件名信息,必須在定義函數名前寫上
GLOBAL _io_hlt,_write_mem8 ;需要鏈接的函數名,都要用global指令聲明,函數名必須以下劃線開關
[SECTION .text] ;在寫函數前必須寫上這句後再寫函數
_io_hlt: ; void io_hlt(void);
HLT
RET
_write_mem8: ; void write_mem8(int addr,int data)
mov ecx,[esp + 4]
mov al,[esp + 8]
mov [ecx],al
ret
其中,ret與C語言中的return類似。如果要傳參數的話,那麼,第一個參數存放的地址是esp + 4,第二個參數存放的地址是esp + 8,第三個是esp + 12,以此類推。
那麼,在.c文件中如何調用呢?
void io_hlt(void);
void write_mem8(int addr,int data);
void HariMain(void)
{
int i;
for(i = 0xa0000;i <= 0xaffff;i++)
write_mem8(i,i & 0x0f);
while(1)
io_hlt();
}
先聲明函數,然後再調用。