806-基于中断的功能调用
# 806-基于中断的功能调用
那现在,我再执行这些运算之后, 也就是执行这些程序的过程中呢,又遇到了一个奇怪的现象。
这里突然有一个地方写着,请查看紧急操作手册的第二百项, 那我就翻到这个地方去了,翻到前面,然后找第二百项, 然后找到了对应的操作,这个呢,是让我在这个本子的某一个地方写一个数,这是怎么回事呢? 一般来说, 刚才我们学到的,都是我在运算当中,遇到了异常的情况, 然后我主动去查找前面的,对应的异常处理的方法,查找中断向量表。 那么现在在这个程序当中,居然主动的写到了, 让我去查找相关的中断项量表,这个是怎么回事情呢? 我们就来看看这个问题
在 x86 指令系统当中,其实还提供了一条中断指令, 它的格式是 INT,加上一个操作数 n, 用这条指令,就可以直接调用对应的中断服务程序, 这个 n 是 0 到 255 当中的一个数,对应中断类型码, 当 CPU 执行到指令时,首先会将标志寄存器压栈, 然后清除 IF 和 TF 两个标志位,也就是关中断。 再将 CS 和 IP 两个寄存器的内容压栈, 然后根据指令中提供的类型码,也就是这个 n, 去查找中断向量表,找到对应的中断服务程序的入口地址。 再将入口地址装到 CS 和 IP 寄存器当中去, 这样下一步 CPU 就会到中断服务程序的入口取出下一条指令,继续执行。 那么看到这一个步骤,和我们之前介绍的,CPU 处理中断的过程是一样的, 但是区别在于,这是由运行指令主动触发的。
比如,之前 CPU 当中,如果发生的 1 号中断, CPU 就会来中断向量表当中,取出 1 号中断向量, 然后转向对应的中断服务程序开始执行, 但如果当前 CPU 执行程序的过程当中,并没有满足触发 1 号中断的条件, 而是直接写了一条指令,INT 1 CPU 也会到中断向量表当中,取出 1 号中断向量, 装入 CS,IP 寄存器,再转到 1 号中断服务程序,开始执行。 那这样做有什么意义呢?明明没有发生中断,我们为什么要调用这个中断服务程序
那这种中断指令的使用场景其实有两类, 一类就是 CPU 的一些专用的中断,就是需要通过调用指令的方式来实现的, 比如说 3 号中断,那就得写 INT 3 这样的指令,才可以产生。 这种情况我们在之前已经介绍过了
现在我们来看另外一种情况。 比如说有一类,我们称为 BIOS 中断。BIOS 就是基本输入输出系统, 这是一套不大但是还挺复杂的程序, 存放在 ROM 当中, 寄存器在刚通电或者复位时,CPU 就会从 BIOS ROM 当中取出第一条指令开始执行, 所以在 BIOS 当中会提供系统加电自检,和主要的输入输出设备处理程序等功能模块。
所谓的功能模块,也就是一个一个具有独立功能的程序, 比如从键盘接收一个字符,或是在显示器上显示一个字符, 这些工作不但在系统初始化时需要使用,在后来用户使用计算机的过程中,其实也会用到, 而如果要用户自己去编写,处理这些输入输出设备的程序,那就太麻烦了, 所以 BIOS 的设计者,就将其中的这些功能各个包装起来,形成了很多个独立的功能模块, 然后将这些独立功能模块的入口地址,放在中断的向量表当中, 那如果我们想使用这些功能,比如说就像在显示器上显示一个字符, 用户就不用去关心,到底用的是什么显示器,要占用多少个像素等等, 那只需要使用 BOIS 提供的功能模块,用 INT 指令调用对应的中断服务程序就可以了。 而且我们可能希望,向这些功能模块传递一些参数,那就可以通过寄存器进行传递。
我们不妨来看一个例子,这个表是 BIOS 中断的一个片段, 那想使用 BOIS 中断,首先就得查找 BIOS 中断的手册,这个手册 一般会提供这样一个表格,列出了 BIOS 这些功能模块所在用的中断号, 比如说 10H,就是用于在显示器上进行显示的一个中断服务程序, 而 1AH,则是设置系统时钟的一个中断服务程序。
我们以 1AH 为例,那如果我们想要改变现在的系统时钟,当然我们可以去分析时钟管理芯片的功能,通过查找它的手册,来分析如何去改变系统时钟的设置,这可能要花很多个步骤。 那 BIOS 的设计者,就帮我们封装好了这个功能, 我们只要这样写代码就可以了。 因为我们通过这个表可以看到,如果要设置时钟,我们需要提供一个功能号为 1, 因为 1AH 这个中断里头,其实有多种功能, 我们可能想读出当前时钟的值,也可能要改变当前时钟的值, 这个中断服务程序怎么识别呢,它就要求你在 AH 寄存器当中放入一个数, 那么在中断服务程序的开始,会先检查 AH 寄存器,如果里面是 0,那它就按照读 时钟的操作,运行后续的代码,如果 AH 里面的值是 1,它就按照设置时钟的操作,执行后续的代码, 那现在我们要设置时钟,所以在 AH 里面先放上 1, 然后我们查这张表,知道我们要设置的时间是放在这几个寄存器当中,CH 放在要设置的小时数,CL 放要上要设的分钟数,DH 是秒,DL 是百分之一秒, 而 CL 和 CH 组成的寄存器是 CX,DL 和 DH 组成的寄存器是 DX, 所以我们直接可以通过对 CX 和 DX 赋值,来设置这个时间
那么现在为了简单,我们就设成 0 点 0 分 0 秒, 这些参数准备好以后,我们最后写 INT 1AH, 接下来就像是之前介绍过,发生中断的时候一样, CPU 会去中断向量表当中,找到 1AH 对应的中断向量,然后转移到对应的 中断服务程序开始执行,而这段中断服务程序就是位于 BIOS 所在的存储区域, 那在这个中断服务程序当中,就会去操作管理系统时钟的芯片或者部件,完成时钟的更改, 然后再返回到这个主程序当中,继续执行下面的代码。
那这样就把一个和底层硬件细节非常相关的操作给封装起来,让编程人员可以比较轻松的完成这样的工作。 那 BIOS 中断其实提供了很多种类型, 可以完成相当丰富的功能,有兴趣的话,可以查找相关 BIOS 的手册。
但是因为 BIOS 容量有限, 因此我们还是可以利用这个方法,在上层的软件当中,提供更为丰富的功能, 那我们来介绍一个例子,就是 DOS 中断, DOS 早期的一种操作系统,它占用了一个中断类型号,21H, 和 BIOS 占据了多个中断类型号不同,DOS 中断只有这一个类型号, 但它的功能非常的丰富,常用的文件管理、 存储管理等很复杂的功能,都可以种这个中断服务程序来解决
那它怎么区分我们到底想使用哪个功能呢?其实刚才 BOIS 中断给我们提供了这样的思路, 我们可以通过一个寄存器,传入一个参数,来告诉中断服务程序,我们到底想调用那样的功能, 所以所有的 DOS 中断都只使用这一个中断入口, 而且 DOS 是一个操作系统, 它所提供的中断功能,比 BIOS 中断更为齐全、完整, 而且进一步品比了设备的物理特性,让编程的使用,变得更加的方便。
我们也来看一个例子,如果我们想在屏幕上输出一个$字符,那我们可以查找 DOS 中断所提供的表格, DOS 中断都是 21H,所以这个表里面只需要列出功能号, 那么发现,6 号功能是在进行输入输出的操作,所以我们现在 AH 寄存器当中,存入 6,然后我们进一步发现, 如果我们想输出一个字符,就在 DL 寄存器当中,放入我们想显示的这个字符,而如果我们想通过键盘输入这个字符,则只需要在 DL 寄存器当中,存入 FF, 而最后输入的字符,会放在 AL 寄存器当中。
那我们现在还是来看输出, 所以我们在 DL 寄存器当中,存上这个字符, 然后调用 INT 21H, 这样 CPU 就会转向 21H 号中断所对应的中断服务程序, 在这个服务程序当中,首先会检查 AH 里面的值,确定功能号, 然后就进入到这个功能对应的程序代码段, 再根据 DL 寄存器的内容,判断出这是一次输出,那这个服务程序接下来就会对显示器进行操作, 让对应的字符显示在屏幕的合适的位置, 那这些繁琐的工作,都不需要用户来关心了, 只要简单的调用这个 DOS 中断就可以了。
当然前提是,这些操作都已经由其它的程序员帮你写好, 并且封装起来,还安装在了你这个电脑上,这样你才可以使用, 归根到底,这些操作都还是要有人来写出程序, 只不过一次写好之后,其他的用户就可以直接调用了, 这样会很方便,所以说无论是 BIOS 中断,还是 DOS 中断,或者是其它类似的中断方式, 其本质并不是计算机运行当中发生了异常的情况, 而是利用了现有的中断这种机制,来实现一些系统函数, 代码的调用,以便向高层的软件屏蔽底层硬件的细节, 从而提高编程的便利性,正确性,和可移植性。
那现在,我们的机制已经讲的很清除了, 在程序执行的过程中,遇到的任何异常情况,我们都可以自如的处理, 而且还可以利用这个机制,完成更加丰富的功能, 所以可以说,我们对任何情况,都已经是能够应付裕如了。