#pragma once
/* 28-[BX]和Loop指令06
一段安全的空间
在8086模式中,随意向一段内存空间写入内容是很危险的 ,因为这段空间中可能存放着重要的系统数据或代码。
比如下面的指令:
mov ax,1000h
mov ds,ax
mov al,0
mov ds:[0],al
我们以前在Debug中,为了讲解上的方便,写过类似的指令。
但这种做法是不合理的 ,因为之前我们并没有论证过 1000:0中是否存放着重要的系统数据或代码。
如果1000:0中存放着重要的系统数据或代码,“mov ds:[0],al” 将其改写,将引发错误。
比如程序
assume cs:code
code segment
start: mov ax,0
mov ds,ax
mov ds:[26H],ax
mov ax,4c00H
int 21H
code ends
end start
..... 执行了之后发现报错了,被终止了.
可见,我们在不能确定一段内存空间中是否存放着重要的放据或代码的时候,不能随意向其中写入内容。
不要忘记,我们是在操作系统的环境中工作,操作系统管理所有的资源,也包括内存。
同样不能忘记,我们正在学习的是汇编语言,要通过它来获得底层的编程体验,理解计算机底层的基本工作机理。
所以我们尽量直接对硬件编程,而不去理会操作系统。
我们似乎面临一种选择,是在操作系统中安全、规矩地编程,还是自由、直接地用汇编语言去操作真实的硬件,了解那些早己被层层系统软件掩盖的真相?
在大部分的情况下,我们选择后者,除非我们就是在学习操作系统本身的内容。
注意:
我们在纯DOS方式(实模式)下,可以不理会DOS,直接用汇编语言去操作真实的硬件,因为运行在CPU实模式下的DOS,没有能力对硬件系统进行全面、严格地管理。
但在Windows XP\2000、UNIX这些运行于CPU保护模式下的操作系统中,不理会操作系统,用汇编语言去操作真实的硬件,是根本不可能的。
硬件已被这些操作系统利用CPU保护模式所提供的功能全面而严格地管理了。
在后面的课程中,我们需要直接向内存中写入内容,可我们又不希望发生刚才报错的那种情况。所以要找到一段安全的空间供我们使用。
在一般的PC机中,DOS方式下,DOS和其他合法的程序一般都不会使用0:200~0:2FF( 0:200h~0:2FFh)的256 个字节的空间。所以,我们使用这段空间是安全的。********* 终点!
不过为了谨慎起见,在进入DOS后,我们可以先用Debug 查看一下,如果0:200~0:2FF单元的内容都是0的话,则证明DOS 和其他合法的程序没有使用这里。(知道了吗?这段空间可以直接实现进程之间的简单通讯)
为什么DOS和其他合法的程序一般都不会使用0:200~0:2FF这段空间?我们将在以后的课程中讨论这个问题。
好了,我们总结一下:
(1)我们需要直接向一段内存中写入内容;
(2)这段内存空间不应存放系统或其他程序的数据或代码,否则写入操作很可能引发错误。
(3)DOS方式下,一般情况, 0:200~0:2FF 空间中没有系统或其他程序的数据或代码;
(4)以后,我们需要直接向一段内存中写入内容时,就使用0:200~0:2FF这段空间。
段前缀的使用
我们考虑一个问题:
将内存ffff:0~ffff:b段元中的数据拷贝到 0:200~0:20b单元中
分析一下:
(1) 0:200~0:20b单元等同于0020:0~0020:b单元,它们描述的是同一段内存空间: (同一地址)
(2)拷贝的过程应用循环实现,简要描述如下:
初始化:X=0
循环12次:
将ffff:X单元中的数据送入0020:X(需要用一个寄存器中转)
X=X+1
(3)在循环中,源单元ffff:X和目标单元的0020:X的偏移地址X是变量。我们用bx来存放。
(4)我们用将0:200~0:20b用0020:0~0020:b描述,就是为了使目标单元的偏移地址和源始单元的偏移地址从同一数值0开始。
程序
assume cs:code
code segment
start: mov bx,0 ;(bx) = 0 偏移地址从0开始 记住bx 以后都用来存放偏移, 这是程序员之间的约定.
mov cx,12 ;(cx) = 12,循环12次
s: mov ax,0ffffH ; 初始化ax 中的值,用来给ds 数据寄存器赋值用
mov ds,ax ; (ds) = 0ffffh
mov dl,[bx] ; (dl) = ((ds)*16+(bx)), 将ffff:bx 中的数据送入dl,一个临时存储的作用 dl 是 dx 寄存器的低位
mov ax,0020H ;
mov ds,ax ;(ds) = 0020H ds 左移一位后就是 00200H啦~
mov [bx],al ;((ds)*16+(bx)) = (dl), 将dl 的数据送入0020:bx
inc bx ;(bx) = (bx) +1
loop s
mov ax,4c00H
int 21H
code ends
end start
代码分析
因源单元ffff:X和目标单元0020:X 相距大于64KB,在不同的64KB段里,程序中,每次循环要设置两次ds。
这样做是正确的,但是效率不高。
我们可以使用两个段寄存器分别存放源单元ffff:X和目标单元0020:X的段地址,这样就可以省略循环中需要重复做12次的设置ds的程序段。
改进的程序分析
assume cs:code
code segment
start: mov ax,0ffffH ; 初始化ax 中的值,用来给ds 数据寄存器赋值用
mov ds,ax ; (ds) = 0ffffh
mov ax,0020H ;
mov es,ax ; 使用了es 附加段寄存器 es 寄存器也和 ds 一样的功能,做的是数据操作.
mov bx,0 ;(bx) = 0 偏移地址从0开始 记住bx 以后都用来存放偏移, 这是程序员之间的约定.
mov cx,12 ;(cx) = 12,循环12次
s: mov dl,[bx] ; (dl) = ((ds)*16+(bx)), 将ffff:bx 中的数据送入dl,一个临时存储的作用 dl 是 dx 寄存器的低位
mov es:[bx],dl ; ((es)*16+(bx)) = (dl) 把dl 的数据放到 es:[bx] 中.
inc bx ;(bx) = (bx) +1 增加偏移
loop s
mov ax,4c00H
int 21H
code ends
end start
改进的程序中,使用 es 存放目标空间0020:0~0020:b的段地址,用ds存放源空间ffff:0~ffff:b的段地址。
在访问内存单元的指令“mov es:[bx],al”中 ,显式地用段前缀 “es:” 给出单元的段地址,这样就不必在循环中重复设置ds。
实验 [bx] 和 Loop 的使用
(1)编程,向内存0:200-0:23F依次传送数据0~63(3FH)。
(2)编程,向内存0:200~0:23F依次传送数据0~63(3FH),程序中只能使用9条指令,9条指令中包括"mov ax,4c00h”和"int 21h”。
(3)下面的程序的功能是将"mov ax,4c00h”之前的指令复制到内存0:200处,补全程序。上机调试,跟踪运行结果。
assume cs:code
code segment 补全:
mov ax,___ 像传递哪就写哪 例如 2000h
mov ds, ax
mov ax, 0020h
mov es, ax
mov bx, 0
mov cx,___ 63 64个字节 因为是从0 开始.
s:mov al, [bx]
mov es: [bx], al
inc bx
loop s
mov ax, 4c00h
int 21h
code ends
end
提示:(1)复制的是什么?从哪里到哪里?
(2)复制的是什么?有多少个字节?你如何知道要复制的字节的数量?
注意,一定要做完这个实验才能进行下面的课程。
*/