Chinaunix首页 | 论坛 | 博客
  • 博客访问: 521443
  • 博文数量: 96
  • 博客积分: 2960
  • 博客等级: 少校
  • 技术积分: 1850
  • 用 户 组: 普通用户
  • 注册时间: 2006-12-11 15:25
文章分类

全部博文(96)

文章存档

2009年(37)

2008年(59)

我的朋友

分类:

2008-12-08 11:08:02

biger410
中断处理程序为什么不能挂起

本人从未在讲操作系统理论的书上看到中断处理程序不能挂起的说法,但实际的操作系统都遵循这个原则。我分析了一下linux,觉得有以下几个原因:
1. 保护数据结构的考虑.
    中断作为一种保护数据结构的方式,如果可以挂起,从而发生进程切换,那么切换到的新进程有可能修改中断也在访问的数据,可能产生问题.
2. 效率的考虑
   中断处理程序执行的时候,一般同种类型的中断则不再响应,因此如果把中断挂起,会大大降低中断处理的效率。
3. 简单性的考虑
   如果允许中断挂起,那么将涉及到中断如何切换,如何恢复等问题,复杂性增加。

以上是我认为的几个原因,不知道说的对不对,也不知道是否还有其他的原因。请大家指点一下。
ps:注意我们这里并不讨论目前linux内核不能处理中断挂起的问题,比如说schedule()函数不能保存中断的上下文等。我感觉schedule之所以写成这样是因为在设计的时候就有"中断处理程序不能挂起"这个设计原则。

2008-10-26 13:41 dogygb
我也没有看到过一定不能挂起的说法,不过如果挂起的话,什么时候切换会那个被中断打断的进程(中断实际上用的它的内核栈嘛)还是未知数,所以如果中断挂 起,那么中断的执行情况将变得不确定,毕竟大部分中断都是期望快速响应执行的。另外,如果存在那种非常不紧急的中断,对处理时间也没什么要求的话,我觉得 倒是可以挂起,不知道传说中的Solaris中断线程化是不是就是这样的,把对时限要求不严格的中断线程化了。

2008-10-26 14:02 biger410
确实是,如果挂起,中断处理会变得不确定。
ULK上“中断和异常处理程序的嵌套执行”章节上说:“允许内核控制路径嵌套执行必须付出代价,那就是中断处理程序必须永不阻塞,换句话说,中断处理程序运行期间不能发生进程切换。”
不明白他这句话,中断嵌套和中断挂起有什么必然的联系?就算嵌套了在挂起了,下次再切换回当前进程,不是也可以从内核栈中恢复出中断上下文的吗?

2008-10-26 17:06 richardhesidu
考虑一下中断线程化。
传说Solaris支持,linux也出了相关的补丁,不过好像还没有进入源代码树。

2008-10-26 17:55 dogygb
嵌套的话 当前中断完了 肯定就回到被嵌套的那个中断中了,但是如果挂起的话,涉及到进程切换了,就不一定会回到那个中断处理程序打断的进程了

2008-10-26 20:09 Au_Hank
中断不属于任何进程, 中断自己也不是进程, 所以本身就没有挂起的说法吧?
我觉得中断的优先级比任何进程都高, 这就是为什么它叫做中断, 是它去中断
进程或者其他内核代码的执行, 而不是进程来中断它.

其次是中断挂起的目的是什么? 进程挂起,或者说睡眠的目的是因为进程必须
等待资源,或者超过它运行的时间片了, 必须睡眠. 中断没有这样的情况. 如果说
有的话, 那就是中断的嵌套, 被更高优先级别的中断中断了. 中断设计的目的就是
为了尽快响应异步事件, 在尽可能短的时间内处理事情, 避免中断嵌套. 如果不这样
做的话, 那所有事件都在中断里面处理了, 中断嵌套中断, 没完没了地嵌套下去...
因为这个目的, 所有才有网络数据处理里面的硬中断和软中断的设计了.

另外我不了解SOLARIS里面中断线程化是什么样的概念, 处于什么目的,有什么优点?
哪位朋友来指点迷津一下?

2008-10-27 10:10 sunchiang
好像是linux的中断处理程序没有线程化,所以所谓挂起无从谈起

2008-10-27 10:18 eclipse_2
是否能挂起应该是对线程、进程而言的吧
而中断并不是一个线程或进程
对中断而言应该是是否允许嵌套什么的

2008-10-27 12:59 new_learner
我觉得原因还是在于:如果中断处理程序阻塞,那么对这个中断处理程序所在的进程是不公平的。因为中断是外部事件,与中断前正在执行的进程是没关系的,如果在中断处理过程中发生阻塞,那么进程就会被切换掉,太冤枉了,不合理。

这个跟异常不一样,异常是进程发起的,就算在异常处理中发生阻塞导致进程被切换出CPU,也是合情合理的,谁叫你进程产生异常呢?活该!

不过,从技术实现上来讲,中断处理程序可以阻塞,
但从道理上却讲不通。

2008-10-27 17:03 dogygb
谈谈自己对中断的理解,中断来说从逻辑上区分了中断上下文和进程上下文,但从物理代码实现上,中断上下文或者进程上下文对CPU等资源来说并没有什么区 别,中断其实是穿了被中断进程的外衣来做与这个进程没有关系的事情,快速执行完毕后,再把控制权给那个被中断进程,就是回到被中断时候的上下文环境中,对 被中断进程来说,我觉得可以看做无辜的被强加了一个函数调用一样,反正中断结束时候也会回到被中断的那条指令。如果中断的时候,执行了可以睡眠的代码,我 也不知道会发生什么,可能硬件的工作会变得混乱,因为中断处理程序是寄生在被中断进程上的,如果这个进程优先级不高,那么睡眠后的下一次进程切换可能就不 能切换到它,也就是不能切换到中断处理程序,同时,这也是内核抢占允许的情况下,所以中断的处理情况会变得非常难以确定。
中断线程化我觉得可以这样理解,我用一个优先级非常高的内核线程来进行中断的线程化处理,这个线程会处理中断服务程序,就算它挂起了,由于优先级非常高, 会在很短的时间内再次被调度执行,于是可以保证中断在一定程度上的快速执行。但是这里必须注意到不让其它程序饿死的问题,由于这样的高优先级线程存在,如 果它造成不断嵌套的话,是比较恐怖的,用户进程可能饿死

以上个人看法,大家评点评点

2008-10-27 21:59 biger410
转一篇IBM网站上的文章。
[url]http://www.ibm.com/developerworks/cn/linux/l-cn-linuxkernelint/index.html[/url]
看来以后LINUX是可能支持中断线程化的,中断处理程序就可以挂起了。

2008-10-28 10:11 jakepain
中断不能挂起,因为挂起后就回不来了

2008-10-28 10:22 rodgerluo
回复 #1 biger410 的帖子

你们把所有中断关闭(包括定时器)看一下你们的进程还能调度吗?“中断挂起”简直是莫名其妙。除非你关闭中断。

2008-10-28 10:51 思一克
中断怎么没有被挂起?

时钟中断后导致了进程调度就是将该始终中断挂起了.

2008-10-28 11:02 specter117
时钟中断引起的进程切换也是在中断结束时进行的,不算中断挂起吧.
我对中断处理函数不能挂起的理解是:在中断处理过程中,对应IRQ line处于被屏蔽状态之中,若中断处理被挂起,将导致该IRQ line一直得不到响应

2008-10-28 11:29 思一克
不管细节.
中断了一个程序在A指令, 中断返回后应该返回A+1执行.
如果没有返回到A+1而去其它地方了, 就是中断没有真正返回.

LINUX中只有时钟中断的进程调度有此情况.

[quote]原帖由 [i]specter117[/i] 于 2008-10-28 11:02 发表 [url=][img]http://linux.chinaunix.net/bbs/images/common/back.gif[/img][/url]
时钟中断引起的进程切换也是在中断结束时进行的,不算中断挂起吧.
我对中断处理函数不能挂起的理解是:在中断处理过程中,对应IRQ line处于被屏蔽状态之中,若中断处理被挂起,将导致该IRQ line一直得不到响应 [/quote]

2008-10-28 15:50 richardhesidu
挂起一个进程或内核线程,需要保存现场,还要有机制来保证在适当的时候能够从某一点重新唤醒被挂起的进程或是内核线程。现在的linux内核并没有对中断 实现这样的机制。中断线程化,就是给中断处理例程一个内核线程的上下文,在内核线程中运行中断处理例程。并且赋予每个中断一个线程的优先级,这样更高优先 级的实时任务可以更快的获得响应。


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

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

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

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





回页首


X86 计算机的 CPU 为中断只提供了两条外接引脚:NMI 和 INTR。其中 NMI 是不可屏蔽中断,它通常用于电源掉电和物理存储器奇偶校验;INTR是可屏蔽中断,可以通过设置中断屏蔽位来进行中断屏蔽,它主要用于接受外部硬件的中断 信号,这些信号由中断控制器传递给 CPU。

常见的中断控制器有两种:

1. 可编程中断控制器8259A

传统的 PIC(Programmable Interrupt Controller)是由两片 8259A 风格的外部芯片以“级联”的方式连接在一起。每个芯片可处理多达 8 个不同的 IRQ。因为从 PIC 的 INT 输出线连接到主 PIC 的 IRQ2 引脚,所以可用 IRQ 线的个数达到 15 个,如图 1 所示。



8259A 级联原理图

2. 高级可编程中断控制器(APIC)

8259A 只适合单 CPU 的情况,为了充分挖掘 SMP 体系结构的并行性,能够把中断传递给系统中的每个 CPU 至关重要。基于此理由,Intel 引入了一种名为 I/O 高级可编程控制器的新组件,来替代老式的 8259A 可编程中断控制器。该组件包含两大组成部分:一是“本地 APIC”,主要负责传递中断信号到指定的处理器;举例来说,一台具有三个处理器的机器,则它必须相对的要有三个本地 APIC。另外一个重要的部分是 I/O APIC,主要是收集来自 I/O 装置的 Interrupt 信号且在当那些装置需要中断时发送信号到本地 APIC,系统中最多可拥有 8 个 I/O APIC。

每个本地 APIC 都有 32 位的寄存器,一个内部时钟,一个本地定时设备以及为本地中断保留的两条额外的 IRQ 线 LINT0 和 LINT1。所有本地 APIC 都连接到 I/O APIC,形成一个多级 APIC 系统,如图 2 所示。



多级I/O APIC系统

目前大部分单处理器系统都包含一个 I/O APIC 芯片,可以通过以下两种方式来对这种芯片进行配置:

1) 作为一种标准的 8259A 工作方式。本地 APIC 被禁止,外部 I/O APIC 连接到 CPU,两条 LINT0 和 LINT1 分别连接到 INTR 和 NMI 引脚。

2) 作为一种标准外部 I/O APIC。本地 APIC 被激活,且所有的外部中断都通过 I/O APIC 接收。

辨别一个系统是否正在使用 I/O APIC,可以在命令行输入如下命令:

# cat /proc/interrupts
CPU0
0: 90504 IO-APIC-edge timer
1: 131 IO-APIC-edge i8042
8: 4 IO-APIC-edge rtc
9: 0 IO-APIC-level acpi
12: 111 IO-APIC-edge i8042
14: 1862 IO-APIC-edge ide0
15: 28 IO-APIC-edge ide1
177: 9 IO-APIC-level eth0
185: 0 IO-APIC-level via82cxxx
...

如果输出结果中列出了 IO-APIC,说明您的系统正在使用 APIC。如果看到 XT-PIC,意味着您的系统正在使用 8259A 芯片。





回页首


中断可分为同步(synchronous)中断和异步(asynchronous)中断:

1. 同步中断是当指令执行时由 CPU 控制单元产生,之所以称为同步,是因为只有在一条指令执行完毕后 CPU 才会发出中断,而不是发生在代码指令执行期间,比如系统调用。

2. 异步中断是指由其他硬件设备依照 CPU 时钟信号随机产生,即意味着中断能够在指令之间发生,例如键盘中断。

根据 Intel 官方资料,同步中断称为异常(exception),异步中断被称为中断(interrupt)。

中断可分为可屏蔽中断(Maskable interrupt)和非屏蔽中断(Nomaskable interrupt)。异常可分为故障(fault)、陷阱(trap)、终止(abort)三类。

从广义上讲,中断可分为四类:中断故障陷阱终止。这些类别之间的异同点请参看 表 1。

表 1:中断类别及其行为
类别原因异步/同步返回行为
中断来自I/O设备的信号异步总是返回到下一条指令
陷阱有意的异常同步总是返回到下一条指令
故障潜在可恢复的错误同步返回到当前指令
终止不可恢复的错误同步不会返回

X86 体系结构的每个中断都被赋予一个唯一的编号或者向量(8 位无符号整数)。非屏蔽中断和异常向量是固定的,而可屏蔽中断向量可以通过对中断控制器的编程来改变。





回页首


中断描述符表(Interrupt Descriptor Table,IDT)是一个系统表,它与每一个中断或异常向量相联系,每一个向量在表中存放的是相应的中断或异常处理程序的入口地址。内核在允许中断发生前,也就是在系统初始化时,必须把 IDT 表的初始化地址装载到 idtr 寄存器中,初始化表中的每一项。

当处于实模式下时,IDT 被初始化并由 BIOS 程序所使用。然而,一旦 Linux 开始接管,IDT 就被移到 ARM 的另一个区域,并进行第二次初始化,因为 Linux 不使用任何 BIOS 程序,而使用自己专门的中断服务程序(例程)(interrupt service routine,ISR)。中断和异常处理程序很像常规的 C 函数

有三个主要的数据结构包含了与 IRQ 相关的所有信息:hw_interrupt_typeirq_desc_tirqaction,图3 解释了它们之间是如何关联的。



IRQ结构之间的关系

在 X86 系统中,对于 8259A 和 I/O APIC 这两种不同类型的中断控制器,hw_interrupt_type 结构体被赋予不同的值,具体区别参见表 2。

表 2:8259A 和 I/O APIC PIC 的区别
8259AI/O APIC
static struct hw_interrupt_type i8259A_irq_type = { "XT-PIC", startup_8259A_irq, shutdown_8259A_irq, enable_8259A_irq, disable_8259A_irq, mask_and_ack_8259A, end_8259A_irq, NULL }; static struct hw_interrupt_type ioapic_edge_type = { .typename = "IO-APIC-edge", .startup = startup_edge_ioapic, .shutdown = shutdown_edge_ioapic, .enable = enable_edge_ioapic, .disable = disable_edge_ioapic, .ack = ack_edge_ioapic, .end = end_edge_ioapic, .set_affinity = set_ioapic_affinity, }; static struct hw_interrupt_type ioapic_level_type = { .typename = "IO-APIC-level", .startup = startup_level_ioapic, .shutdown = shutdown_level_ioapic, .enable = enable_level_ioapic, .disable = disable_level_ioapic, .ack = mask_and_ack_level_ioapic, .end = end_level_ioapic, .set_affinity = set_ioapic_affinity, };

在中断初始化阶段,调用 hw_interrupt_type 类型的变量初始化 irq_desc_t 结构中的 handle 成员。在早期的系统中使用级联的8259A,所以将用 i8259A_irq_type 来进行初始化,而对于SMP系统来说,要么以 ioapic_edge_type,或以 ioapic_level_type 来初始化 handle 变量。

对于每一个外设,要么以静态(声明为 static 类型的全局变量)或动态(调用 request_irq 函数)的方式向 Linux 内核注册中断处理程序。不管以何种方式注册,都会声明或分配一块 irqaction 结构(其中 handler 指向中断服务程序),然后调用 setup_irq() 函数,将 irq_desc_tirqaction 联系起来。

当中断发生时,通过中断描述符表 IDT 获取中断服务程序入口地址,对于 32≤ i ≤255(i≠128) 之间的中断向量,将会执行 push $i-256,jmp common_interrupt 指令。随之将调用 do_IRQ() 函数,以中断向量为 irq_desc[] 结构的下标,获取 action 的指针,然后调用 handler 所指向的中断服务程序。

从以上描述,我们不难看出整个中断的流程,如图 4 所示:



X86中断流

本文作者之一曾经对2.6.10的中断系统进行过情景分析,有兴趣的读者可以和作者取得联系,获取相关资料。





回页首


在 SMP 体系结构中,我们可以通过调用系统调用和一组相关的宏来设置 CPU 亲和力(CPU affinity),将一个或多个进程绑定到一个或多个处理器上运行。中断在这方面也毫不示弱,也具有相同的特性。中断亲和力是指将一个或多个中断源绑定 到特定的 CPU 上运行。中断亲和力最初由 Ingo Molnar 设计并实现。

/proc/irq 目录中,对于已经注册中断处理程序的硬件设备,都会在该目录下存在一个以该中断号命名的目录 IRQ#IRQ# 目录下有一个 smp_affinity 文件(SMP 体系结构才有该文件),它是一个 CPU 的位掩码,可以用来设置该中断的亲和力, 默认值为 0xffffffff,表明把中断发送到所有的 CPU 上去处理。如果中断控制器不支持 IRQ affinity,不能改变此默认值,同时也不能关闭所有的 CPU 位掩码,即不能设置成 0x0

我们以网卡(eth1,中断号 44 )为例,在具有 8 个 CPU 的服务器上来设置网卡中断的亲和力(以下数据出自内核源码 Documentation\IRQ-affinity.txt):

[root@moon 44]# cat smp_affinity
ffffffff
[root@moon 44]# echo 0f > smp_affinity
[root@moon 44]# cat smp_affinity
0000000f
[root@moon 44]# ping -f h
PING hell (195.4.7.3): 56 data bytes
...
--- hell ping statistics ---
6029 packets transmitted, 6027 packets received, 0% packet loss
round-trip min/avg/max = 0.1/0.1/0.4 ms
[root@moon 44]# cat /proc/interrupts | grep 44:
44: 0 1785 1785 1783 1783 1 1 0 IO-APIC-level eth1
[root@moon 44]# echo f0 > smp_affinity
[root@moon 44]# ping -f h
PING hell (195.4.7.3): 56 data bytes
..
--- hell ping statistics ---
2779 packets transmitted, 2777 packets received, 0% packet loss
round-trip min/avg/max = 0.1/0.5/585.4 ms
[root@moon 44]# cat /proc/interrupts | grep 44:
44: 1068 1785 1785 1784 1784 1069 1070 1069 IO-APIC-level eth1
[root@moon 44]#

在 上例中,我们首先只允许在 CPU0~3 上处理网卡中断,接着运行 ping 程序,不难发现在 CPU4~7 上并没有对网卡中断进行处理。然后只在 CPU4~7 上对网卡中断进行处理, CPU0~3 不对网卡中断进行任何处理,运行 ping 程序之后,再次查看 /proc/interrupts 文件时,不难发现 CPU4~7 上的中断次数明显增加,而 CPU0~3 上的中断次数没有太大的变化。

在探讨中断亲和力的实现原理之前,我们首先来了解 I/O APIC 中的组成。

I/O APIC 由一组 24 条 IRQ 线,一张 24 项的中断重定向表(Interrupt Redirection Table),可编程寄存器,以及通过 APIC 总线发送和接收 APIC 信息的一个信息单元组成。其中与中断亲和力息息相关的是中断重定向表, 中断重定向表表中的每一项都可以被单独编程以指明中断向量和优先级、目标处理器及选择处理器的方式

通过表 2,不难发现 8259A 和 APIC 中断控制器最大不同点在于 hw_interrupt_type 类型变量的最后一项。对于 8259A 类型,set_affinity 被置为 NULL,而对于 SMP 的 APIC 类型,set_affinity 被赋值为 set_ioapic_affinity

在系统初始化期间,对于 SMP 体系结构,将会调用 setup_IO_APIC_irqs() 函数来初始化 I/O APIC 芯片,芯片中的中断重定向表的 24 项被填充。在系统启动期间,所有的 CPU 都执行 setup_local_APIC() 函数,完成本地的 APIC 初始化。当有中断被触发时,将相应的中断重定向表中的值转换成一条消息,然后,通过 APIC 总线把消息发送给一个或多个本地 APIC 单元,这样,中断就能立即被传递给一个特定的 CPU,或一组 CPU,或所有的 CPU,从而来实现中断亲和力。

当我们通过 cat 命令将 CPU 掩码写进 smp_affinity 文件时,此时的调用路线图为:write() ->sys_write() ->vfs_write() ->proc_file_write() ->irq_affinity_write_proc() ->set_affinity() ->set_ioapic_affinity() ->set_ioapic_affinity_irq() ->io_apic_write();其中在调用 set_ioapic_affinity_irq() 函数时,以中断号和 CPU 掩码作为参数,接着继续调用 io_apic_write(), 修改相应的中断重定向中的值,来完成中断亲和力的设置。当执行 ping 命令时,网卡中断被触发,产生了一个中断信号,多 APIC 系统根据中断重定向表中的值,依照仲裁机制,选择 CPU0~3 中的某一个 CPU,并将该信号传递给相应的本地 APIC,本地 APIC 又中断它的 CPU,整个事件不通报给其他所有的 CPU。





回页首


在 嵌入式领域,业界对 Linux 实时性的呼声越来越高,对中断进行改造势在必行。在 Linux 中,中断具有最高的优先级。不论在任何时刻,只要产生中断事件,内核将立即执行相应的中断处理程序,等到所有挂起的中断和软中断处理完毕后才能执行正常的 任务,因此有可能造成实时任务得不到及时的处理。中断线程化之后,中断将作为内核线程运行而且被赋予不同的实时优先级,实时任务可以有比中断线程更高的优 先级。这样,具有最高优先级的实时任务就能得到优先处理,即使在严重负载下仍有实时性保证。

目前较新的 Linux 2.6.17 还不支持中断线程化。但由 Ingo Molnar 设计并实现的实时补丁,实现了中断线程化。最新的下载地址为:

下面将对中断线程化进行简要分析。

在初始化阶段,中断线程化的中断初始化与常规中断初始化大体上相同,在 start_kernel() 函数中都调用了 trap_init()init_IRQ() 两个函数来初始化 irq_desc_t 结构体,不同点主要体现在内核初始化创建 init 线程时,中断线程化的中断在 init() 函数中还将调用 init_hardirqs(kernel/irq/manage.c(已经打过上文提到的补丁)),来为每一个 IRQ 创建一个内核线程,最高实时优先级为 50,依次类推直到 25,因此任何 IRQ 线程的最低实时优先级为 25。

void __init init_hardirqs(void)
{
……
for (i = 0; i < NR_IRQS; i++) {
irq_desc_t *desc = irq_desc + i;
if (desc->action && !(desc->status & IRQ_NODELAY))
desc->thread = kthread_create(do_irqd, desc, "IRQ %d", irq);
……
}
}
static int do_irqd(void * __desc)
{
……
/*
* Scale irq thread priorities from prio 50 to prio 25
*/
param.sched_priority = curr_irq_prio;
if (param.sched_priority > 25)
curr_irq_prio = param.sched_priority - 1;
……
}

如果某个中断号状态位中的 IRQ_NODELAY 被置位,那么该中断不能被线程化。

在中断处理阶段,两者之间的异同点主要体现在:两者相同的部分是当发生中断时,CPU 将调用 do_IRQ() 函数来处理相应的中断,do_IRQ() 在做了必要的相关处理之后调用 __do_IRQ()。两者最大的不同点体现在 __do_IRQ() 函数中,在该函数中,将判断该中断是否已经被线程化(如果中断描述符的状态字段不包含 IRQ_NODELAY 标志,则说明该中断被线程化了),对于没有线程化的中断,将直接调用 handle_IRQ_event() 函数来处理。

fastcall notrace unsigned int __do_IRQ(unsigned int irq, struct pt_regs *regs)
{
……
if (redirect_hardirq(desc))
goto out_no_end;
……
action_ret = handle_IRQ_event(irq, regs, action);
……
}
int redirect_hardirq(struct irq_desc *desc)
{
……
if (!hardirq_preemption || (desc->status & IRQ_NODELAY) || !desc->thread)
return 0;
……
if (desc->thread && desc->thread->state != TASK_RUNNING)
wake_up_process(desc->thread);
……
}

对于已经线程化的情况,调用 wake_up_process() 函数唤醒中断处理线程,并开始运行,内核线程将调用 do_hardirq() 来处理相应的中断,该函数将判断是否有中断需要被处理,如果有就调用 handle_IRQ_event() 来处理。handle_IRQ_event() 将直接调用相应的中断处理函数来完成中断处理。

不难看出,不管是线程化还是非线程化的中断,最终都会执行 handle_IRQ_event() 函数来调用相应的中断处理函数,只是线程化的中断处理函数是在内核线程中执行的。

并不是所有的中断都可以被线程化,比如时钟中断,主要用来维护系统时间以及定时器等,其中定时器是操作系统的脉搏,一旦被线程化,就有可能被挂起,这样后果将不堪设想,所以不应当被线程化。如果某个中断需要被实时处理,它可以像时钟中断那样,用 SA_NODELAY 标志来声明自己非线程化,例如:

static struct irqaction irq0 = {
timer_interrupt, SA_INTERRUPT | SA_NODELAY, CPU_MASK_NONE, "timer", NULL, NULL
};

其中,SA_NODELAYIRQ_NODELAY 之间的转换,是在 setup_irq() 函数中完成的。





回页首


中断负载均衡的实现主要封装在 arch\ arch\i386\kernel\io-apic.c 文件中。如果在编译内核时配置了 CONFIG_IRQBALANCE 选项,那么 SMP 体系结构中的中断负载均衡将以模块的形式存在于内核中。

late_initcall(balanced_irq_init);
#define late_initcall(fn) module_init(fn) //include\linux\init.h

balanced_irq_init() 函数中,将创建一个内核线程来负责中断负载均衡:

static int __init balanced_irq_init(void)
{ ……
printk(KERN_INFO "Starting balanced_irq\n");
if (kernel_thread(balanced_irq, NULL, CLONE_KERNEL) >= 0)
return 0;
else
printk(KERN_ERR "balanced_irq_init: failed to spawn balanced_irq");
……
}

balanced_irq() 函数中,每隔 5HZ=5s 的时间,将调用一次 do_irq_balance() 函数,进行中断的迁徙。将重负载 CPU 上的中断迁移到较空闲的CPU上进行处理。





回页首


随着中断亲和力和中断线程化的相继实现,Linux 内核在 SMP 和实时性能方面的表现越来越让人满意,完全有理由相信,在不久的将来,中断线程化将被合并到基线版本中。本文对中断线程化的分析只是起一个抛砖引玉的作用,当新特性发布时,不至于让人感到迷茫。


阅读(2379) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~