课程设计二
编写一个可以自行启动计算机,
不需要在现有操作系统环境中运行的程序! 答:
分析:
在编写该程序前,请仔细阅读相关的材料(书中的302页)。由于在实验十六中我们已经知道用直接定址表实现对多个子程序的控制,所以我们现在
只要分析各个子功能的实现即可,之后再考虑安装任务程序的相关问题。
一,实现各个功能子程序:
1,reset pc
根据材料,我们只需要修改cs:ip指向FFFF:0000。这样我们需要解决的问题是如何修改cs,ip
总结前面学过的知识,我们不难发现如下指令都可以修改相关的cs、ip或者同时修改cs和ip:
jmp call ret retf iret
在给出源程序前,请仔细比较分析,哪种方法更简便易行。
2,start system
根据材料,我们可以通过两步实现:
1) 读取硬盘c的0道0面1扇区的内容到0:7c00h,这在实验十七应该弄得非常清楚了
2) 修改cs:ip指向0:7c00h,而这和上一个子功能的操作类似
********这里涉及到一个非常重要的问题,将在后面进行分析*******************************
3,clock
这里要求动态显示系统当前时间。我们会很容易想到实验十四中我们就已经写过显示系统当前时间的子程序,仔细一想,这里只多了一个“动态”:
我们可以通过循环读区系统时间实现
另外它要求我们能够通过按下F1改变显示颜色,按下ESC返回主选单,这在实验十五中已经涉及到键盘操作的问题。那么我们可以通过写自己
的键盘中断处理程序实现。当然还有更简单的办法,就是直接读键盘端口60h,注意:读取的是扫描码。
4,set clock
设置时间,这本质上是显示时间的逆过程:“显示时间”要求我们从CMOS RAM中读取时间信息并显示到屏幕上,“设置时间”要求我们输入时间,显
示到屏幕上并且写入到CMOS RAM中保存起来。这里也许可以让你很快联想到十七章中关于字符串的输入的讨论,但是书中的程序比较麻烦,不过思想值得借鉴。
1) 我们可以先进行字符串的输入,但是不需要设置额外的内存存放输入的字符,而可以直接输入到显存中显示到屏幕上,这就是我们简化的所在。
2) 把在屏幕上的数字字符位置与CMOS的存储单元对应起来,当按下“enter”键后把屏幕上输入的数字字符转换成BCD码后写入到对应的CMOS存储单
元中。
二,在根据上面的分析以后我们完全可以写出传统的任务程序,下面我们来讨论如何安装任务程序,以及在讨论之后对上述任务程序中的一些东西进
行改进。
1)编写安装程序
我们首先得编写好安装程序,我们知道安装程序的功能:把任务程序写到软盘上,我想这个操作在实验十七中我们已经掌握了。
为增强交互,在写操作的时候,我们可以对返回参数ah和al进行判断,看是否写成功。(具体见书中299页的返回参数)
2)对任务程序编写的深入思考:
a,通过以前对中断例程的编写,我们不难发现可以将“安装程序”和“任务程序”都将放在代码段中。如果这样,“任务程序”中的非代码数据也
得写到代码段中,这里的相关设计方法同实验十二中对“divide error!”的处理。
b,根据书中给出的建议(3),我们的任务程序的设计上必须考虑到:当任务程序的长度大于512个字节时,必须在一扇区中用一个程序段负责将其他扇
区中的内容读入内存。而对磁盘的读取操作在实验十七中已经掌握拉。
c,在编写任务程序的时候,要千万注意,“绝对地址”和“相对地址”的区别。要注意,我们的任务程序安装到软盘以后,已经与安装程序没有了
关联,如果我们直接用offset指令取得某些标号的偏移地址,在软盘中将变成一个不可识别的地址,导致程序出错,因此我们必须使用“绝对地址”,也就是
说我们在处理一些标号等问题时,必须明确每个地址是“显性的”,而不是模糊不清的。为了实现“显性”,那么我们可以用内存地址0:7c00h作为基址进行
相关的操作。
另外注意一点,依据位移进行的jmp指令等是允许的,因为即使绝对位置我们不知道,但是在已经知道某个绝对位置之后,与它相对的位置也就固定
拉,同时,offset A-offset B也是允许使用的,原理同上。
实现: 根据以上分析,我们写出大概的设计步骤:
1,设计各个子功能程序,并单独进行调试,注意:子功能1、2、4必须在实模式(书中的附注中详细谈到)下进行调试。
2,根据实验十六组装各个子功能程序,当然组装的时候要充分考虑上面分析到的问题
3,在任务程序的最前面(也就是要安装到软盘的1扇区位置的数据)编写“负责将其他扇区中的内容读入内存”的程序段,下面叫这段程序段为
“搬运工”。而我们的主任务程序叫“主程序”。
4,编写安装程序,组装整个程序
5,在dos下对整个程序进行汇编、连接,得到可执行文件,之后插入软盘,运行即可
6,插入软盘,重新启动计算机(之前需要修改bios设置,让机子从软盘启动)
下面给出源代码:kcsj2.asm kcsj22.asm
【注意:子功能程序二中涉及到的一个严重问题:
我们知道子功能程序二实现的第一个步骤中,首先要求把硬盘c的0道0面1扇区的内容读取到0:7c00h。这就是问题的所在。
下面我们就此进行分析:
我们进行到“实现”中的第六步之后,计算机将从我们的软盘启动,把我们的任务程序写到0:7c00开始的内存中,如果“搬运工”把“主程序”, 也读入到它之后的程序段中,那么我们的子功能程序二也有可能处于从0:7c00开始的512个字节的内存中。
之后,当我们选择功能2,那么程序将转到我们的子功能程序开始执行:
第一步:它把硬盘c的0道0面1扇区的内容读取到0:7c00h开始的512个字节中。
第二步:我们也许会理所当然的认为:即把cs:ip指向0:7c00h,但是这不一定实现,原因就在上面标有红色的部分,这样会使得c盘中读入的内容覆盖
掉我们原来在0:7c00h开始的512个字节中的任务程序,第二步的跳转操作也可能在其中而被覆盖掉,因此,这个跳转指令将执行不到,而是指令512个字节之后
的某一个并不是我们期待的语句,从而得到不可预测的结果。
所以我们必须想办法排除可能的覆盖:最根本的是让我们的功能子程序二不至于被读取到0:7c00h开始的前512个字节内存处。
我们的实现方法是,在安排任务的各个功能子程序的时候,把它安排在整个任务程序开始的512个字节后面,也可以干脆把“主程序”安装在二扇区以后,
再通过“搬运工”把它搬到0:7e00h开始的控制处,并且在“搬运工”中实现对cs:ip的修改,即使得在执行完它自己之后,把cs:ip指向0:7e00h处,从而运
行我们的任务程序。
kcsj2.asm和kcsj22.asm分别就这两种解决办法进行了不同的实现。前者虽然在操作伤多了一个安装过程和一个修改cs:ip的过程,但是结构上更好看,而
且不要去刻意计算字节数;但是后者却减少了一些代码。 】