什么是中断
中断其实是一种“中断”事件,中断具体代表什么意思需要考虑它所处的上下文环境
和参照对象
是谁。考虑事件,我们可以简单把中断抽象为这样一种模型:
当我们分析某种中断事件时,我们需要搞清楚这四个对象:
中断源
- 中断源是谁
- 中断源在什么条件下触发中断
- 中断源如何触发
中断信号
- 信号具体指的是什么
- 信号是否需要存储
- 如何存储
中断控制器
- 中断信号的管理
比如说中断源发送的信号是否屏蔽,信号是否可被中断处理器重复处理,信号的处理是否有优先级...
中断处理器
- 如何获取到信号
- 拿到信号做什么样的操作
- 处理完信号后做什么样的操作
在实际的中断事件中,并不一定刚好有上面提到的这四类对象,可能更复杂可能更简单化。但是当我们考虑中断事件时,需要明确应该有类似功能的“对象”承担这这样的逻辑。
下面我们主要围绕操作系统的中断机制
,Java的中断机制
,如何设计一个异步线程间的中断系统
这三部分简单探讨下。
操作系统的中断机制
与操作系统有关的中断,通常是指:程序在执行过程中,遇到急需处理的事件时,暂时中止CPU上现行程序的运行, 转去执行相应的事件处理程序,待处理完成 后再返回原程序被中断处或调度其他程序执行的过程。
按照中断事件本身的不同,可以划分为处理器之外的中断事件
,异常
,系统异常
。
处理器之外的中断事件
指由外围设备发出的信号引起的,与当前运行指令无关的中断事件。示意图如下:
我们分别以上述四个对象来看:
- 中断源
中断源:外部设备,如打印机,键盘,鼠标等。
触发条件:如外围设备报告I/O状态的I/O中断;外围设备发出的对应信号中断,如时钟中断,键盘/鼠标对应信号的中断,关机/重启动中断等。
触发方式:由外部设备向中断控制器发出中断请求IRQ。
- 中断信号
也就是说中断源通知给中断控制器的是什么。
可以是通过一条信号线上产生特定的电平(利用高低电平表示是否中断两种状态),也可以在总线上发送特定消息或者消息序列,也可以是在中断寄存器中设置已发生的中断状态等。
- 中断控制器
CPU中的一个控制部件,包括 中断控制逻辑线路和中断寄存器。负责中断的发现和响应。
也就是说负责检查中断寄存器中的中断信号,当发现中断时让CPU切换当前进程程序,去处理中断程序。响应示意图如下:
- 中断处理器
指的是CPU接收到不同的中断信号该怎么处理。包括“中断处理过程”和“恢复正常操作”两部分。
1.中断处理过程
首先CPU需要将当前运行进程的上下文保存,从中断进程中分析PSW,确定对应的中断源和执行对应的中断处理程序。
小贴士
:PSW(Program Status Word): 是指在电脑中,一段包含被操作系统使用的程序状态信息的内存或硬件区域。一般用一个专门的寄存器来指示处理器状态。可以理解为我们上面提到的中断信号存储装置.
2.恢复正常操作
当中断程序执行完毕,接下来执行哪个进程由进程调度决定,由调度策略决定是否调度到中断执行前的进程。
较为完整的中断响应流程图如下:
异常 和 系统异常 这两类中断事件主要属于处理器执行特定的指令引起的中断事件。和上述硬件外围设备引起的中断事件的中断源不同,中断的发起,控制和处理主要是由操作系统的指令逻辑和线路来承担。是一种同步的处理操作,而外部中断是由外部设备发起,是一种异步的处理操作。下面我们简要介绍下。
异常
异常指当前运行指令引起的中断事件。包括错误情况引起的故障,如除零算数错误,缺页异常;也包括不可恢复的致命错误导致的终止,通常是一些硬件错误。
- 异常的处理
对于故障
的处理,根据故障是否能够被恢复,故障处理程序要么重新执行引起故障的指令,要么终止。
对于终止
的处理,处理程序将控制返回给一个abort例程,该例程会终止这个应用程序。
系统异常
系统异常指执行陷入指令而触发系统调用引起的中断事件,如请求设备、请求I/O、创建进程等。
- 系统调用的处理
这种有意的异常,称为陷阱处理。处理完成后陷阱程序会将控制返回给应用程序控制流的下一条指令。
总结一下,操作系统的中断类别行为如下:
好了,大头总算完了。因为小姐姐主要是Java码农,下面将主要介绍和Java相关的中断语义是什么。
Java的中断机制
理解了上面操作系统的中断之后,Java的中断机制就很easy了 :D
Java中断指的是A线程发送中断信号给B线程,B线程再根据自己当前执行程序中的中断处理逻辑决定如何响应。嗯,就这么简单~
我们来稍微分析一下中断事件中的“四个对象”:
- 中断源
中断源:A线程
中断触发条件:A线程说了算
中断源触发方式:A线程中调用threadB#interrupt()
方法.
实现机制也不难,扯淡之前我们先思考两个问题:
问:
问题1
: 线程之间如何通信,A线程的中断信号怎么才能传给线程B?
问题2
: 线程的状态有Running,Blocked,Waiting等,当线程B处在不同的状态下,如何响应中断信号?
答:
问题1
:这种情况下线程之间通信用共享内存就可以了。只需要给每个线程都设置一个中断标示位, 这样A线程中调用threadB#interrupt()
方法,实际操作是把B线程的中断标示位设置为true。信号就算传递过去了
问题2
:当B线程处于非阻塞状态时,B线程可以在自己需要处理中断逻辑的地方判断中断标示位是否为true,就可以响应处理中断。
但是当B线程处于阻塞状态时,这特么怎么查自己的中断标示位啊?
JVM帮帮忙,当B线程阻塞在Object#wait()
,Thread#join()
,Thread#sleep()
,实现了InterruptibleChannel
接口的IO操作 和Selector
接口的select()
这些操作时,JVM会让B线程马上抛出异常或被唤醒,从而让B线程可以选择是否响应中断。
因为是Java实现的中断机制,中断标示位的设置也是JVM帮做的。
- 中断信号
信号:线程的中断标示位。
存储方式:JVM说了算。
- 中断控制器
JVM控制了信号的存储和让线程B及时唤醒。
线程B控制了自己的中断响应逻辑,何时响应,如何响应。
- 中断处理器
获取信号:B线程可通过调用threadB#isInterrupted()
方法得知自己是否被中断,也就是通过自己主动拉取信号(poll方式)。
如何处理信号:B线程说了算。
处理完信号后做什么:B线程说了算。
Java的线程中断机制设计的比较灵活,使用者可以决定中断处理的较多事情。
总结下Java中和中断有关的方法:
在JDK中,线程池的ThreadPoolExecutor#shutdownNow()
方法就是调用workers线程数组中每个worker线程的interrupt()
方法来关闭线程池。
这样暴力关闭线程会存在一个问题,线程池并不知道worker线程的中断执行情况,如果worker线程忽略了中断信号,那可能导致当前任务还在执行,发生意想不到的结果。
设计一个异步线程间的中断系统
我们再来看Java的中断机制,它其实只是提供了A线程给B线程发送中断信号。
- A线程并不能知道B线程的中断处理结果。
- 如果A线程拿不到B线程的thread对象时,也就没法发送中断信号。
考虑这么一种场景:
当我们执行一个大任务Task1
时,它太大了。我们把它分为Task2
和Task3
,丢进线程池中处理。它们同样很大,我们把他们分别分为Task4
,Task5
和Task6
,Task7
,同样丢进线程池中处理。
如果此时我们想取消task1的执行,如何保证图中所有的worker都成功取消对应task的执行?
- 需求分析
当我们取消task1时,想要做的是取消所有task程序的继续运行
,并且能够获得所有task程序的取消结果
。
为什么要强调task程序呢?因为worker可能并不是只为一个task工作啊..比如task2的worker,它把task4和task5丢进线程池,就算完事了。如果我们把取消task1变为取消task1的worker线程,可能会导致worker线程当前运行的非task1程序的失败。
我们不太容易知道所有task程序当前运行的线程,我们还需要知道所有task程序的运行结果。
- 设计思路
只用Java的中断机制是满足不了我们的需求的,但是我们可以借鉴它的思路:
1.它用中断标示位记录线程是否应该中断
2.当线程阻塞时可以抛出异常
我们这里要终止的是所有task程序的执行,所以我们需要设计与task 强绑定的中断标示位
,可以有未中断
,中断中
,中断成功
,中断失败
四种 状态。为了让所有的线程都可以访问到,定义成全局共享变量就可以。
中断源和中断处理器之间通过task的中断标示位来通信就可以。如果运行task程序的线程一直在阻塞,怎么唤醒它让它判断中断状态 呢?
对于我们这个场景,我们很难知道当前运行task程序的阻塞线程是谁。。能做的只是多安插中断判断点,这样当阻塞线程醒来后,再次判断task 的中断标示位,就可以响应中断了。
另:
唤醒一个线程只有Java的中断机制可以做,但是如果当前worker不是你能管理的线程池,那么它的中断处理逻辑就控制不了。
如果你能控制运行task的所有worker,而且worker在执行task时是同步获得结果的。那么可以结合与task强绑定的中断标示位
和Java中断机制
来做,这里前者的作用更多是充当获取到任务的中断结果的作用。
后记
小姐姐觉得像是“事件处理”这种场景在线程池,消息中间件,流式处理等很多地方有共通之处,比如说:如何保证事件的exactly once,推拉模型,调度等等。
在写这篇文章时,特别是操作系统的中断机制,小姐姐也是现学现卖,并且参考了资料大部分内容。文章中有理解错误或者难懂的地方还请小伙伴帮我指出,一起交流进步。
最后的技术部分讨论“如何设计一个异步线程间的中断系统”,这是小姐姐目前工作中遇到的一个问题。这个问题和任务调度组件的取消任务很相似,只是我们目前还没有用任务调度组件管理起所有的任务工作线程。小伙伴有更好方案的也请告知小姐姐。
欢迎关注我的公众号:「码农知识点」,和我交流讨论~
参考资料:
[1].https://www.icourse163.org/course/NJU-1001571004
[2].《深入理解计算机系统》
[3].https://www.zhihu.com/question/47862508/answer/110694813