一道题

作者: 焜_8899 | 来源:发表于2020-06-20 18:00 被阅读0次

1. 题目

背景
中断是一个很有效率的IO通讯方式。在PC里,1ch是一个计时器计时中断,每秒钟会中断18.2次。在原本的ISR(interrupt service routine,中断服务程序)中,1ch只有一条“iret”指令。实际上,原本的1ch ISR什么都没做。你需要重写一个新的1ch ISR以完成下述任务。

任务
每3秒在屏幕上打印“bell ring”并响铃。原本的ISR必须在程序结束前被存回原本的位置。

提示
在主程序中,通过调用DOS系统功能将一个新的ISR向量安装在中断向量表中。通过执行INT 21h的25h号功能可以安装中断向量。须注意应当先用DOS INT 21h的35h号功能将原本的中断向量保存下来。响铃ISR被定义为一个用于打印信息与响铃的过程。

2. 我的答案

虽然程序中的注释用了比较蹩脚的英文,但下面有分步解析,可以把此页面用两个标签页同时打开对比着看。

; This is the answer for the question.
; @author Hao Yukun
; @version 2020/6/19
stack segment
    db 128 dup(0)
stack ends

data segment
    count dw 1
    message db 'bell ring', 0dh, 0ah, '$'
data ends

code segment
    assume cs:code, ds:data, ss:stack
start:
    mov ax, data
    mov ds, ax
    
    ; get the old interrupt vector
    mov ah, 35h
    mov al, 1ch
    int 21h
    push es
    push bx
    
    ; install the new interrupt vector
    push ds         ; save the data before using the register
    mov ax, seg ring
    mov ds, ax
    mov dx, offset ring
    mov ah, 25h
    mov al, 1ch
    int 21h
    pop ds          ; return the data into the register
    
    ; delay some time for printing and ringing
    mov ah, 86h
    mov al, 0
    mov cx, 98h     ; 10s = 10,000,000us
    mov dx, 9680h   ; 10,000,000 = 0x 98 9680
    int 15h
    
    ; restore the old interrupt vector
    pop dx
    pop ds
    mov ah, 25h
    mov al, 1ch
    int 21h
    
    ; return to DOS (quit the program)
    mov ah, 4ch
    int 21h
    
    ; ring and print procedure
ring proc near
    ; ISR
    
    push ds         ; save to protect data in stack
    push ax
    push cx
    push dx
    
    dec count       ; count the time when 1ch is called
    jnz quit_the_interrupt
    
    ; print the message "bell ring"
    lea dx, message
    mov ah, 9
    int 21h
    
    ; ring
    mov dx, 100
    in al, 61h
    and al, 0fch
    sound:
        xor al, 02
        out 61h, al
        mov cx, 1989
        delay_for_freg: loop delay_for_freg
        dec dx
        jnz sound
    
    mov count, 55   ; 55 times of calling 1ch causes approximately 3s
    
quit_the_interrupt:
    
    pop dx          ; restore data from stack
    pop cx
    pop ax
    pop ds
    
    iret            ; interrupt return
ring endp

code ends
end start

3. 分步解析

3.1 获取并保存原中断向量

对应代码中注释; get the old interrupt vector后面的部分。

3.1.1 获取原中断向量

因为题目要求的是重写1ch中断,所以先将其原本内容取出。于是为al寄存器赋值为1ch
关于取中断向量的方法请看这里

mov ah, 35h
mov al, 1ch
int 21h

获取到的中断向量的段地址会被存入es寄存器,偏移地址会被存入bx寄存器。

3.1.2 保存原中断向量

本程序使用压栈的方式保存原中断向量。
刚才说到,“获取到的中断向量的段地址会被存入es寄存器,偏移地址会被存入bx寄存器”,所以将此二者压栈。

push es
push bx

当然,保存原中断向量不止这一种方法,例如

mov word ptr old, bx
mov word ptr old+2, es

对比之下,个人认为压栈的方式比较简单简洁。

3.2 安装新中断向量

对应代码中注释; install the new interrupt vector后面的部分。
关于安装中断向量的方法请看这里

push ds         ; save the data before using the register
mov ax, seg ring
mov ds, ax
mov dx, offset ring
mov ah, 25h
mov al, 1ch
int 21h
pop ds          ; return the data into the register

因为ds中存了此程序的数据段内容,而在安装新中断时又需要用到ds寄存器,所以先将其压栈保存,后面再出栈存回。
因为题目要求的是重写1ch中断,所以为al寄存器赋值为1ch
因为要安装的新中断向量以过程的形式写在了此程序靠后的部分,名称为ring, 所以取ring的段地址和偏移地址,分别存入dsdx

3.3 延时

对应代码中注释; delay some time for printing and ringing后面的部分。
因为题目要求“每3秒在屏幕上打印‘bell ring’并响铃”,而只有程序运行时间大于3秒、打印并响铃大于1次,才能体现出是每3秒做了一次规定操作,所以需要设置一个大于3秒的延时。
本程序中设置了一个约10秒的延时,关于延时的用法请看这里

mov ah, 86h
mov al, 0
mov cx, 98h     ; 10s = 10,000,000us
mov dx, 9680h   ; 10,000,000 = 0x 98 9680
int 15h

其实延时不只有这一种方法,这里介绍了三种方法。
此程序之所以选用这一种方法,是因为可以确定延时的具体时间。

3.4 将旧中断向量装回中断向量表

对应代码中注释; restore the old interrupt vector后面的部分。
在3.2和这里都讲了如何安装一个中断向量,将旧中断向量装回中断向量表的操作与之同理。

3.4.1 取出保存的旧中断向量

这里说到,需要将中断向量的段地址和偏移地址分别存入DSDX寄存器中。而在3.1.2中,旧中断向量的段地址和偏移地址已经被压栈。于是可直接将栈中内容取出,存入对应寄存器。

pop dx
pop ds

注意:由于栈是先进后出的,所以在进栈和出栈是都需要注意顺序
在3.1.2中,先进栈的是段地址,后进栈的是偏移地址,所以此处先将偏移地址出栈到dx中,在将段地址出栈到ds中。
若程序结束运行后,无法向DOS输入任何内容,则有可能是进栈或出栈的顺序出了问题。建议检查一下。

3.4.2 安装旧中断向量

旧中断向量的安装与3.2安装新中断向量的操作相同。

mov ah, 25h
mov al, 1ch
int 21h

3.5 退出程序,返回DOS

对应代码中注释; return to DOS (quit the program)后面的部分。
查阅资料时发现,结束程序返回DOS的方法不唯一。个人认为这种方法比较简单简洁,并用于此程序中。

mov ah, 4ch
int 21h

3.6 新中断向量过程

根据题目要求,将新中断向量写为了一个过程,对应代码中注释; ring and print procedure后面的部分。

3.6.1 保护现场

在调用中断的时候,需要对现场进行保护,所以将以下可能受影响的寄存器压栈。

push ds         ; save to protect data in stack
push ax
push cx
push dx

3.6.2 计时

由于本程序所使用的1ch中断会每秒被自动调用18.2次,而题目要求每3秒响一次铃并显示信息,所以需要通过计数以计时。
1ch每秒中断18.2次,所以每3秒中断54.6次。相当于每中断55次,可以认为大约过了3秒。
因此,在响一次铃并显示信息后,将count赋值为55,并在1ch每次中断的时候减一。这样,每当count变为零的时候,可以认为距上次响铃与显示信息过了大约3秒,需要再次响铃并显示信息。而在count非零的时候,需要跳过响铃和显示信息的步骤。

    dec count       ; count the time when 1ch is called
    jnz quit_the_interrupt
    
    ; 显示信息与响铃

    mov count, 55   ; 55 times of calling 1ch causes approximately 3s
    
quit_the_interrupt:
    
    ; 恢复现场与中断返回

3.6.3 显示信息

对应代码中注释; print the message "bell ring"后面的部分。
data segment部分,要显示的字符串信息已被存入message中,直接输出即可。

lea dx, message
mov ah, 9
int 21h

3.6.4 响铃

对应代码中注释; ring后面的部分。
其实这一部分我是存有疑问的。
在学习的过程中,包括大部分网上的资料,都表明扬声器应当如下述步骤使用。

; 1. 开启扬声器
in al,61h
or al,3
out 61h,al

; 2. 初始化8253定时器
mov al,0b6h
out 43h,al

; 3. 播放声音
mov bx,freg
mov al,bl
out 42h,al
mov al,bh
out 42h,al

; 4. 关闭扬声器
in al,61h
and al,0fch
out 61h,al

然而,经试验,中断时使用上述方法只能听到电流声,并没有清楚的响铃声。
在查找资料后,以下方法确认可行,但原因尚不清楚。

mov dx, 100
in al, 61h
and al, 0fch
sound:
    xor al, 02
    out 61h, al
    mov cx, 1989
    delay_for_freg: loop delay_for_freg
    dec dx
    jnz sound

同时,注意到其中的这一段代码:

mov cx, 1989
delay_for_freg: loop delay_for_freg

介绍延时的方法的时候,这段程序中1989应表示循环次数。然而,经试验,这里却成了调节音调的高低,原因尚不清楚。

3.6.5 恢复现场

在3.6.1中,通过压栈保护了现场。于是在中断返回前,需要出栈以恢复现场。

pop dx          ; restore data from stack
pop cx
pop ax
pop ds

相关文章

  • 生活,干活,生生的干好答题这一道活。

    活好,什么意思? 生活,一道题,一道必选题。生活,一道题,一道必选题。 多为客观选择题,少为主观表述题。 客观少有...

  • 名牌大学生的成功

    我再多会做一道题, 再多会做另一道题, 再多会做另另一道题, 多会做的每一道题, 都是在为高考加分, 多学点,再多...

  • 无关爱情 - 恋爱就像一张考卷

    选择题 判断题 填空题 论证题 计算题 最后一道大题 你说 最后一道大题你不会 有点郁闷 我只会最后一道大题……

  • iOS开发——从一道题看Delegate

    iOS开发——从一道题看Delegate iOS开发——从一道题看Delegate

  • 生活是什么?

    生活是什么?生活就是一道又一道的无解题!是语文题,是哲理题,是证明题!但不是数学题!因为数学题有唯一的答案!如果生...

  • 暮省

    今天做语文预习单的最后一道题。对我有很大的启发。我发现那道题不只是一道思考题题。而是一道关于人生的问题。当我...

  • 多做一点小努力 20220930 晨间日记

    人生的本质,它不是一道加法题,而是一道乘法题。 我们经常会觉得人生是一道加法题,你的财富需要每天朝九晚五工作好好积...

  • 做卷子

    今天我在学校语文课上做了一张卷子,老师让我们把几道题划去。 老师让我们一道题一道题的做。认真读题老...

  • 2020-04-22 第十四天

    2020-4-22 第十四天继续还是三道题;两道easy题。一道medium; 第一道easy题:#### 13...

  • 中考数学中解答题的8个题型及解题方法分析,拿140分真的很容易!

    中考数学解答题共有八道大题,其中技能部分占五道题,另一道应用题,一道探究题或方法迁移性问题,一道综合题. 一、实数...

网友评论

      本文标题:一道题

      本文链接:https://www.haomeiwen.com/subject/dfrbxktx.html