第3天 進入32位模式並導入C語言

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();
}
先聲明函數,然後再調用。

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