学习交流加
- 个人qq:
1126137994- 个人微信:
liu1126137994- 学习交流资源分享qq群:
962535112
上一篇文章,我们用比较原始的方法编写了主引导扇区的代码。点击链接查看上一篇文章:编写主引导扇区代码
本片文章将学习以下内容:
- 用一种不同的分段方法,从另一个不同的的角度理解处理器的分段内存访问机制
- 使用循环和条件转移指令来优化上一篇文章的主引导扇区代码
1、代码清单
首先先贴上代码。50行代码,不长。看到汇编不要害怕!!!后面会一步一步分析这个汇编代码的每一条指令的意思。
;代码清单6-1
;文件名:c06_mbr.asm
;文件说明:硬盘主引导扇区代码
;
jmp near start
mytext db 'L',0x07,'a',0x07,'b',0x07,'e',0x07,'l',0x07,' ',0x07,'o',0x07,\
'f',0x07,'f',0x07,'s',0x07,'e',0x07,'t',0x07,':',0x07
number db 0,0,0,0,0
start:
mov ax,0x07c0 ;设置数据段基地址
mov ds,ax
mov ax,0xb800 ;设置附加段基地址,也就是将ES寄存器指向显存的起始地址
mov es,ax
cld ;方向清零标志,将DF标志位清零,代表传送是正向的
mov si,mytext ;SI与DS组成数据段的地址 DS:SI 代表数据的真实物理地址
mov di,0 ;DI与ES组成显存的物理地址 ES:DI 代表显存的真实物理地址
mov cx,(number-mytext)/2 ;实际上等于 13
rep movsw ;循环movsw,直到cx寄存器内容为0(rep指令代表反复传送)
;得到标号所代表的偏移地址
mov ax,number
;计算各个数位
mov bx,ax
mov cx,5 ;循环次数
mov si,10 ;除数
digit:
xor dx,dx
div si
mov [bx],dl ;保存数位
inc bx ;使bx寄存器里的值加1
loop digit
;显示各个数位
mov bx,number ;将number的汇编地址传送给BX寄存器
mov si,4 ;bx+si 得到字符串的每一个字符,SI从4递减到0,这是由于要先显示万位上的数字
show:
mov al,[bx+si]
add al,0x30 ;得到它对应的ASCII码
mov ah,0x04 ;对应的颜色属性
mov [es:di],ax ;AX中是一个完整的字,前8位是显示属性值,后8位是字符的ASCII码
add di,2 ;DI寄存器在之前用过,现在在“Label offset:” 字符串后面,刚好我们想让number的汇编地址在这里显示
dec si ;SI-1,从number代表的汇编地址的万位到个位,dec指令会影响SF标志位,当SI寄存器的值为0的时候,SF的标志位置1
jns show ;判断SF标志位是否为0,当SF标志位不为0,继续执行show处的代码。当SF标志位为0,则跳过这条指令执行下一条指令。
mov word [es:di],0x0744 ;高字节0x07是黑底白字的属性,低字节0x44是字符‘D’的ASCII码
jmp near $ ;相当于 infi: jmp near infi
times 510-($-$$) db 0 ; 计算512字节中,需要填满的字节有哪些。
db 0x55,0xaa ;一个有效的主引导扇区,最后两字节必须是0x55 0xaa
2、代码分析
坚持看完,一定能看懂!!!
- 8行-9行:这里声明了非指令的数据。一般来说,所有处理器指令都是按顺序存放,在他们中间不允许夹杂非指令的数据。但是如果有办法让处理器不执行这些数据,则又另当别论。如第6行的代码。
这两行声明的是要在显示屏上显示的数据:"Label offset:
",其中0x07是每个字符的显示属性值。
- 6行:它是一条转移指令。让处理器跳转到标号
start
处开始执行。这就避开了数据区。 - 13-14行:设置数据段的基地址。DS代表数据段的基地址。
这里为什么是0x07c0
呢?
由上几篇文章学过的知识知道,主引导扇区程序加载时,被加载到的位置是0x0000:0x7c00
.也就是物理地址:0x07c00
这其实就是将整个物理地址空间看成是基地址0x0000
,偏移地址0x7c00
的分段方式。
这样的话,CPU每次访问内存的时候总是要加上0x7c00
这个偏移地址。但是程序中一般访问内存的指令非常多,每一条都加上0x7c00
很不现实。
但是Intel处理器的分段策略很灵活。逻辑地址0x0000:0x7c00
对应的物理地址是0x07c00
,而该地址又是另一个逻辑地址0x07c0:0x0000
的地址。如下图是以两个逻辑段的视角看待同一个内存区域。
我们可以将512字节的区域看成是一个单独的段。段的基地址是:0x07c0
段长512字节。注意,该段的最大长度是64KB,但是这里我们实际上只用了512字节。尽管BIOS是将主引导扇区加载到物理地址0x07c00
处,但是我们却可以认为它是从0x07c0:0x0000
处开始加载的。
所以13-14行将数据段寄存器DS指向0x07c0
- 16-17行:使附加段寄存器ES的内容指向显存的基地址
0xb800
- 19-23行:循环movsw,直到cx寄存器内容为0(rep指令代表反复传送)。这里是循环将
DS:SI
所指向的数据传送到ES:DI
所指定的显示缓冲区。
循环movsw与movsb指令执行时,将DS:SI
所指向的数据传送到ES:DI
所指定地址。同时每传送一次 ,CX寄存器的内容减一。
rep代表循环movsw,直到寄存CX的内容为0为止。所以22行中,计算出数据的字节数,并将其传送到CX寄存器。
20行将SI指向数据区的首地址,SI与DS组成数据段的地址 DS:SI 代表数据的真实物理地址
21行将0给DI寄存器,DI与ES组成显存的物理地址 ES:DI 代表显存的真实物理地址。很明显,我们是从显存的0偏移地址开始存数据。
19行,方向清零标志,将DF标志位清零,代表传送是正向的。**正向的意思是传送操作的方向是从内存的低地址端到搞地质端。**很明显我们是正向传送。
-
26行:我们还是想像上一篇文章一样,显示字符串后将number这个标号的数值显示出来。所以先将number标号的汇编地址传送给AX寄存器保存。后面会用。
-
29-37行:还记得上一篇文章是如何分解number的各个数位的么?如果不记得,请点击链接查看:上一篇文章 上一篇文章是一个一个分解然后保存的。这里有所改变。使用了循环,可以让我们少写很多代码。这里就不多说了,不懂的看上一篇文章,这个循环也很好理解,
loop
这个指令将循环次数CX减一,指导CX等于0为止。 -
40-49行:显示标号number的汇编地址的各个数位。同理,如何显示各个数位,可以查看上一篇文章。这里只是将重复的代码,写成了循环的形式。
jns
这个指令判断SF标志位是否为0,当SF标志位不为0,继续执行show处的代码。当SF标志位为0,则跳过这条指令执行下一条指令。
dec
指令会影响SF标志位,当SI寄存器的值为0的时候,SF的标志位置1
这里唯一需要注意的是低端字节序传送的时候,寄存器的低字节传送到显示缓冲区的低地址部分,寄存器的高字节传送到显示缓冲区的高地址部分。如下图所示:
- 51行:显示字符‘D’
- 53行:死循环
- 55行:计算512字节中,空字节有多少,然后将这些空字节填满0
$
代表当前行的汇编地址
$$
代表当前段的起始地址。由于本程序没有定义段,所以自成一个段,并且起始地址是0地址。
- 56行:一个有效的主引导扇区,最后两字节必须是0x55 0xaa
3、编译运行
将我们汇编代码编译好的二进制bin文件写到虚拟硬盘的主引导扇区中。启动虚拟机,就会运行我们写的代码,运行结果如下:
今天的程序运行的很顺利。
4、总结
了解汇编的运行机制,对以后深入学习高级语言,很有帮助:比如JVM。
笔记记得不是很全,像汇编的语法以及如何将代码写到虚拟硬盘的主引导扇区这些都没有写。如果又不懂的可以加我联系方式一起交流。
学习探讨加个人:
qq:1126137994
微信:liu1126137994