美文网首页
Interrupt 线程与OS

Interrupt 线程与OS

作者: jiangmo | 来源:发表于2018-06-14 11:53 被阅读38次

    基本思想

    一个线程不应该由其他线程来强制中断或停止,而是应该由线程自己自行停止。 中断,只是一个协作通知信号量。好比是家里的父母叮嘱在外的子女要注意身体,但子女是否注意身体,怎么注意身体则完全取决于自己。

    • 因之前的思想与现在的相悖,故Thread.stop, Thread.suspend, Thread.resume 都已经被废弃了。
    • 现在的思想是采用Thread.interrupt ,其作用其实也不是中断线程,而是「通知线程应该中断了」,具体到底中断还是继续运行,应该由被通知的线程自己处理。

    具体来说,当对一个线程,调用 interrupt() 时,

    • 如果线程处于被阻塞状态(例如处于sleep, wait, join 等状态),那么线程将立即退出被阻塞状态,并抛出一个InterruptedException异常。仅此而已。
    • 如果线程处于正常活动状态,那么会将该线程的中断标志设置为 true,仅此而已。被设置中断标志的线程将继续正常运行,不受影响。

    interrupt() 并不能真正的中断线程,需要被调用的线程自己进行配合才行。也就是说,一个线程如果有被中断的需求,那么就可以这样做。

    • 在正常运行任务时,经常检查本线程的中断标志位,如果被设置了中断标志就自行停止线程。
    • 在调用阻塞方法时正确处理InterruptedException异常。
      (例如,catch异常后就结束线程。)
    • 当你的捕获到一个InterruptedException异常后,亦可以处理它,或者向上抛出。
    • 抛出时要注意:当你捕获到InterruptedException异常后,当前线程的中断状态已经被修改为false(表示线程未被中断);此时你若能够处理中断,则不用理会该值;但如果你继续向上抛InterruptedException异常,你需要再次调用interrupt方法,将当前线程的中断状态设为true。
    • 注意:绝对不能“吞掉中断”!即捕获了InterruptedException而不作任何处理。这样违背了中断机制的规则,别人想让你线程中断,然而你自己不处理,也不将中断请求告诉调用者,调用者一直以为没有中断请求。

    相关方法

    方法 说明
    public static boolean interrupted 测试当前线程是否已经中断。线程的中断状态 由该方法清除。换句话说,如果连续两次调用该方法,则第二次调用将返回 false(在第一次调用已清除了其中断状态之后,且第二次调用检验完中断状态前,当前线程再次中断的情况除外)。
    public boolean isInterrupted() 测试线程是否已经中断。线程的中断状态不受该方法的影响。
    public void interrupt() 中断线程。

    Linux 中断

    什么是中断,操作系统级别的定义

    Linux 内核需要对连接到计算机上的所有硬件设备进行管理,毫无疑问这是它的份内事。如果要管理这些设备,首先得和它们互相通信才行,一般有两种方案可实现这种功能:

    1. 轮询(polling)** 让内核定期对设备的状态进行查询,然后做出相应的处理;
    2. 中断(interrupt)** 让硬件在需要的时候向内核发出信号(变内核主动为硬件主动)。

    第一种方案会让内核做不少的无用功,因为轮询总会周期性的重复执行,大量地耗用 CPU 时间,因此效率及其低下,所以一般都是采用第二种方案

    从物理学的角度看,中断是一种电信号,由硬件设备产生,并直接送入中断控制器(如 8259A)的输入引脚上,然后再由中断控制器向处理器发送相应的信号。处理器一经检测到该信号,便中断自己当前正在处理的工作,转而去处理中断。此后,处理器会通知 OS 已经产生中断。这样,OS 就可以对这个中断进行适当的处理。不同的设备对应的中断不同,而每个中断都通过一个唯一的数字标识,这些值通常被称为中断请求线。

    一、最简单的中断机制

    最简单的中断机制就是像芯片手册上讲的那样,在中断向量表中填入跳转到对应处理函数的指令,然后在处理函数中实现需要的功能。类似下图:



    这种方式在原来的单片机课程中常常用到,一些简单的单片机系统也是这样用。
    它的好处很明显,简单,直接。

    二、下半部:分离中断接收与中断处理过程

    中断处理函数所作的第一件事情是什么?答案是屏蔽中断(或者是什么都不做,因为常常是如果不清除IF位,就等于屏蔽中断了),当然只屏蔽同一种中断。之所以要屏蔽中断,是因为新的中断会再次调用中断处理函数,导致原来中断处理现场的破坏。即,破坏了 interrupt context。

    随着系统的不断复杂,中断处理函数要做的事情也越来越多,多到都来不及接收新的中断了。于是发生了中断丢失,这显然不行,于是产生了新的机制:分离中断接收与中断处理过程。中断接收在屏蔽中断的情况下完成;中断处理在使能中断的情况下完成,这部分被称为中断下半部。



    从上图中看,只看int0的处理。Func0为中断接收函数。中断只能简单的触发func0,而func0则能做更多的事情,它与funcA之间可以使用队列等缓存机制。当又有中断发生时,func0被触发,然后发送一个中断请求到缓存队列,然后让funcA去处理。

    由于func0做的事情是很简单的,所以不会影响int0的再次接收。而且在func0返回时就会使能int0,因此funcA执行时间再长也不会影响int0的接收。

    三、软中断(统一调度器)

    下面看看linux中断处理。作为一个操作系统显然不能任由每个中断都各自为政,统一管理是必须的。

    我们不可中断部分的共同部分放在函数do_IRQ中,需要添加中断处理函数时,通过request_irq实现。下半部放在do_softirq中,也就是软中断,通过open_softirq添加对应的处理函数。


    四、tasklet

    旧事物跟不上历史的发展时,总会有新事物出现。

    随着中断数的不停增加,软中断不够用了,于是下半部又做了进化。

    软中断用轮询的方式处理。假如正好是最后一种中断,则必须循环完所有的中断类型,才能最终执行对应的处理函数。显然当年开发人员为了保证轮询的效率,于是限制中断个数为32个。

    为了提高中断处理数量,顺道改进处理效率,于是产生了tasklet机制。

    Tasklet采用无差别的队列机制,有中断时才执行,免去了循环查表之苦。


    总结下tasklet的优点:

    (1)无类型数量限制;
    (2)效率高,无需循环查表;
    (3)支持SMP机制;

    五、工作队列

    前面的机制不论如何折腾,有一点是不会变的。它们都在中断上下文中。什么意思?说明它们不可挂起。而且由于是串行执行,因此只要有一个处理时间较长,则会导致其他中断响应的延迟。为了完成这些不可能完成的任务,于是出现了工作队列。工作队列说白了就是一组内核线程,作为中断守护线程来使用。多个中断可以放在一个线程中,也可以每个中断分配一个线程。

    工作队列对线程作了封装,使用起来更方便。

    因为工作队列是线程,所以我们可以使用所有可以在线程中使用的方法。

    Tasklet其实也不一定是在中断上下文中执行,它也有可能在线程中执行。

    假如中断数量很多,而且这些中断都是自启动型的(中断处理函数会导致新的中断产生),则有可能cpu一直在这里执行中断处理函数,会导致用户进程永远得不到调度时间。

    为了避免这种情况,linux发现中断数量过多时,会把多余的中断处理放到一个单独的线程中去做,就是ksoftirqd线程。这样又保证了中断不多时的响应速度,又保证了中断过多时不会把用户进程饿死。

    问题是我们不能保证我们的tasklet或软中断处理函数一定会在线程中执行,所以还是不能使用进程才能用的一些方法,如放弃调度、长延时等。

    Tasklet机制分析

    有了软中断,linux内核为什么要引入tasklet机制呢?主要原因:

    • 软中断的pending标志位也就32位,一般情况是不随意增加软中断处理的。
    • 内核没有提供通用的增加软中断的接口。
    • 软中断处理函数要求可重入,需要考虑到竞争条件比较多,要求比较高的编程技巧。所以内核提供了tasklet这样的一种通用的机制。

    把细节的东西说明白,会越写越多。这样做的好处是能真正理解其中的机制。但是,内容太多的一个坏处就是难道记忆。所以先把精髓总结出来。Tasklet的特点,也是tasklet的精髓就是:

    • tasklet不能休眠
    • 同一个tasklet不能在两个CPU上同时运行
    • 不同tasklet可能在不同CPU上同时运行
    • 需要注意共享数据的保护。

    软中断和tasklet的总结

    • 软中断:

    1、软中断是在编译期间静态分配的。
    2、最多可以有32个软中断。
    3、软中断不会抢占另外一个软中断,唯一可以抢占软中断的是中断处理程序。
    4、可以并发运行在多个CPU上(即使同一类型的也可以)。所以软中断必须设计为可重入的函数(允许多个CPU同时操作),因此也需要使用自旋锁来保护其数据结构。
    5、目前只有两个子系直接使用软中断:网络和SCSI。
    6、执行时间有:从硬件中断代码返回时、在ksoftirqd内核线程中和某些显示检查并执行软中断的代码中。

    • tasklet:

    1、tasklet是使用两类软中断实现的:HI_SOFTIRQ和TASKLET_SOFTIRQ。本质上没有什么区别,只不过HI_SOFTIRQ的优先级更高一些,建立在HI_SOFTIRQ上的tasklet会早于TASKLET_SOFTIRQ执行。
    2、可以动态增加减少,没有数量限制。
    3、同一类的tasklet不能并发执行。
    4、不同类的tasklet可以并发执行。
    5、大部分情况下推荐使用tasklet。

    Ref:
    https://www.zhihu.com/question/41048032
    http://www.infoq.com/cn/articles/java-interrupt-mechanism
    https://www.ibm.com/developerworks/cn/linux/l-cn-linuxkernelint/index.html
    http://blog.jobbole.com/105524/
    http://unicornx.github.io/2016/02/12/20160212-lk-drv-tasklet/

    相关文章

      网友评论

          本文标题:Interrupt 线程与OS

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