直接定址标

描述了单元长度的标号(数据标号)

以往我们定义一段内存,都是这样的:  db 1,2,3,4,5,6,7,8    dw 0,如果为了方便,我们还可以在这些前面加上标号:

 a:db 1,2,3,4,5,6,7,8

 b:dw 0

这样的话,如果我们要用这些定义的数据或内存,我们可以通过他们的标号来寻找。但是我们现在有了一种可以使得我们工作起来更方便的定义数据或内存的方法,它不仅包含了内存单元或数据的完整地址,还包含了后面的数据或内存段的单元长度是字节还是字还是双字。

 a db 1,2,3,4,5,6,7,8

 b dw 0

与之前唯一的不同就是没有加冒号,这样定义数据或内存单元后,标号中就包含了我们定义的东西的完整地址,还有内存单元长度。比如,对于我们上面定义的 a 段。

当 mov al,a 就相当于mov al,cs:[0],因为此时 标号‘a’ 它就表示了 a 段的起始地址(我们假设它是程序的第一条指令,所以段地址在CS中),并且因为 标号a中还储存了内存单元的长度,所以我们转移的是一个字节的内容,所以用寄存器  al  。对于数据标号我们还可以这样用: mov al,a[si] 这就相当于mov al,cs:0[si] ,这种用法记住就可以,我们还可以对其扩展为 mov al,a[bx+si+常数]

也等同于 mov al,cs:0[bx+si+常数]

当 mov ax,b  就相当于mov ax,cs:[8] ,因为 b 段是接在 a 段后面的,它的段地址和 a 段一样,偏移地址为 8 。 mov b,2 就相当于mov word ptr cs:[8],2   ,每次转移一个字节的数据。如果是 mov b,al  将会出错。

在其他段中使用数据标号

如果我们想在代码段(就是不是我们标号所在段)中直接使用数据标号来访问其中的数据,那么我们需要用 assume 指令将段寄存器与我们标号所在的段联系起来,否则编译器编译时无法确定我们的标号的段地址在哪一个段中,而我们在程序中还需要将标号所在段的段地址存储到段寄存器中, mov ax,data  mov ds,ax 就用类似这样的指令。比如我们的指令 mov b,ax 就会直接编译为 mov [8],ax 而段地址就在ds中。而我们前面没有 assume ds:data 这一步是因为我们后面的程序与我们数据标号是在一个段中的,这里我们的代码与标号不在一个段中,所以我们要用 assume 指令。

我们还可以将标号当做数据来定义,比如:

 a db 1,2,3,4,5,6,7,8

 b dw 0

 c dw a,b

这样 c 段中就存储 a 段和 b 段的偏移地址,就相当于  c dw offset a,offset b

而如果 是 c dd a,b 那么 c 段中存储的就是 a 段和 c 段中的段地址和偏移地址,相当于 c dd offset a,seg a,offset b,seg b

 seg 是取段地址的指令,这里也是高位字存放段地址,低位字存放偏移地址

上面用 dw 时就是只存偏移地址,用 dd 时就是偏移地址和段地址

直接定制表

我们将给出的数据进行一些计算,然后将数据存储在我们规定的一个内存段中,然后我们后要用这些数据进行一些计算时,可以直接在那个内存段中查找对应结果,这种方式我们称为查表,具体方法就是根据我们给出的数据找出索要查找的结果在表中的位置,向这种可以通过可依据数据,直接可以计算出所要查找元素的位置的表,我们称为直接定制表。

我们举个例子:

假如我们要编写一个程序,它能够将我们给定的字节型数据以16进制的形式显示在屏幕上。因为我们给定的数据是一个字节,它会显示两个16进制数,因此我们可以将我们提供的数据分成两部分,每部分4位,正好对应一个16进制数,然后我们再找出它对应的16进制数。但是我们应该如何找到对应的16进制数呢?

方法一:计算,因为所有的16进制数为: 0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F  。对于前面的数字,我们可以用  数值+30h=对应的ASCII码。但对于后面的字母 10+37h=A的ASCII码  11+38h=B对应的ASCII码....以此类推。但是如果这样来寻找对应的16进制数会比较麻烦。所以我们可以用到直接定制表

方法二:直接定址表,我们可以将所有的16进制数都存到一段内存中,这就是我们的定址表。然后我们要用一种方法来找出我们的数据对应的结果在表中得位置。假设我们将数据存在了AL中,那么我们要先将这个数的二进制的前4位与后4位分开。我们可以先将AL中的数复制到AH中,然后再将AH中的数右移4位,然后再对AL中的数据进行相于运算,最后AH中存放了数据的高4位,AL中存放数据的低4位,然后我们分别将AH,AL中的数据存到另一个寄存器中,然后我们用那个寄存器中的值配合我们之前学的数据标号来寻址,然后就是数据对应的16进制数。代码实现如下

程序入口地址的直接定址表

我们可以在定址表中储存子程序的地址,这样就方便我们用时直接调用它

我们可以将我们要用的子程序的标号储存在我们的定址表中,注意:我们调用这些子程序时,是根据标号所包含的地址去寻找子程序的位置然后调用的。然后我们后面调用程序时应该使用 call word ptr 定址表名称[位置],而我们每一个子程序最后都要有一个 ret 指令,配套我们的 call 指令。

用BIOS进行键盘输入和磁盘读写

int 9h中断例程对键盘输入的处理:
通过前面的学习我们知道,当我们在键盘上按下一个键时,键盘上的芯片会产生一个扫描码,这个扫描码将会被送到端口60h中,然后相关芯片向CPU发送中断信息,此时如果IF为1则CPU转而去处理这个中断,引发中断过程,然后开始执行BIOS提供的 int 9h 中断例程,这个例程将从端口 60h 中提取扫描码,根据扫描码再生成其对应的ASCII码或状态信息,然后加将其ASCII码和扫描码或者状态信息存入 BIOS键盘缓冲区或者状态字节中。

在键盘缓冲区中,一共有16个字单元,可以存储15个按键和其扫描码,每个字单元中高位存储扫描码,低位存储ASCII码。那么这里有一个问题,就是键盘的扫描码和ASCII码是如何存入键盘缓冲区的。

初始状态下,键盘缓冲区是空的,什么元素也没有。就像下面这个表格(每个格子代表一个字单元):

然后,当我们按下一个键,比如说a键,引发键盘中断,CPU转而处理 int 9h 中断例程,int 9h例程从端口60h读取a键的通码,然后查看一下状态字节,看看是否有控制键或者切换键按下,没有,就将A键的扫描码和ASCII码写入键盘缓冲区,在一个字单元中低字节存储ASCII码,高字节存储扫描码。然后键盘缓冲区中就成了下面这个样子:

其中 1E为a键的扫描码。61为a键的ACSII码(这个61为16进制的)。

后面再按下其他键时其他键的信息就依次存放在后面。

而当我们按下左 shift 键,也就是切换键时,引发中断,int 9h 例程从端口60h 中提取左 shift键的通码,然后设置状态字节的第一位为 1 ,假如我们按着切换键,然后我们再按 a 键,引发中断,int 9h 中断例程从端口60h中读取 a 键的通码,然后检查一下状态字节,发现状态字节的第一位为 1 ,表示左 shift 键按下了,然后将A键的扫描码和 shift_A的ASCII码存入键盘缓冲区。

最后一个数据为我们按下 左shift键时按的A键的扫描和ASCII码。

然后松开shift 键,又会引发中断, int 9h 例程从端口 60h 获取 shift 的断码,然后将状态字节的第一位改为0 ,表示 shift 键没有按下。

int 16h 中断例程读取键盘缓冲区

int  16h 中断例程的作用以及执行过程:

  1. 检测键盘缓冲区是否有数据
  2. 有则进行第三步,没有则进行第一步
  3. 读取缓冲区中第一个字单元中的数据
  4. 将读取的扫描码送入 AH ,ASCII码送入 AL
  5. 将刚刚读取的数据从键盘缓冲区中删除

 假如我们的键盘缓冲区现在是这个样子:

然后我们将AX先归0,然后我们执行 int 16h,缓冲区就变成了这个样子:

AH中的数据变成了 1E,AL中的数据变成了61h。

再执行一次 int 16h,缓冲区变成了:

AH中的数据变成了 30,AL中的数据变成了62h。

以此类推。

如果我们执行很多次 int 16h 直到缓冲区空了之后,我们再执行一次,那么int 16h例程发现键盘缓冲区是空的,就循环等待,直到我们再次按下键盘,键的扫描码和ASCII码存入键盘缓冲区,然后 int 16h例程再次工作,将这个数据读入AX后从缓冲区删除它。

与 int 9h例程不同的是 :int 9h例程是只要当我们按下键盘,并且IF中的值为1时,它就执行,而 int 16h例程是只有当我们调用它时它才执行。

字符串的输入

假如我们要编写一个程序,这个程序可以实现:显示我们输入的字符,可以删除字符,输入回车键后代表输入结束。

对于字符的输入和删除:

  因为每次输入字符都是在之前的字符的后面,而每次删除字符也是删除掉最后面的字符,所以我们可以联想到栈,这和栈的机制是一样的,所以我们可以把从键盘输入的数据储存在栈中。然后对于我们按下回车键就结束输入的机制:我们可以在输入回车键后,在字符串中加入0,表示字符串输入结束。然后因为我们要显示我们输入的数据,应该是我们每输入一次都要将全部的字符显示一遍,就是从栈底到栈顶全部显示一遍。然后还有一个问题,就是我们如何将我们从键盘输入的数据存到栈中。方法就是用 int 16h例程,调用这个例程,然后将AL中的数据压入栈。

然后我们可以确定一下程序的处理过程:

  1. 调用 int 16h 读取键盘输入
  2. 如果是字符,将ASCII码压入栈,然后再执行1过程
  3. 如果是退格键,从字符栈弹出一个字符,并且显示所有字符,然后再返回执行1过程
  4. 如果是回车键,把0压入字符栈,然后返回

对于入栈出栈以及显示栈的内容,因为要在多处用到,所以我们将这些都写成子程序。注意:因为每次显示新的内容我们都需要将之前的内容清除,也就是要清屏,我们可以像显存存入空格字符。

应用 int 13h中断例程对磁盘进行读写

我们以3.5英寸软盘为例,3.5英寸软盘分为上下两面,每面有80个磁道,每个磁道有18个扇区,每个扇区有512字节,我们对磁盘进行读取数据时,是通过每面的一个磁头来读取的。

磁盘的访问由磁盘控制器来进行,在对磁盘进行访问时,我们只能以扇区为单位,而读取扇区的时候我们要给出面号,磁道号,扇区号。面号和磁道号的序号从0开始数,而扇区号从1开始数。

BIOS提供了访问磁盘的中断例程,可以为我们免去很多因为要访问磁盘而要处理的细节。这个中断例程就是 int 13h 。

如果我们要用中断例程 int 13h 来访问磁盘,那么我们需要提供以下数据:

 ah =int 13h的功能号(2表示读取扇区)al=读取的扇区数,ch=磁道号  cl=扇区号,dh=磁头号(磁头号对应着面号)dl=驱动器号(驱动器中包含软驱还有硬盘,软驱的序号从0开始 ,0表示软驱A,1表示软驱B。硬盘从80h开始,80h表示硬盘C,81h表示硬盘D),然后我们还要设置 ES:BX,它指向的内存是用来存放从磁盘读取的数据的。

所以假如我们要读取0面0道1扇区的内容到0:400的内存中的话,可以这样实现:

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值