• 在屏幕中间显示,按ESC键后改变字符的颜色


    程序功能:编写程序在屏幕中间显示“a”~“z”,并可以让人看清,这个任务比较好实现。
    (1)在b800:[ 160*12+40*2]处存入a的ASCII码、(2)在循环中使用一个100000000000H次的循环空转达到延迟效果、(3)按键盘引发int9中断改变颜色
    那么如何实现,按下Esc 键后,改变显示的颜色呢?
    键盘输入到达60h 端口后,就会引发9号中断,CPU 则转去执行int 9中断例程。
    我们可以编写int 9中断例程,功能如下:
    (1)从60h 端口读出键盘的输入;
    (2)调用BIOS 的int 9 中断例程,处理其他硬件细节;
    (3)判断是否为Esc的扫描码,如果是,改变显示的颜色后返回;如果不是则直接返回。
    我们对这些功能的实现一一进行分析
    1、从端口60h读出键盘的输入使用:in al,60h
    2、调用BIOS的int 9中断例程
    有一点要注意的是,我们写的中断处理程序要成为新的int 9中断例程,主程序必须要将中断向量表中的int 9中断例程的入口地址改为我们写的中断处理程序的入口地址。那么在新的中断处理程序中调用原来的int 9中断例程时,中断向量表中的int 9中断例程的入口地址却不是原来的int 9 中断例程的地址。所以我们不能使用int 指令直接调用。要在我们写的新中断例程中调用原来的中断例程,就必须在将中断向量表中的中断例程的入口地址改为新地址之前,将原来的入口地址保存起来。这样,在需要调用的时候,我们才能找到原来的中断例程的入口。对于我们现在的问题,假设我们将原来int 9中断例程的偏移地址和段地址保存在ds:[0]和ds:[2]单元中。那么我们在需要调用原来的int 9中断例程时候,就可以在ds:[0]、ds:[2] 单元中找到它的入口地址。那么,有了入口地址后,我们如何进行调用呢?
    当然不能使用指令int 9来调用。我们可以用别的指令来对int指令进行一些模拟,从而实现对中断例程的调用。我们来看,int 指令在执行的时候,CPU 进行下面的工作:
    (1)取中断类型码n;
    (2)标志寄存器入栈;
    (3)IF=0,TF=0;
    (4)CS 、IP 入栈;
    (5)设置(IP)=(n*4),(CS=(n*4+2)。
    取中断类型码是为了定位中断例程的入口地址,在我们的问题中,中断例程的入口地址已经知道。所以,我们用别的指令模拟int指令时候,不需要做第(1)步。在假设要调用的中断例程的入口地址在ds:0和ds:2单元中的前提下,我们将int 过程用下面几步模拟:
    (1)标志寄存器入栈;
    (2)IF=0,TF=0;
    (3)CS、IP入栈;
    (4)(IP)=((ds)*16+0),(CS)=((ds)*16+2)。
    可以注意到第(3)、(4)步和call dword ptr ds:[0]的功能一样。call dword ptr ds:[0]的功能也是:(1)CS 、IP 入栈;(2)(IP)=((ds)*16+0),(CS)=((ds)*16+2)。
    说明:如果还有疑问,复习10.6节的内容。
    所以int 过程的模拟过程变为:
    (1)标志寄存器入栈;
    (2)IF=0,TF=0;
    (3)call dword ptr ds:[0]
    对于(1),可用pushf来实现。
    对于(2),可用and和popf实现,如下面的指令实现。
    实现IF=0,TF=0步骤:
    pushf
    pop ax
    and ah,11111100b ;IF和OF为标志寄存器的第9位和第8位
    push ax
    popf
    这样,模拟int指令的调用功能,调用入口地址在ds:0、ds:2中的中断例程的程序如下
    pushf ;标志寄存器入栈
    pushf;实现IF=0,TF=0的功能
    pop ax
    and ah,11111100b ;IF和OF为标志寄存器的第9位和第8位
    push ax
    popf ;IF=0、TF=0
    call dword ptr ds:[0];call功能: ①CS、IP入栈,②;(IP)=((ds)*16+0),③;(CS)=((ds)*16+2)
    3、如果是Esc键的扫描码,改变显示的颜色后返回,如何改变显示的颜色?
    显示的位置是屏幕的中问,即第12行40列,显存中的偏移地址为:160*12+40* 2。所以字符的ASCII码要送入b800:160*12+40*2处。而b800:160*12+40*2+1处是字符的属性,我们只要改变此处的数据就可以改变在b800:160*12+40*2处显示的字符的颜色了。
    该程序的最后一个问题是,要在程序返回前,将中断向量表中的ini 9中断例程的入口地址恢复为原来的地址。否则程序返回后,别的程序将无法使用键盘。
    注意,本章中所有关于键盘的程序,因要直接访问真实的硬件,则必须在DOS实模式下运行。在Windows 2000 的DOS 方式下运行,会出现一些和硬件工作原理不符合的现象。
    开发int9中断例程架构:
    ①在主程序中把原来的int9的原始程序入口保存到data段中,并把自己写的int9中断例程入口地址替换到中断向量表的9号中断地址,对应的是IP是0: [9*4]和CS是0[9*4+2]
    ②等待外部中断自动调用int9
    ③程序运行完后还原int9原来的中断例程入口
    在int9中断例程内部结构
    1.保存用到的通用寄存器
    2.接收60h端口的数据
    3.修改IF和TF的值为0
    4.处理数据
    5.使用call模拟调用int9的系统中断例程
    6.还原通用寄存器
    说明:由于中断例程使用的是iret返回,而iret的过程是①从栈中还原IP和CS,②从栈中还原寄存器状态;这里使用了call的远跳转(地址在data段中),而用call过程是先把CS和IP保存进栈,跳转到指定地址执行完再通过retf返回,调用完成后再从栈中还原IP和CS;而这里我们调用的是中断例程,是用iret返回的,retf和iret返回的IP和CS顺序相同,而iret比retf多一步还原寄存器状态,所以我们要构造供iret的返回的栈数据:就是在call前先保存寄存器状态,然后就可以使用iret的形式还原程序的IP,CS,标志寄存器。我们只要在自己编写的中断例程中处理完自己的数据后再调用BIOS 的int 9中断例程就可以了。

    ;程序功能:在屏幕中间同一点显示a-z的所有字符
    ;        1.使用cpu循环空运行实现延迟
    ;        2.按ESC键改变正在循环显示的字符的颜色
    ;        3.程序完成时,再次还原int9中断向量表
    assume cs:code
    data segment
    db 100 dup(0)
    
    data ends
    
    code segment
    start: 
        ;init ds,es
        mov ax,data
        mov ds,ax
        mov ax,0
        mov es,ax
        
        ; save int9 IP and CS
        mov ax,es:[9*4]
        mov ds:[0],ax
        mov ax,es:[9*4+2]
        mov ds:[2],ax
        mov es:[9*4],offset int9
        mov es:[9*4+2],cs
        
        ;循环显示a-z的所有字符
        mov ax,0b800h
        mov es,ax
        mov ah,'a'
        show: mov es:[160*12+40*2],ah
        call delay    ;调用延迟
        inc ah
        cmp ah,'z'
        jna show
        
    
        ;还原int9中断向量表
        mov ax,0
        mov es,ax
        push ds:[0]
        pop es:[9*4]
        push ds:[2]
        pop es:[9*4+2]
        
        mov ax,4c00h
        int 21h
    
    
    ;使用cup循环空运行,实现延迟时间的作用
    ;使用32位的借位减法实现 1000 0000 0000h 次数的循环
    delay:
        push ax
        push dx
        mov dx,1000h
        mov ax,0
        s: sub ax,1
        sbb dx,0
        cmp ax,0
        jne s
        cmp dx,0
        jne s
        pop dx
        pop ax
        ret
    
    ;实现int9中断例程
    ;pushf是16位,pop是16位
    int9:
        
        push ax    ;保存临时变量
        in al,60h
        
        pushf    ;用于call模拟int9后的popf操作
        pushf    ;用于修改IF=0、TF=0 操作
        pop bx
        and bh,11111100b
        push bx
        popf
        call dword ptr ds:[0]    
        ;调用完这个模拟的int9后,会执行iret操作
        ;会先popf出第一次pushf的值
        
        cmp al,1
        jne int9ret
        add byte ptr es:[160*12+40*2+1],11h
        int9ret:
        pop ax
        iret
    
    
    code ends
    end start

    程序简化:

    1)在键盘按键的时候触发int9中断例程,这时候IF和TF都已经设置成0了,使用我们就没必要设置了

    2)原始的int9可以保存到0:200中,在程序中使用中断调用

    存在的问题:

    在上面的程序,如果设置中断向量表的时候没有全部完成,就是重定位中断向量表地址的时候出现了键盘的按键中断,在CUP会跳到一个错误的地址执行,如何解决呢?                            提示:使用sti和cli命令

  • 相关阅读:
    遍历datatable的方法
    C# 存储过程使用方法
    C# 存储过程
    dev repositoryItem 手工定义
    无限极分类
    ThinkPHP
    ThinkPHP
    RBAC
    ThinkPHP
    正则表达式
  • 原文地址:https://www.cnblogs.com/mq0036/p/5150801.html
Copyright © 2020-2023  润新知