实验指南:8086汇编大作业 1. 实验目标
熟悉8086汇编的语法和部分中断例程的使用或修改; 通过实验加深对中断的理解,并能编程模拟简单的中断切换过程。
2. 实验内容 实验一:键盘中断综合练习
实验步骤:
实验分 4 个部分完成:
1. 编写子程序func1,完成下列功能:
在代码段 code 中存放 3 个字符串: this is message 1 , this is message 2 , this is message 3 ,在数据段 data 中存放一个计数器变量;
函数 func1 中首先对数据段 data 中的计数器变量 + 1 ,然后进行 % 3 运算,若结果为 1 打 印 this is message 1 ,结果为 2 打印 this is message 2 ,结果为 0 打印 this is message 3 ,打印位置为屏幕第一行中间。
2. 编写子程序func2,完成下列功能:
在代码段 code 中存放 3 个变量:当前小时数 hour 、当前分钟数 minute 、当前秒
数 second ,并存放一个输出缓冲区字符串 00:00:00 ,便于后续输出;
函数 func2 中首先调用 DOS 中断例程 int 21h 将当前时间的 时:分:秒 存放到 ch:cl:dh 寄 存器中,然后通过运算将时间值转化为字符写到缓冲区字符串 00:00:00 中,最后将字符串 打印到屏幕第一行右侧。
3. 修改int9中断例程,完成下列功能:
按下 1 键,执行 func1 子程序;
按下 2 键,执行 func2 子程序;
按下 3 键,将 dx 寄存器的值 – 1 ,便于后续的程序退出; 其他键照常处理。
4. 编写一个完整程序sysp.asm,包含上述3个部分的程序,并完成下列功能:
安装新的 int 9 中断例程,并将原来的 int 9 中断例程保存在数据段 data 中; 调用死循环子程序 work , work 中先将 dx 清零,然后进行循环判断,如果 dx 为零则进入循 环,否则退出子程序 work ;
最后恢复中断向量表中 int 9 中断例程的入口地址。
最后运行程序 sysp 的效果是:
按下 1 键在屏幕第一行中间显示字符串 this is message 1 ,后续按下 1 键在 3 个字符串之间循 环显示;
按下 2 键在屏幕第一行右侧显示当前时间 hh:mm:ss ,后续按下 2 键更新显示的时间;
按下 3 键退出程序 sysp 。
实验二:栈切换模拟”并发”流程 注:请在完全理解实验一的前提下做实验二
实验介绍:
实验一中的程序是在一个死循环中抽空执行其他代码,我们在实验二中对此情景做一个修改
有 A , B , C 三个函数,逻辑大体一样,都是死循环,然后每次循环递增一次计数器并打印,然后调
用 delay 函数延时一秒。现在要求,运行程序后,按下 A 键,执行 A 函数, A 计数器逐渐递增;按下 B 则是执行 B , B 对应的计数器递增; C 同样。程序机制大致同实验一,也是通过修改键盘中断例程完 成。
额外约束:三个函数用同一个寄存器作为计数器,更进一步除了打印位置不一样,三个函数用的寄存器
一致,不得错开使用不同的寄存器。
为了降低工作量,此实验给出了主干代码,如果觉得困难的话可以考虑参照助教给出的代码与注释进行
补全
实验分析:
我们知道在8086是单核的,也就是说在同一时间只能执行一条指令。那么问题来了,如果我们想要同时 运行多个指令序列,该怎么办呢?我们在之前的学习中了解过多进程并发的概念,我们可以通过快速地 切换正在执行的指令序列来达到目的。只要指令的切换速度足够快,那么人肉眼看起来就像是同时在执 行这几个指令序列一样。比如我们有三个执行序列 A , B , C
如果 cpu 按照序号依次执行:先执行语句A1,然后切换到B执行到B3,再切换到C执行到C2,再切回到 B3继续向下执行。只要我们的切换速度足够快,看起来我们就像同时在执行 A , B , C 三个指令序列一 样。实际上,咱们的电脑看起来能同时运行 PPT , WORD 等软件,某种程度上也是基于这个原理(不过现 在机器都是多 cpu 机器,多 cpu 机器可以真正实现并行)。上面我们都假设指令序列的切换是自动的, 不过在本实验中,我们偷个懒,采用键盘中断来进行切换。
有人可能会说,切换执行的序列还不简单吗?我直接用 call 调用不就完事了吗?
请设想,如果共用一个栈,当前在运行A函数,A call 了一个 print 函数,此时 sp 指向返回地址 (call压栈返回地址)了。此时指令序列执行切换到B函数,B也call了一个 print ,然后这时指令序列 又切换到了 A 运行,A应该接着运行 print ,那么 A 从 print 返回的地址是不是正确的?这个时候 A 相 当于是重新执行了,而不是接着上次的执行序列继续执行。
那么为什么呢?
请思考: 本实验能否只用一个栈?为什么?
问题一的答案应该很明显,肯定是不能只使用一个栈的。让我们进一步思考,从A如何切换到B,然后如 何切换回去
时间暂停在A被中断打断的那一瞬间,我们对这个时间点的寄存器快照一下,我们可以说,如果栈和相 关数据不发生改变的话,任何时刻修改寄存器使其和快照一样,那么我们就能回到A被打断的那一瞬 间。举例:普通中断例程,会保存用到的寄存器,然后结束后恢复寄存器,可以看作是上述的操作,使 得程序回到被打断之前。
因为寄存器只有一套,毫无疑问切换到B的时候我们需要保存A的寄存器,然后从某个地方载入B的寄存 器内容。重新运行A的时候,使用保存的寄存器值还原A的运行场景,即回到A被中断的那一瞬间,从而 恢复运行A,好像A没有被打断一样。
故我们需要保存3套寄存器,从而可以使得ABC可以任意的来回切换。但我们怎么知道要不要切换呢, 比如按下A的时候正在运行A,这时候是不必切换的,所以我们引入一个参数,记录当前正在运行的程序 (程序中的 Current 变量)。
具体分析中断处理例程的场景,首先根据按键,决定要不要切换。如果要切换的话,我们首先得保存一 下寄存器,即快照,我们叫它 save_all 。然后恢复目标函数的运行快照,切换的工作是用一个叫 switch的函数完成。不考虑细节的话,我们已经成功的做出来了这个程序,完美的符合要求。
现在来探讨一些细节的东⻄,从 save_all 开始,首先我们根据按键找到储存的地方(三个函数有三个 储存的地方),接下来就简单了,我们把寄存器挨个放进去就好了。
switch也很简单,无非是找到储存的地方,然后修改现有的寄存器就可以,这不就切换成功了么。
实验步骤:
实验分如下几步进行:
1. 补全init函数,该函数的作用是首先保存原来的9号中断例程地址,并且将键盘中断处理函数指 向自己编写的例程 int9 函数。
2. 模仿init_state函数中的initA,补全下面的initB,initC,并解释init_state的功能。
3. 根据实验分析中的描述与代码中的注释补全save_all函数,用以保存当前寄存器的快照。
4. 根据实验分析中的描述与代码中的注释补全switch函数,在switch函数中我们根据读到的键盘
按键值来决定切换到哪里执行,这里我们首先把 Current 变量根据我们读到的键盘输入值设置 好,之后切换到对应执行序列的栈,之后我们把之前 save_all 保留的所有寄存器的值全部弹出 来,这样仿佛我们就恢复到了切换前的样子,可以接着继续执行。
3. 实验提示
1. 在代码段中存放数据:《汇编语言》P248
2. 使用div指令进行除法和取余运算:《汇编语言》P169
3. 在屏幕上打印字符串可以直接往显存中写数据,也可以使用int21h的9号子程序(P261)。 4. int21h中2c号子程序获取系统时间:http://spike.scu.edu.au/~barry/interrupts.html#ah2c 5. int9中断例程的安装和恢复:《汇编语言》P276
4. 提交要求与提交方式 提交一个压缩包(里面有四个文件)
1. 实验一的代码
2. 实验二的代码
3. 对实验二中“能否使用一个栈,为什么?”问题的解答,以及实验二的整个程序的流程(可以用流程图
的方式表达)
4. 实验小组的组员名单与权重
提交时间:6月14日晚23:55之前,每个小组只需提交一份