操作系统实践之第二章(实验环境升级)

其实上面的那个实验即使在实模式下也是可以实现的,并没有体现出保护模式的优势。我们知道实模式下寻址能力只有1M(因为能用的地址线只有20条),而保护模式下可以达到4G。而且前面的实验只是让程序进入了保护模式,我们可以在程序的末尾又再次跳转回实模式。

在进行这些美妙的跨越之前我们不得不再一次更新自己的调试环境。你可能想问为什么要更新环境,oh,Sweety,前面的方法都是把我们的程序写到引导扇区去执行的,但是我们知道那个“倒霉鬼”只有512个字节的容量,我们的程序一旦超过了这个限度它就无力再支持我们的程序正确运行了!所以我们得借助一些外力来达到目的,比如DOS。我们先把程序编译成COM文件,然后让DOS来替我们执行它。

那就开始动手吧。

1.下一个FreeDos,我已经为大家准备好了:http://pan.baidu.com/s/1jHVIYou  下载后将其解压,其中有一个a.img文件,将该文件复制到Bochs的安装目录下,并改名为freedos.img。

2.用bximage工具生成一个软盘映像(不会请参考第一章的内容),取名为pm.img。

3.修改Bochs的配置文件,原先为:floppya: 1_44=a.img,status=inserted;现在将其替换为如下三句:

 floppya: 1_44=freedos.img, status=inserted

floppyb: 1_44=pm.img,status=inserted

boot:a  #该配置用于启动FreeDos

4.启动Bochs,等待FreeDos启动完毕后格式化B:盘(有floppyb指定的pm.img),如下图示(使用指令:format  b:):


有时候由于种种原因,你第一次格式化失败了,那么以后成功的时候画面是下面这样的:


不管是哪种,反正都说明格式化成功了。

5.将前面的代码org处的07C00h改为0100h,并重新编译。注意,这次是编译为COM文件,使用命令nasm pmtest1.asm  -o  pmtest1.com 。

6.在Linux环境下(没有的话只有装个虚拟机了,在下也是这样解决的)将pmtest1.com复制到虚拟软盘pm.img上。最后把pm.img考回Bochs安装目录。


7. 启动Bochs,进入到FreeDos界面后运行如下命令,效果如图:


看见那个运行后的红色“P”就说明我们的环境升级成功了。

好了,那我们就运行如下代码来体验一下访问大于1M地址空间的感受吧(说不定那边的空气更清新),并且实现由实模式跳入保护模式再由保护模式跳回(我又进去了,我又出来了,你来打我啊)。

整片的代码就不贴了,可以在网上下到整本书中相关的完整代码(下面对应的代码是chapter3/b/pmtest2.asm中的内容),后面就主要的部分大家鉴赏一下。基本的解释都已经有了,有个别指令不认识的可以在网上查一下,个人觉得看懂还是不难的。

LABEL_SEG_CODE32:
	mov	ax, SelectorData
	mov	ds, ax			; 数据段选择子
	mov	ax, SelectorTest
	mov	es, ax			; 测试段选择子
	mov	ax, SelectorVideo
	mov	gs, ax			; 视频段选择子

	mov	ax, SelectorStack
	mov	ss, ax			; 堆栈段选择子

	mov	esp, TopOfStack


	; 下面显示一个字符串
	mov	ah, 0Ch			; 0000: 黑底    1100: 红字
	xor	esi, esi
	xor	edi, edi
	mov	esi, OffsetPMMessage	; 源数据偏移
	mov	edi, (80 * 10 + 0) * 2	; 目的数据偏移。屏幕第 10 行, 第 0 列。
	cld
.1:
	lodsb
	test	al, al
	jz	.2
	mov	[gs:edi], ax	;每个字节都设置了属性ah
	add	edi, 2
	jmp	.1
.2:	; 显示完毕

	call	DispReturn

	call	TestRead
	call	TestWrite
	call	TestRead

	; 到此停止
	jmp	SelectorCode16:0

; ------------------------------------------------------------------------
TestRead:
	xor	esi, esi
	mov	ecx, 8
.loop:
	mov	al, [es:esi]
	call	DispAL
	inc	esi
	loop	.loop

	call	DispReturn

	ret
; TestRead 结束-----------------------------------------------------------


; ------------------------------------------------------------------------
TestWrite:
	push	esi
	push	edi
	xor	esi, esi
	xor	edi, edi
	mov	esi, OffsetStrTest	; 源数据偏移
	cld
.1:
	lodsb
	test	al, al
	jz	.2
	mov	[es:edi], al
	inc	edi
	jmp	.1
.2:

	pop	edi
	pop	esi

	ret
; TestWrite 结束----------------------------------------------------------


; ------------------------------------------------------------------------
; 显示 AL 中的数字
; 默认地:
;	数字已经存在 AL 中
;	edi 始终指向要显示的下一个字符的位置
; 被改变的寄存器:
;	ax, edi
; ------------------------------------------------------------------------
DispAL:
	push	ecx
	push	edx

	mov	ah, 0Ch			; 0000: 黑底    1100: 红字
	mov	dl, al
	shr	al, 4
	mov	ecx, 2
.begin:
	and	al, 01111b
	cmp	al, 9
	ja	.1
	add	al, '0'
	jmp	.2
.1:
	sub	al, 0Ah
	add	al, 'A'
.2:
	mov	[gs:edi], ax
	add	edi, 2

	mov	al, dl
	loop	.begin
	add	edi, 2

	pop	edx
	pop	ecx

	ret
; DispAL 结束-------------------------------------------------------------


; ------------------------------------------------------------------------
DispReturn:
	push	eax
	push	ebx
	mov	eax, edi
	mov	bl, 160
	div	bl
	and	eax, 0FFh
	inc	eax
	mov	bl, 160
	mul	bl
	mov	edi, eax
	pop	ebx
	pop	eax

	ret
; DispReturn 结束---------------------------------------------------------

上面这个代码段实现了所有和读写以及显示相关的操作。需要注意的是上面代码中的寻址方式,基本都是以偏移的形式来给出的。例如其中的offsetStrTest和offsetPMMessage,我们在保护模式下需要用到的正是这个偏移量,而不再是实模式下的地址。代码中的section的妙用也可在此体现出来,配合$$标识符可以使我们的代码思路更加清晰,一目了然。关于这两个模式下的寻址我会在后面的章节详细描述,这里暂时就不解释了,因为篇幅可能会比较大。

[SECTION .s16code]
ALIGN	32
[BITS	16]
LABEL_SEG_CODE16:
	; 跳回实模式:
	mov	ax, SelectorNormal
	mov	ds, ax
	mov	es, ax
	mov	fs, ax
	mov	gs, ax
	mov	ss, ax
	;置cr0的PE位
	mov	eax, cr0
	and	al, 11111110b
	mov	cr0, eax

LABEL_GO_BACK_TO_REAL:
	jmp	0:LABEL_REAL_ENTRY	; 段地址会在程序开始处被设置成正确的值

Code16Len	equ	$ - LABEL_SEG_CODE16

; END of [SECTION .s16code]

以上代码主要实现从保护模式返回实模式的功能。从实模式进入保护模式时可以直接跳转来达到目标,但是返回的时候回稍微复杂一些。因为在准备结束保护模式回到实模式之前,需要加载一个合适的描述符选择子到有关段寄存器,以使对应段描述符高速缓冲寄存器中含有合适的段界限和属性。而且,我们不能从32位代码段返回实模式,只能从16位代码段返回。这是因为无法实现从32位代码段返回时CS高速缓冲寄存器中的属性符合实模式的要求(实模式不能改变段属性)。

着重看一下返回实模式的跳转:

jmp	0:LABEL_REAL_ENTRY

看起来好像它的段地址为0,其实前面有这样的操作:

mov	ax, cs
mov	ds, ax
mov	es, ax
mov	ss, ax
mov	sp, 0100h
mov	[LABEL_GO_BACK_TO_REAL+3], ax

实模式下长跳转指令的示意图如下:


即刚好将进入保护模式前的实模式下的CS设置为了该跳转指令的段地址。

最后,跳回实模式下,程序重新设置各个段寄存器的值,恢复sp,然后关闭A20地址线,开中断,恢复原来的样子。具体代码如下:

LABEL_REAL_ENTRY:		; 从保护模式跳回到实模式就到了这里
	mov	ax, cs
	mov	ds, ax
	mov	es, ax
	mov	ss, ax
	mov	sp, [SPValueInRealMode];恢复sp,进入保护模式前改制已被保存

	in	al, 92h		; \
	and	al, 11111101b	;  | 关闭 A20 地址线
	out	92h, al		; /
	sti			; 开中断
	mov	ax, 4c00h	; \
	int	21h		; /  回到 DOS

程序大致就是这个样子了,可以使用我们新搭建的环境运行一下,其运行结果如下图所示(可以将pm.img再次格式化后再将pmtest2.com拷贝到其中去):







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值