美文网首页
指北 | 聊聊中断机制

指北 | 聊聊中断机制

作者: Monica2333 | 来源:发表于2020-05-17 14:15 被阅读0次

    什么是中断


    中断其实是一种“中断”事件,中断具体代表什么意思需要考虑它所处的上下文环境参照对象是谁。考虑事件,我们可以简单把中断抽象为这样一种模型:

    当我们分析某种中断事件时,我们需要搞清楚这四个对象:

    中断源

    • 中断源是谁
    • 中断源在什么条件下触发中断
    • 中断源如何触发

    中断信号

    • 信号具体指的是什么
    • 信号是否需要存储
    • 如何存储

    中断控制器

    • 中断信号的管理

    比如说中断源发送的信号是否屏蔽,信号是否可被中断处理器重复处理,信号的处理是否有优先级...

    中断处理器

    • 如何获取到信号
    • 拿到信号做什么样的操作
    • 处理完信号后做什么样的操作

    在实际的中断事件中,并不一定刚好有上面提到的这四类对象,可能更复杂可能更简单化。但是当我们考虑中断事件时,需要明确应该有类似功能的“对象”承担这这样的逻辑。

    下面我们主要围绕操作系统的中断机制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时,它太大了。我们把它分为Task2Task3,丢进线程池中处理。它们同样很大,我们把他们分别分为Task4Task5Task6Task7,同样丢进线程池中处理。

    如果此时我们想取消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

    [4].https://zhuanlan.zhihu.com/p/26524241

    相关文章

      网友评论

          本文标题:指北 | 聊聊中断机制

          本文链接:https://www.haomeiwen.com/subject/rrqsohtx.html