软中断与硬中断 & 中断抢占 中断嵌套

参考了这篇文章: 从本质上来讲,中断是一种电信号,当设备有某种事件发生时,它就会产生中断,通过总线把电信号发送给中断控制器(如 8259A)。如果中断的线是激活的中断控制器就把电信号发送给处理器的某个特定引脚处理器于是立即停止自己正在做的事,跳到中断处理程序的入口点,进行中断处理。 常见的中断控制器有两种:可编程中断控制器 8259A 和高级可编程中断控制器(APIC),中断控制器应该在大学的硬件接口和计算机体系结构的相关课程中都学过。传统的 8259A 只适合单 CPU 的情况,现在都是多 CPU 多核的 SMP 体系,所以为了充分利用 SMP 体系结构、把中断传递给系统上的每个 CPU 以便更好实现并行和提高性能,Intel 引入了高级可编程中断控制器(APIC)。光有高级可编程中断控制器的硬件支持还不够,Linux 内核还必须能利用到这些硬件特质,所以只有 kernel 2.4 以后的版本才支持把不同的硬件中断请求(IRQs)分配到特定的 CPU 上,这个绑定技术被称为 SMP IRQ Affinity.   IRQ中断处理程序比一个进程要“轻”(中断的上下文很少,建立或终止中断处理需要的时间也很少)中断处理是由内核执行的最敏感的任务之一,因为它必须满足下列约束:
◎ 当内核正打算去完成一些别的事情时,中断随时会到来。因此,内核的目标就是让中断尽可能快地处理完,尽其所能把更多的处理向后推迟。因此,内核响应中断后需要进行的操作分为两部分:关键而紧急的部分,内核立即执行;其余推迟的部分,内核随后执行。
        ◎ 因为中断随时会到来,所以内核可能正在处理其中的一个中断时,另一个不同类型的中断又发生了。内核应该尽可能地允许这种情况发生,因为这能维持更多的I/O设备得到处理的机会。因此,中断处理程序必须编写成使相应的内核控制路径能以嵌套的方式执行。当最后一个内核控制路径终止时,内核必须能恢复被中断进程的执行,或者,如果中断信号已导致了重新调度,内核也应能切换到另外的进程。
        ◎ 尽管内核在处理前一个中断时可以接受一个新的中断,但在内核代码中还是存在一些临界区,在临界区中,中断必须被禁止。必须尽可能地限制这样的临界区,因为根据以前的要求,内核,尤其是中断处理程序,应该在大部分时间内以开中断的方式运行。
 因为外部设备不能直接发出中断,而必须通过中断控制器的标准组件来请求中断,所以这种请求更正确的叫法是IRQ,或中断请求(Interrupt Request)。 中断不能由处理器外部的外设直接产生,而必须借助于一个称为可编程中断控制器(programmable interrupt controller, PIC)的标准组件来请求,该组件存在于每个系统中。外部设备,会有电路连接到用于向中断控制器发送中断请求的组件。控制器在执行了各种电工任务之后,将中断请求转发到CPU的中断输入中。 每个能够发出中断请求的硬件设备控制器都有这么一条名为IRQ的输出线。所有现有的IRQ线都会与这个中断控制器(PIC)的硬件电路的输入引脚相连。下面来看看这种中断控制器执行下列动作:        1)    监视IRQ线,检查产生的信号 。如果有一条或两条以上的IRQ线上产生信号,就选择引脚编号较小的IRQ线。        2)    如果一个引发信号出现在IRQ线上:                a)    把接收到的引发信号转换成对应的向量(索引)。                b)    把这个向量存放在中断控器的一个I/O端口,从而允许CPU通过数据总线读取此向量。                c)    把引发信号发送到处理器的INTR引脚,即产生一个中断。                d)    等待,直到CPU通过把这个中断信号写进可编程中断控制器的一个I/O端口来确认它;当这种情况发生时,清INTR线。        3)    返回到第一步。        IRQ线是从0开始顺序编号的,因此,第一条IRQ线通常表示成IRQ0。与IRQn关联的Intel缺省向量是n+32。如前所述,通过向中断控制器端口发布合适的指令,就可以修改IRQ和向量之间的映射。        可以有选择地禁止每条IRQ线。因此,可以对PIC编程从而禁止IRQ,也就是说,可以告诉PIC停止对给定的IRQ线发布中断,或者激活它们,禁止的中断是丢失不了的,它们一旦激活,PIC就又把它们发送到CPU这个特点被大多数中断处理程序使用,因为这允许中断处理程序逐次地处理同一类型的IRQ。 中断通常分为同步中断和异步中断:
同步中断是当指令执行时由CPU控制单元产生的,之所以称为同步,是因为只有在一条指令终止执行后CPU才会发出中断。

异步中断是由其他硬件设备依照CPU时钟信号随机产生的。
在Intel微处理器手册中:        ◎ 把同步中断称为异常(exception)        ◎ 把异步中断称为中断(interrupt)        这两类中断的共同特点是什么?如果CPU当前不处于核心态,则发起从用户态到核心态的切换。接下来,在内核中执行一个专门的例程,称为中断服务例程(interrupt service routine)。或中断处理程序(interrupthandler)        另一方面,异常是由程序的错误产生的,或者是由内核必须处理的异常条件产生的。第一种情况下,内核通过发送一个每个Unix/Linux程序员都熟悉的信号来处理异常。第二种情况下,内核执行恢复异常需要的所有步骤,例如缺页异常等。 Intel处理器,好像把前32个中断向量,用来处理异常。另外还有0x80用来处理系统调用。 术语中断处理程序的使用可能引起岐义。因为它是用于指代CPU对ISR(中断服务程序)的调用,包括了进入/退出路径和ISR本身。当然,如果只指代在进入路径和退出路径之间进行由C语言实现的例程,将更为准确。中断技术上的实现有两方面:        1)    汇编语言代码:与处理器高度相关,用于处理特定平台上相关的底层细节;        2)    抽象接口:是设备驱动程序及其他内核代码安装和管理IRQ处理程序所需的。 回到 (1) 硬中断由与系统相连的外设(比如网卡、硬盘)自动产生的。主要是用来通知操作系统系统外设状态的变化。比如当网卡收到数据包的时候,就会发出一个中断。我们通常所说的中断指的是硬中断(hardirq)。 (2) 软中断为了满足实时系统的要求,中断处理应该是越快越好。linux为了实现这个特点,当中断发生的时候,硬中断处理那些短时间就可以完成的工作,而将那些处理事件比较长的工作,放到中断之后来完成,也就是软中断(softirq)来完成。(注:也就是bottom-half) 软中断指令int是软中断指令。中断向量表是中断号和中断处理函数地址的对应表。int n - 触发软中断n。相应的中断处理函数的地址为:中断向量表地址 + 4 * n。 硬中断和软中断的区别软中断是执行中断指令产生的,而硬中断是由外设引发的。硬中断的中断号是由中断控制器提供的,软中断的中断号由指令直接指出,无需使用中断控制器。硬中断是可屏蔽的,软中断不可屏蔽。(下面讲到,处理一种硬中断的时候,屏蔽所有同类型中断)硬中断处理程序要确保它能快速地完成任务,这样程序执行时才不会等待较长时间,称为上半部。软中断处理硬中断未完成的工作,是一种推后执行的机制,属于下半部。  开关
(1) 硬中断的开关
简单禁止和激活当前处理器上的本地中断:
local_irq_disable();
local_irq_enable();
保存本地中断系统状态下的禁止和激活:
unsigned long flags;
local_irq_save(flags);
local_irq_restore(flags);
 
(2) 软中断的开关
禁止下半部,如softirq、tasklet和workqueue等:
local_bh_disable();
local_bh_enable();
需要注意的是,禁止下半部时仍然可以被硬中断抢占。
 
(3) 判断中断状态
#define in_interrupt() (irq_count()) // 是否处于中断状态(硬中断或软中断)
#define in_irq() (hardirq_count()) // 是否处于硬中断
#define in_softirq() (softirq_count()) // 是否处于软中断
View Code 软中断(1) 定义软中断是一组静态定义的下半部接口,可以在所有处理器上同时执行,即使两个类型相同也可以。一个软中断不会抢占另一个软中断,唯一可以抢占软中断的是硬中断。 软中断由softirq_action结构体表示:
struct softirq_action {
    void (*action) (struct softirq_action *); /* 软中断的处理函数 */
};
 目前已注册的软中断有10种,定义为一个全局数组:
static struct softirq_action softirq_vec[NR_SOFTIRQS];

enum {
    HI_SOFTIRQ = 0, /* 优先级高的tasklets */
    TIMER_SOFTIRQ, /* 定时器的下半部 */
    NET_TX_SOFTIRQ, /* 发送网络数据包 */
    NET_RX_SOFTIRQ, /* 接收网络数据包 */
    BLOCK_SOFTIRQ, /* BLOCK装置 */
    BLOCK_IOPOLL_SOFTIRQ,
    TASKLET_SOFTIRQ, /* 正常优先级的tasklets */
    SCHED_SOFTIRQ, /* 调度程序 */
    HRTIMER_SOFTIRQ, /* 高分辨率定时器 */
    RCU_SOFTIRQ, /* RCU锁定 */
    NR_SOFTIRQS /* 10 */
};
 (3) 触发软中断 调用raise_softirq()来触发软中断。 

在下列地方,待处理的软中断会被检查和执行:

1. 从一个硬件中断代码处返回时

2. 在ksoftirqd内核线程中

3. 在那些显示检查和执行待处理的软中断的代码中,如网络子系统中

 

而不管是用什么方法唤起,软中断都要在do_softirq()中执行。如果有待处理的软中断,

do_softirq()会循环遍历每一个,调用它们的相应的处理程序。

在中断处理程序中触发软中断是最常见的形式。中断处理程序执行硬件设备的相关操作,

然后触发相应的软中断,最后退出。内核在执行完中断处理程序以后,马上就会调用

do_softirq(),于是软中断开始执行中断处理程序完成剩余的任务。

 

(4) ksoftirqd内核线程

内核不会立即处理重新触发的软中断。

当大量软中断出现的时候,内核会唤醒一组内核线程来处理。

这些线程的优先级最低(nice值为19),这能避免它们跟其它重要的任务抢夺资源。

但它们最终肯定会被执行,所以这个折中的方案能够保证在软中断很多时用户程序不会

因为得不到处理时间而处于饥饿状态,同时也保证过量的软中断最终会得到处理。

 

每个处理器都有一个这样的线程,名字为ksoftirqd/n,n为处理器的编号。

 

中断嵌套linux中的中断处理程序是无需重入的。当一个给定的中断处理程序正在执行时,相应的中断线在所有处理器上都会被屏蔽掉, 以防止在同一中断线上接收另一个新的中断。 通常情况下,所有其他的中断都是打开的,所以这些不同中断线上的其他中断都能被处理 ,但当前中断线总是被禁止的。由此可以看出,同一个中断处理程序绝对不会被同时调用以处理嵌套的中断这极大的简化了中断处理 程序的编写。说明:linux会把处理硬件中断的过程分为两个部分叫做top halves 和 Bottom halves 上半部分做的工作是时间要求比较紧,操作硬件,或不能被别的中断打断的的重要工作,这时会在所有处理器上屏蔽当前中断线,如果这个中断处理是SA_INTERRUPT标记的,那么所有的本地中断都会全局的被屏蔽掉。下半部分则会恢复响应所有中断,这就使系统处于中断屏蔽状态的时间尽可能的短了,中断响应能力自然也就高了。下半部分完成的工作对时间也不那么敏感,和硬件无关了,可以稍后点执行。 就比如本上有个例子:说的是网卡接收中断处理,上半部分:中断开始,接收,进入中断处理程序,应答网卡,拷贝网络数据包到内存sk_buff,之后就是下半部了。  抢占硬中断可以被另一个优先级比自己高的硬中断“中断”,不能被同级(同一种硬中断)或低级的硬中断“中断”,更不能被软中断“中断”。软中断可以被硬中断“中断”,但是不会被另一个软中断“中断”。在一个CPU上,软中断总是串行执行。所以在单处理器上,对软中断的数据结构进行访问不需要加任何同步原语。 按照Intel的微处理器手册,同步中断和异步中断也分别称为异常(或者软件中断)和中断。硬中断和软中断都可以抢占(或者称为中断)异常(最典型的是系统调用),但是异常不能抢占硬中断和软中断。硬中断和软中断(只要是中断上下文)执行的时候都不允许内核抢占,换句话说,中断上下文中永远不允许进程切换。(个人理解,由于中断处理程序都需要较快地完成,而且中断处理程序可以嵌套,因此中断处理程序必须不能阻塞,否则性能就非常不能保证了。 

相关内容推荐