CPU在同一时刻只能运行一个程序的指令,对于一个循环检测键盘输入的程序,如果总是循环检测等待键盘的输入,那就会浪费许多时间;CPU在运行一个程序的时候,突然出现了一些紧急的事件,CPU需要停下当前程序,转去处理紧急事件。因此需要一种机制能让CPU在工作时可控的停下,去处理其他事件。即中断机制。
场景1:

小明正在洗衣服,此时一个快递电话打了过来,通知小明立即去收快递,小明停下洗衣服,随后做了点简单的收拾,出去收快递。收完快递回家之后,小明继续洗衣服。

场景2:

小明正在写一份Word文档,一个电话来了,说突然有一篇报告要立马交,赶快马上的那种,小明一想,现在的Word不着急,于是转去写这份报告,写着写着,又一个电话打过来,说明天交一份xx报告,这时候小明一想,这个报告明天交不急,继续写这份报告。

描述的这些场景就是典型的中断场景,CPU中也要能够有这么一个功能。
从这些场景中或多或少可以知道,一个理想的,符合现实生活的中断机制,有如下特点:

  • CPU需要中断信号来判断中断发生的
  • 中断处理完成之后,CPU可以返回来在被中断时的位置继续执行
  • 收到中断信号后,可以知道是谁发起的中断,这样CPU就能找到对应的中断处理程序
  • 对于多个中断信号,处理是有优先级的
  • 可以在某段特定时间内允许/禁止中断
  • 中断可以嵌套

    CPU判断中断的发生

    一种合适的解决方案是总线上设置一个特殊的中断请求控制线,当中断发生时,请求中断的设备就在中断请求线上持续发送中断信号直到中断被处理,中断寄存器中的中断信号位被设置为1。CPU每执行完一条指令,都会检查中断寄存器,若有中断信号,就进入中断处理流程。
    image.png
    一台计算机中可以有多个设备向CPU发出中断信号。但是不可能每增加一个外设,就增加一个中断请求线。所以多个设备共用请求线的设计更为合理。
    image.png

    现场保存/恢复

    中断需要从一个程序的运行转到另外一个程序运行,且在运行结束之后,还要使原程序能正常运行,而不受除延迟之外中断的任何影响。
    因此至少需要对原程序执行的CPU现场进行保存,即CPU内各种寄存器,运算器的值。称为保存现场
    这些现场信息必须在被中断程序重新开始执行时重新装入。现场信息一般包括状态码标志,寄存器内容等
    保存和恢复现场的工作可以由CPU自动完成也可以由程序指令完成。

  • CPU自动完成指检测到中断之后,CPU内部电路会自动触发的工作。

  • 程序指令完成则是等待CPU跳转到相应的中断处理程序之后,由中断处理程序给出指令

CPU一般只保存程序完整执行的最小信息量,以减少与存储器的交互,降低执行开销,降低CPU接收中断请求到开始执行中断处理程序的延迟(这个延迟是中断等待)。通常CPU保存程序计数器PC和状态寄存器的内容。而其他寄存器的内容交给中断处理程序保存。
另外一种方案,能够继续执行的充分条件是,所有一切都是原样,寄存器的内容不被破坏。那么只需要不使用原来使用的寄存器,当被中断时,使用一组未被使用的寄存器,用于中断处理,处理结束之后,直接转回去使用原寄存器就好了。
image.png
如上图,正常执行时使用寄存器组A,在中断发生时,后续使用寄存器组B,中断处理完成时转回寄存器组A即可继续执行。

CPU判断发起中断请求的设备

在早期,外设的状态寄存器中配一个状态位,称为IRQ,当外设发送中断请求后,其IRQ位就为1,当CPU收到中断请求后,就对与总线相连的I/O设备进行测试。测试到的第一个IRQ位为1的设备就是发送设备。这种轮询测试IRQ的方式容易实现,但是却对没有发送任何中断请求的设备也同样进行检测,会浪费不少时间。
结合上面实际的事例,更好的解决方案是:既然要让CPU知道是哪个设备发送的,直接告诉CPU我是谁就好了。设备在发起中断请求的时候,请求中断的设备向CPU标明自己,这样CPU就不必花费时间去检查是哪个设备发起的中断请求,CPU就可以直接去执行相应的中断处理程序。向量中断指的就是所有基于这种方法的中断处理方式。
请求中断的设备可以通过总线发送专门的标识码来标识自己。这个标识码除了可以标识自己,同样也可以包含其他信息,它可以提供一部分地址信息包含在标识码中,CPU就可以根据这部分信息获得完整地址。这种工作方式要求给定设备的地址总是在同一个地方开始执行。不过可以在该位置放置一条指令,由该指令跳转到相应的中断处理程序(往往这是中断处理机制自动完成的)。
中断设备指向的位置用来存储中断服务程序的起始地址,处理器读取该地址称为
中断向量(指地址,也可包含其他信息),并且装入PC寄存器中。
中断向量一般在数据总线上传输,使用总线控制信号避免相互干扰。发送中断请求时,CPU需要执行完当前指令才能开始处理中断,这条指令可能使用总线,因此如果在发送中断请求时发送中断向量,可能干扰指令正常执行。
因此合适的处理顺序是:
设备发送中断请求->CPU处理完当前指令->CPU给设备返回
中断确认**信号(通过中断确认线(INTA)发送)->
设备发送中断向量,并且撤销中断请求信号->CPU处理中断

中断允许/禁止

在某一个特殊的时间段内,中断应该是能够允许或禁止的。
中断信号是随时都可能出现,但是CPU开始处理中断之后进行的保存现场,进行中断确认等操作的时间并不是瞬时的,再处理这些中断处理的必要步骤的时候,如果发生新的中断,也应该保证这些必要步骤会执行完的,也就是要实现中断允许/禁止。
image.png
硬件上的一种实现方式是寄存器中设置一个中断允许状态位,当CPU执行完一条指令的时候,先去检查中断允许状态位查看是否允许中断,不允许直接进入下一个指令执行阶段,允许则继续检查中断信号状态位,查看是否有中断发生。这种方式可以使得CPU中断变得可控,从而更符合实际需求。
在CPU开始中断处理之后,中断允许状态位就置为禁止中断的0。进行必要处理之后,将中断允许位置为允许中断的1。

中断嵌套与优先级

很通常的一种情况是在处理中断的过程中,新的中断发生了,这时候如果新的中断事件十分重要,是应该可以被处理的,也就是优先级高的中断,可以中断优先级更低的中断。
要进行优先级的比对,有一点必须实现,CPU要知道当前中断的优先级,这个可以采用寄存器的方式,寄存器中可以使用几位用于存储当前优先级。即可以认为CPU有一个优先级,是当前运行进程的优先级。当中断优先级高于CPU优先级的时候,可以中断CPU的进程转而处理中断。
多优先级可以通过使用单独的中断请求和确认线实现,然后通过优先级仲裁判断是否处理该中断。
image.png
这和前面提到的共用中断请求/确认线是不太一样的。这同样面临一个问题就是中断请求和确认线是有限的。
合理的解决方案是同优先级的设备共用中断请求/确认线,即一个优先级对应一个中断请求/确认线。
但是会产生一个新的问题,对于同优先级的中断,多个中断请求同时产生,如何响应?
一个广泛使用的解决方案是构成菊花链的结构
image.png
当有CPU收到请求后,CPU直接通过确认线返回确认信号给设备1,如果设备1没有发送中断请求,就转发中断确认给设备2;如果设备1发送了中断请求,则截断确认信号,不转发给设备2。
这就解决了同优先级的多个中断处理。结合起来就是
image.png

控制设备请求

当I/O设备接口准备一次传送时,只产生一个中断请求。有一点很中重要,确保I/O的中断只能由被进程使用的设备。因此,对于I/O设备,除了CPU判断是否响应中断,还需要I/O设备自己自觉,在自己没被使用的时候,不产生中断请求。即使已经准备好参与I/O传送。
这个功能在设备接口的控制电路处实现一个中断允许位。也就是说在CPU端通过优先级结构判断是否处理中断,在设备接口处还有一个中断允许位,用于阻止不必要的中断请求。

异常

指一个进程执行中止,另外一个进程开始执行的事件。包括中断。可理解为任何引起中断的事件。简单介绍三类异常

  • 错误恢复

计算机使用大量的技术确保所有的硬件都能正确工作。但是仍然会因为各种原因出现不可避免的错误,因此对于这些错误也需要进行异常处理。当遇到错误时,程序根据情况进行处理,尽可能恢复错误造成的影响。无法处理的错误也报告给用户。

  • 调试

系统软件通常包括一个调试器程序。提供两个功能,跟踪和断点。当CPU运行在跟踪模式下,每个指令完成都产生一次异常,对应的模式即为调试服务程序,接着做后面的处理,使得用户可以查看寄存器,存储器等内容。调试程序处理完毕之后接着处理下一条指令。注意,调试服务程序运行期间无法跟踪。
断点模式,被调试程序在用户标记的断点处产生异常,后面提供与跟踪类似的功能,通常用陷阱和软件中断实现断点调试。

  • 特权异常

CPU在用户态执行特权指令产生的异常。

其他中断

前面讨论的只是I/O设备与CPU交互的中断,还有多种中断。
时钟中断:CPU内有定时器装置,定时器可以在设置每隔一段时间或固定时间后产生一个中断。这个功能是操作系统实现进程时间片的关键
软中断:在软件层面实现的中断。
硬件失效中断:如掉电,存储器奇偶校验出错引发的中断