比高斯更快的计算

1.1.从1加到100的故事

1.通过计算1到100的累加和,学习栈,了解处理器为访问栈提供的支持
2.总结Intel8086处理器的寻址方式
3.学习几个新的处理器指令,是or,and,push和pop
4.学习Bochs中调试程序时查看栈的方法

1.2.代码清单

1.3.显示字符串

可直接用单引号把一串字符围起来。
message db '1+2+3+...+100='
NASM支持这样的做法。在编译阶段,编译器将把它们拆开,形成一个个单独的字节。

1.4.计算1到100的累加和

先将寄存器AX清零,再用AX的内容和1相加,结果在AX中;接着,再用AX的内容和2相加,结果依旧在AX中,...,这样一直加到100。
AX可以容纳的无符号数最大是65535,再大就不行了。

1.5.累加和各个数位的分解与显示

1.5.1.栈和栈段的初始化

本章,分解出来的数位并不保存在数据段中,而保存在一个叫做栈的地方。
和代码段,数据段和附加段一样,栈也被定义成一个内存段,叫栈段,由段寄存器SS指向。
用栈指针寄存器SP来指示下一个数据应当压入栈内的什么位置,或数据从哪里出栈。
定义栈需要两个连续的步骤,即初始化段寄存器SS和栈指针SP的内容。
目前为止,我们已经定义了3个段。总的内存容量是1MB,物理地址范围是0x00000~0xFFFFF,其中,假定数据段长度是64KB,占据了物理地址0x07c00~0x17BFF,对应的逻辑地址范围是0x07c0:0x0000~0x07c0:0xFFFF;代码段和栈段是同一个段,占据着物理地址0x00000~0x0FFFF,对应的逻辑地址范围是0x0000:0x0000~0x0000:0xFFFF。
虽然代码段和栈段本质上指向同一块内存区域,但不用担心,主引导程序只占据中间一小部分,有办法让它们互不干扰。

1.5.2.分解各个数位并压栈

or
0 or 0 = 0
0 or 1 = 1
1 or 0 = 1
1 or 1 = 1
在处理器内部,or指令的目的操作数可以是8位或16位的通用寄存器,或包含8/16位实际操作数的内存单元,源操作数可以是与目的操作数数据宽度相同的通用寄存器,内存单元或立即数。比如:
or al, cl
or ax, dx
or [label_a], bx
or byte [bx], 0x55
or指令不允许目的操作数和源操作数都是内存单元的情况。
or指令对标志寄存器的影响是,OF和CF位被清零,SF,ZF,PF位的状态依计算结果而定,AF位的状态未定义。
and
0 and 0 = 0
0 and 1 = 0
1 and 0 = 0
1 and 1 = 1
相应地,处理器设计了and指令。在16位处理器上,and指令的两个操作数都应当是字节或者字。其中,目的操作数可以是通用寄存器和内存单元;源操作数可以是通用寄存器,内存单元,或者立即数。但不允许两个操作数同时为内存单元,且它们在数据宽度上应当一致。比如:
and al, 0x55
and ch, cl
and ax, dx
and [label_a], ah
and word [bx], 0xf0f0
and dx, [bx + si]
当这些指令执行时,两个操作数对应的各个比特位分别进行逻辑"与",结果保存在目的操作数中。and指令执行后,OF和CF位被清零,SF,ZF,PF位的状态依计算结果而定,AF位的状态未定义。
在16位处理器上,push指令的操作数可以是16位的寄存器或内存单元。例如:
push ax
push word [label_a]
8086处理器只能压入一个字,但其后的32位和64位处理器允许压入字,双字,四字。就8086处理器来说,压入栈的内容必须是字。下面指令非法
push al
push byte [label_a]
处理器在执行push指令时,首先将栈指针寄存器SP的内容减去操作数的字长【以字节为单位,16位处理器上为2】,然后,把要压入栈的数据存放到逻辑地址SS:SP所指向的内存位置。
例子中,当push第一次执行时,SP的内容减去2,即0x0000 - 0x0002 = 0xFFFE,借位被忽略。于是,被压入栈的数据在内存中的位置实际是0x0000:0xFFFE。push指令的操作数是字,且Intel处理器是使用低端字节序的,故低字节在低地址部分,高字节在高地址部分,正好占据栈段最高两个字节位置。
代码段在处理器上执行时,由低地址向高地址端推进。压栈,是从高地址端向低地址端推进的。push指令不影响任何标志位。

1.5.3.出栈并显示各个数位

pop dx指定的功能是将逻辑地址SS:SP处的一个字弹出到寄存器DX中,然后将SP的内容加上操作数的字长【2】。
和push指令一样,pop指令的操作数可以是16位的寄存器或内存单元。例如:
pop ax
pop word [label_a]
pop指令执行时,处理器将栈段寄存器SS的内容左移4位,再加上栈指针寄存器SP的内容,形成20位的物理地址访问内存,取得所需数据。然后,将SP的内容加操作数的字长,以指向下一个栈位置。pop指令不影响任何标志位。

1.5.4.进一步认识栈

1.push指令的操作数可以是16位寄存器或16位内存单元,push指令执行后,压入栈中的仅仅是该寄存器或内存单元里的数值,与该寄存器或内存单元不再相干。
2.栈在本质上也只是普通的内存区域,之所以用push和pop指令来访问,是因为你把它看成栈而已。实际上,如果把它看成普通的数据段而忘掉它是一个栈,它将不再神秘。
引入栈,pop,push只是为了方便程序开发。可以有等价替代方法。
push ax的等价替代
sub sp, 2
mov bx, sp
mov [ss:bx], ax
同样,pop ax指令的执行结果和下面的代码相同:
mov bx, sp
mov ax, [ss:bx]
add sp, 2
3.要注意保持栈平衡。如果做某件事的时候要使用栈,则栈指针寄存器SP在做这件事之前的值,应当和这件事做完后的值相同。
4.编写程序前,需充分估计所需的栈空间,以防止破坏有用的数据。特别是在栈段和其他段属于同一个段的时候。
5.通过将栈定义到一个单独的64KB段,可以使错误仅局限于栈,而不破坏其他段的有用数据。

1.6.程序的编译和运行

1.6.1.观察程序的运行结果

1.6.2.在调试过程中察看栈中内容

1.7.8086处理器的寻址方式

1.7.1.寄存器寻址

1.7.2.立即寻址

1.7.3.内存寻址

1.直接寻址
但凡表示内存地址的,都需用中括号括起来。比如:
mov ax, [0x5c0f]
add word [0x0230], 0x5000
xor byte [es:label_b], 0x05
2.基址寻址
所谓基址寻址,就是在指令的地址部分使用基址寄存器BX或者BP来提供偏移地址,如:
mov [bx], dx
add byte [bx], 0x55
第一条指令中的目的操作数采用了基址寻址。
第二条类似。基址寻址可使代码变得简洁高效,如:
	mov bx, buffer
	mov cx, 4
lpinc:
	inc word [bx]
	add bx, 2
	loop lpinc
基址寻址的寄存器也可是BP,如
mov ax, [bp]
与前面相比,它稍微有点特殊。原因在于,它采用的基址寄存器BP,在形成20位物理地址时,默认的段寄存器是SS。这条指令执行时,处理器将栈寄存器SS的内容左移4位,加上寄存器BP的内容,形成20位物理地址,并将该地址处的一个字传送到寄存器AX中。
为了能访问到被压在栈底的参数,这是BP就能派上用场。
mov ax, 0x5000
push ax
mov bp, sp
mov ax, 0x7000
push ax
mov dx, [bp]	
上述在0x7000仍在栈中时,访问到了其下的0x5000
基址寻址允许在基址寄存器的基础上使用一个偏移量。有时候,这使得它更加灵活,如:
mov dx, [bp - 2]
处理器执行时,将段寄存器SS的内容左移4位,加上BP的内容,再减去偏移量2以形成物理地址。
	
3.变址寻址
类似于基址寻址,唯一不同之处在于这种寻址方式使用的是变址寄存器【或称索引寄存器】SI和DI。例如:
mov [si], dx
add ax, [di]
xor word [si], 0x8000
当带有这种操作数的指令执行时,除非使用了段超越前缀,处理器会访问由段寄存器DS指向的数据段,偏移地址由寄存器SI或DI提供。
同样地,变址寻址方式也允许带一个偏移量。
mov [si + 0x100], al
and byte [di + label_a], 0x80

4.基址变址寻址
8086处理器也支持一种基址加变址的寻址方式,简称基址变址寻址。
使用基址变址的操作数可使用一个基址寄存器【BX或BP】,外加一个变址寄存器【SI或者DI】。它的基本形式是这样的:
mov ax, [bx + si]
add word [bx + di], 0x3000
第一条指令的源操作数采用了基址变址寻址。处理器执行这条指令时,把数据段寄存器DS的内容左移4位,加上基址寄存器BX的内容,再加上变址寄存器SI的内容,共同形成20位物理地址。然后,从该地址处取得一个字,传送到寄存器AX中。
第二条指令与第一条指令类似,不过是加法指令,它的目的操作数采用了基址变址寻址。源操作数采用立即寻址。这条指令执行时,处理器访问由段寄存器DS指向的数据段,加上由BX和DI相加形成的偏移地址,共同形成20位物理地址,然后将立即数0x3000加到该地址处的字单元里。
同样地,基址变址寻允许在基址寄存器和变址寄存器的基础上带一个偏移量。比如:
mov [bx + si + 0x100], al
add byte [bx + di + label_a], 0x80

猜你喜欢

转载自blog.csdn.net/x13262608581/article/details/124906569