裸机控制权与引导程序

操作系统实验一

从零开发能使用的操作系统需要的可能不只是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汇编编程,为以后更复杂的操作系统开发打下基础。

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