裸機控制權與引導程序

操作系統實驗一

從零開發能使用的操作系統需要的可能不只是C語言,還需要彙編的支持。我沒有接觸過X86彙編,不過我學過MIPS的彙編,兩者其實有很多相似之處,主要的差別應該是在X86的寄存器上。除此之外其實X86還有串操作的語句,方便我們直接操作字符串或數組。這裏我們做一個練手的項目,設計一個能讓字符串在屏幕上按照左上、左下、右上、右下四種方向自動彈動的程序。
另外,我們還希望能夠通過把這段彙編程序的可執行二進制程序裝入軟盤,來實現裸機控制權的接管。這也可以說成是一個最簡單的操作系統。
打印字符串

main:
	org			07c00h
	mov			ax, cs
	mov			ds, ax
	mov			es, ax
	call		DispStr
	jmp			$


DispStr:
	mov			ax, BootMessage
	mov			bp, ax
	mov			cx, [Strlen]      ;字符串長
	mov			ax, 01301h		;寫模式
	mov			bx, 000fh		;頁號0,黑底白字		
	mov			dl, 0			;行=列=0
	int			10h				;10h號接口
	ret

BootMessage:		db		"huangwx8 17310031"
Strlen              db      17
times	510-($-$$) 	db		0
dw		0xaa55

這段代碼其實相當簡潔,最開始有一行org 07c00h,這個指令會讓編譯器把代碼裝載到0x7c00,如果我們把它編譯成BIN文件,在PC上運行時,PC識別到扇區末的0xaa55代號,認爲它是引導扇區;然後它會把這個扇區放到7c00h的地址處,把程序計數器設爲7c00h,從這裏開始執行程序,從此bios不再控制PC,而是我們的程序接管PC。
然後我們把ds和es指向與cs相同的段,是爲了後面偏移尋址找數據不會出錯。
DispStr部分,我們會把Message(一個指向字符串的地址)塞到es:bp,然後我們會爲ax賦一個值,高8位是13h,定義01h的功能爲寫字符串,低8位是01h,定義寫模式。然後爲bx賦一個值,高8位是頁號,這裏設爲0;低8位是控制顏色,高4位是底色,低四位是字色。0fh的意義就是黑底白字。cx是字符串長度,dx控制位置。
我們call int 10h,10h中斷號就是call系統的17號中斷向量。然後屏幕上就會打印出字符串了。
最後ret,然後 jmp 。這個是“當前行被彙編後的地址”。$$是”程序被編譯後的首地址”.因此jmp $是一個死循環,相當於halt。而times 510-($-$$) db 0則是爲了填滿內存從開頭到其後的510個字節爲0,再加上0xaa55的2字節,以重置512字節即一個扇區的空間。方便我們直接把這個東西燒到軟盤裏,讓bios識別並執行這段引導程序。
在這裏插入圖片描述
我們用nasm編譯器編譯這段代碼,就能得到一個512字節的bin程序。然後我們把這段程序複製到虛擬機所搭載的啓動軟盤的第一個扇區,虛擬機會執行程序邏輯,我們就能在屏幕上看見這樣的字符輸出啦,我的姓名和學號。
任意位置打印字符
上面說過了,設置dh和dl的數值就能控制打印位置。我們只需要用一個循環,在幾個不同的位置打印字符串,觀察一下輸出怎麼樣。

main:
	org			07c00h
	mov			ax, cs
	mov			ds, ax
	mov			es, ax
	call		DispStr
	jmp			$


DispStr:
	dec 		word[count]				; 遞減計數變量
	jnz 			DispStr					; >0:跳轉;
	mov 		word[count],delay
	dec 		word[dcount]				; 遞減計數變量
	jnz 			DispStr
	mov 		word[count],delay
	mov 		word[dcount],ddelay
	;用簡單的雙循環控制時延,這裏時延50000x580個時間單位
	
	mov			ax, BootMessage
	mov			bp, ax
	mov			cx, [Strlen]    ;字符串長
	mov			ax, 01301h		;寫模式
	mov			bx, 000fh		;頁號0,黑底白字		
	mov			dh, [boundary]	;行=b
	mov			dl, [boundary]	;列=b
	int			10h				;10h號接口
	dec 		byte[boundary]
	jnz 			DispStr
	ret

BootMessage:		db		"huangwx8 17310031"
Strlen               dw      17
delay 				equ 	50000
ddelay 				equ 	580
count 				dw 		delay
dcount 				dw 		ddelay
boundary			db		10

times	510-($-$$) 	db		0
dw		0xaa55

在這裏插入圖片描述
正如我們希望的,字符串在對角線方向被打印了10次。
彈球邏輯
我們剛學C語言的時候做過的最簡單的邏輯大概就是類似這種的邏輯了。這裏我們設置初始方向是向右下方,一旦觸壁就改變方向。即初始的速度vx、vy爲1,位置x、y爲0,0,除了上面的打印函數要調整以外,還要設置位置更新函數和邊界判斷函數;每次打印前判斷邊界並更新速度,打印時按照位置打印字符串,打印後更新位置。

org			07c00h
mov			ax, cs
mov			ds, ax
mov			es, ax
	
loop1:
	dec word[count]				; 遞減計數變量
	jnz loop1					; >0:跳轉;
	mov word[count],delay
	dec word[dcount]			; 遞減計數變量
    jnz loop1
	mov word[count],delay
	mov word[dcount],ddelay
	; 以上是用一個二重循環實現時延50000*580個單位時間
	
	jmp			Entrance		;進行一個週期的工作
	jmp			$				;halt

Entrance:
	jmp			BoundaryCheckx
DispStr:
	mov			ax, BootMessage		;打印字符串
	mov			bp, ax
	mov			cl, byte[Strlen]    ;字符串長
	mov			ch, 0
	mov			ax, 01301h			;寫模式
	mov			bx, 000fh			;頁號0,黑底白字		
	mov			dh, byte[x]			;行=x
	mov			dl, byte[y]			;列=y
	int			10h					;10h號接口
Updatexy:
	mov 		al, byte[x]
	add			al, byte[vx]
	mov			byte[x], al
	mov 		al, byte[y]
	add			al, byte[vy]
	mov			byte[y], al
	jmp			loop1				;無限循環
	
	
BoundaryCheckx:
	mov 		al, byte[x]
	add			al, byte[vx]	;預測下一刻的x
	cmp			al, byte[upper]	;如果x小於上邊界
	jl			Changevx		;更新vx
	cmp			al, byte[lower]	;如果x大於下邊界
	jg			Changevx		;更新vx
	
BoundaryChecky:
	mov 		al, byte[y]
	add			al, byte[vy]
	cmp			al, byte[left]	;如果y小於左邊界
	jl			Changevy		;更新vy
	add			al, byte[Strlen];預測下一刻的yr=y+字符串長
	cmp			al, byte[right]	;如果yr大於下邊界
	jg			Changevy		;更新vy
	jmp			DispStr			;如果不需要更新vx vy就繼續打印流程			


Changevx:
	neg			byte[vx]
	jmp			BoundaryChecky
	
Changevy:
	neg			byte[vy]
	jmp			DispStr
	
	

BootMessage:		db		"Welcome!"
Strlen              db      8
delay 				equ 	50000
ddelay 				equ 	580
count 				dw 		delay
dcount 				dw 		ddelay

x    				db 		0
y    				db 		0
vx    				db 		1
vy    				db 		1
left					db		0
upper				db		0
right				db		75
lower				db		24

boundary			db		10

times	510-($-$$) 	db		0
dw		0xaa55

對於比較複雜的邏輯要使用跳轉語句來實現簡單的遞歸調用。上面用了jg和jl作爲邏輯判斷語句,沒有用call和ret而是用了jmp實現遞歸調用和返回。總體上還是一個死循環(檢測-打印-更新位置),結果如圖。
在這裏插入圖片描述
字符串持續打印並按照一個速度運動,一旦遇到邊界就會讓一個方向的速度反轉。

小結
這個實驗難度並不大,儘管我沒有x86基礎,但經過了兩個小時的學習和兩個小時的編程實驗,這個項目也很快被完成了。實驗的目的主要是爲了讓我們理解PC開機後,硬件軟件做了什麼;以及讓我們熟悉x86彙編編程,爲以後更復雜的操作系統開發打下基礎。

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