8086中断介绍

  任何一种CPU,都具备一种能力,可以在执行完当前正在执行的指令之后,检测到来自CPU内部或外部产生的特殊通知信息,并立即对所接收到的信息做出相应的处理。这类特殊的信息,被称作中断信息。

  顾名思义,中断指的是CPU不去正常执行接下来的指令,而是被中断,转而处理中断信息。

  中断信息的种类有很多,但却有着一些共同点,中断信息中都包含了中断信息的类型码,用于标识中断信息。8086的中断类型码是8位的,这代表着8086CPU最多可以处理256种不同的中断信息。

中断处理程序

  CPU接受到了中断信息后,需要进行相应的处理,处理逻辑依然是由开发人员编写程序来控制的,所编写的程序被称作中断处理程序。一般来说,需要编写不同的中断处理程序以应对不同的中断信息。

  要令CPU中止当前指令的执行,转而跳转执行中断处理程序,其原理依然是通过改变8086CPU中CS:IP的值,使之指向中断信息对应的中断处理程序。

  想要CPU令处理不同的中断信息时跳转到对应的中断处理程序,则必须要有一种机制将中断信息和中断处理程序建立关联。

中断向量表

  8086CPU的设计者提供了一种叫做中断向量表的结构,用于建立中断类型码和中断处理程序入口的关联关系。中断向量表,就是中断程序入口地址的一个列表,被保存在指定的内存地址中,便于CPU读取。

  每一个中断向量列表项(即中断处理程序入口地址)是32位的,占两个字的空间,其中高16位存放段地址,低16为存放偏移地址。中断向量表在8086CPU中的位置是固定的,位于0000:0000~0000:03ff这一特殊内存空间中(CPU会固定的到约定的内存处获取数据)。

  CPU在跳转中断处理程序时,以中断类型码*4+2字单元中的数据设置CS,中断类型码*4字单元中的数据设置IP,如此一来,便能正确的跳转对应的中断处理程序。

中断处理过程

  虽然已经说明了CPU是如何根据中断信息中的类型码跳转执行指定的中断处理程序。但实际上,在CPU执行完中断处理程序后,还需要能够返回中断处理之前的程序,并接着执行之前的程序。

  因此,CPU在跳转中断处理程序前,需要和跳转子程序时进行同样的操作,将当前的CS:IP压入栈中,以便中断处理程序返回时,恢复现场。

中断过程大致有以下步骤:

  1.从中断信息中取得中断类型码
  2.pushf 标志寄存器flag入栈(第三步中断过程会修改flag的值,因此需要保护当前程序下flag的内容,便于返回后还原)
  3.将标志寄存器中关于中断的TF、IF设置为0(TF=0,IF=0)
  4.CS入栈(push CS)
  5.IP入栈(push IP)
  6.以中断类型码*4+2的字单元中的数据设置CS,中断类型码*4的字单元中的数据设置IP(CS=N*4+2,IP=N*4)
  7.执行编写好的中断处理程序
  8.IP出栈
  9.CS出栈
  10.popf 标志寄存器flag出栈

  纵观整个中断处理过程,8086CPU的中断跳转指令将1/2/3/4/5/6整合为了一个由硬件自动执行的完整操作,用于跳转至中断处理程序,开发人员无法直接干预这一过程。而对于8/9/10三个步骤,8086汇编的设计者则提供了指令iret(interrupt return中断返回)指令,将中断返回时必须的出栈操作合而为一,简化了中断返回时的步骤,降低了出错的可能性。

  要想使用8086CPU提供的中断处理功能,有几点需要注意:

  1.将中断类型码对应的中断处理程序入口地址存放安装在中断向量表中正确的位置。

  2.中断处理程序和编写子程序类似,需要将用到的寄存器预先压入栈中保存,并在返回前按顺序弹出恢复,避免寄存器冲突。

  3.中断处理程序返回时,需要和中断跳转时的相关寄存器压栈顺序相反,将IP、CS、Flag按顺序出栈(使用iret指令),以跳转回中断处理前的程序,恢复现场。

中断向量表初始化

  中断向量表的建立和初始化工作是由 BIOS 在计算机启动时负责完成的。BIOS 为每个中断号填写入口地址,因为它不知道多数中断处理程序的位置,所以,一律它们指向一个相同的程序入口地址,在那里,只有一条指令:iret。 

  也就是说,当这些中断发生时,只做一件事,就是立即返回。当计算机启动后,操作系统和用户程序再根据自己的需要,来修改某些中断的入口地址,使它指向自己的程序代码

中断来源类型

  中断信息从来源上可以分为内中断外中断两种。

内中断

  内中断,便是指来自CPU内部的中断信息。

8086的内中断大致可以分为以下四种:

  1.除法错误(例如除数为0),中断类型码为0

  2.单步执行,中断类型码为1

  3.执行into指令,中断类型码为4

  4.执行int指令,指令的格式为int n(byte类型 idata立即数),n表示中断类型码。例如在DOS下,用于返回DOS操作系统的int 21h。

  内中断的存在允许开发人员在程序中主动地引发中断,控制CPU执行中断处理程序。

外中断

  CPU除了能够执行程序指令外,还能对所连接的外设实施控制,接收外设的输入数据、向外设输出数据(I/O)。

  以CPU获取外设的输入为例分析,CPU应该如何处理外设输入事件呢?键盘、鼠标或是触屏等用户操作;磁盘、网卡的输入都是随时随地可能发生的,CPU需要及时的获取到这一消息,并做出相应的处理。

  从CPU到外设这数据接受和发送的双方看来,对是否有数据需要读取的检测方式无非是推与拉两种。CPU可以不断的对所有的外设端口进行轮询,判断是否存在数据的输入,并进行处理(CPU主动拉取数据)。另一方面,外设也可以在有数据需要CPU处理时,主动的推送通知给CPU,CPU收到通知后再来读取对应外设的数据,可以避免大量无效的轮询(外设主动推送通知)。

  由于CPU连接的外设可能会很多,且大多数外设在一次轮询周期内可能并没有数据需要处理。周期性的轮询会一定程度上占用CPU资源,降低CPU的执行效率。因而在CPU硬件层面,由外设推送数据的方案效率更高,更有效。

  8086CPU提供了外中断这一机制,允许外设以外中断的形式通知CPU,与之交互。外中断可以分为两大类:可屏蔽中断、不可屏蔽中断

可屏蔽中断

  可屏蔽中断指的是CPU可以选择不响应的中断。8086CPU是否响应中断,是根据当前标志寄存器中IF的值判断,若IF=1,CPU在处理当前指令后立即对中断指令做出响应;若IF=0,则CPU不响应可屏蔽中断。

  几乎所有的外设引发的外中断,都是可屏蔽中断。典型的可屏蔽中断源的例子是打印机中断,CPU对打印机中断请求的响应可以快一些,也可以慢一些。在被屏蔽中断期间,打印机会反复的发送中断信号,这期间让打印机等待一会是可以接受的。

不可屏蔽中断

  不可屏蔽中断指的是CPU必须做出响应的中断。无论IF的值是多少,都必须在收到不可屏蔽中断后,立即做出相应。在8086CPU中,不可屏蔽中断的中断类型码固定为2。    

  典型的非屏蔽中断源的例子是CPU电源断电,一旦出现,必须立即无条件地响应。CPU电源掉电时真的会引发中断吗?

为什么进行中断处理时,需要先将IF、TF都设置为0?

  将IF设置为0的原因主要是因为8086CPU的设计者认为中断处理程序一般是不需要对其它中断做出响应的。

  因此默认的将IF设置为0,禁止CPU处理可屏蔽中断。当然,如果有的中断处理程序确实需要处理可屏蔽中断,也可以在中断处理程序中开中断,将IF重新设置为1。指令sti,设置IF=1;指令cti,设置IF=0。

  将TF单步调试功能关闭的原因则是为了避免出现单步中断的死循环。

  试想如果开启了单步调试功能,那么在进入中断处理程序,并执行完第一条指令后,便会引发单步中断,进而跳转到单步中断处理程序中。而在执行了单步中断处理程序的第一条指令后,又会再度引发单步中断,这成为了一个死循环。因此,在进入中断处理程序之前,必须首先把TF置为0,关闭单步调试功能,以避免上述情况产生。

总结

  学习8086汇编的主要目的是为了更好的学习操作系统,而中断这一概念在操作系统中是十分重要的,操作系统中许多功能都依赖硬件所提供的中断能力。

  例如,通过8086中断的学习,我想通了一个困扰我很久的问题:在单核CPU机器上,操作系统是如何从当前正在运行的应用程序手中夺回控制权,进行应用进程调度的?虽然之前了解过时钟中断这一概念,但是却一直没有彻底的将这些知识逻辑自洽的串联起来(好菜啊>_<)。

  我目前给出的回答是:这依赖于时钟外设周期性发出的时钟中断信号。当CPU正在运行应用程序时,时钟中断会使得CPU停止执行当前应用程序的指令,转而去执行时钟中断处理程序。而时钟中断处理程序由操作系统提供,因此此时CPU的控制权便又回到了操作系统程序手中。操作系统程序便可以按照一定的规则,对运行中并发的应用程序进行相应的调度。通过时钟中断这一机制,操作系统得以始终拥有CPU的主导权。

  尽管不同CPU硬件的结构不同,实现中断处理的细节也不尽相同,但一些核心的概念是不会发生太大变化的。理解了较为简单的8086CPU中断,能为理解更复杂的硬件及操作系统层面的中断机制打下一个好的基础。