中断及任务调度管理
Linux 书籍中常说的 BottomHalf 已然不见了,它们被转成 tasklets,这是支持 SMP 的。但其思想基本 一致。
中断及软中断模型
我们在此不会对中断及异常的原理和机制做深入的介绍。但必须要作出一些说明,因为这是理解 Linux 内核与其它嵌入式/实时操作系统的不同,以及理解网络协议栈收报文的基础。 Linux 支持 CPU 的外部硬件中断和内部中断。严格来说,内部中断包含系统调用陷入和异常,在一 般的嵌入式操作系统(比如 VxWorks)中是没有系统调用这个概念的,所以对于一直从事嵌入式软件开 发的人初次进入到大型操作系统(比如 Linux 和 Windows)开发环境中,会面临内核空间与用户空间概 念上的困惑。其实说到底,所谓系统调用就是软件有计划地调用 CPU 提供的特殊指令,触发 CPU 内部 产生一个中断,于是完成一次核内核外运行空间的切换,具体可以参考许多书籍。而所谓异常就是软件 无意的执行了一个非法指令(比如除 0)从而造成 CPU 内部引发一次中断。
外部中断特指外部设备发出的中断信号。但这几种中断的 CPU 处理过程基本相同,即:在执行完当 前指令后,或在执行当前指令期间,根据中断源所提供的“中断向量”,在内存中找到相应的 ISR(中断 服务例程)然后调用之。
不管是内部还是外部中断,系统都会根据接收到的中断信息,查询 idt 表。idt 表依照中断源的位置按序组成,并对应中断服务程序(以及异常处理程序)的入口地址。Linux 系统在初始化页式虚存管理的 初始化以后,便调用 trap_init 和 init_IRQ 两个函数进行中断机制的初始化。我们只介绍init_IRQ
。
中断系统和软中断
中断向量?中断请求号?这是个问题。 现在分析试图给大家解释一下,看看是不是这样的:IRQ 是设备相关的号码,一般生产厂商都会使 自己的设备分配到一个合适的号码。而中断向量就纯粹是操作系统中关于如何处理中断的内存组织结构, 它们之间存在某种映射关系,这种关系是由 CPU 体系结构以及操作系统决定的。那么在 IA32 体系的 Linux 中,是一种直接映射的关系,所有的 IRQ 号产生的中断全部映射到从 INT_vec[32]
开始的内存中。为什么 要从第 32 个单元开始呢?
上图中彩色部分都是系统能处理的中断,intel CPU 保留使用的中断向量是 0~32,根本不可能有哪 一种设备会使用这个区域的中断向量,这一部分就是我们常说的异常处理函数,还有一个比较特殊的中 断向量号 0x80
(即 128)就是系统调用号,由于不可能由外部设备引发这类中断,它们就被统称为内部 中断,这就是为什么要从第 32 个单元开始的原因。内核用调用 trap_init
函数挂接与之相应的中断处理函 数。接着系统调用init_IRQ
函数来初始化外部中断向量,其中断处理函数的挂接由各驱动程序自己完成。 由上图可以看出中断向量和中断请求号是相关但却不是一个东西:前者是内核中存在的一块内存,专门 存放中断处理函数的地址(指逻辑上,具体实现比较复杂) ,而后者就是一个概念,在内核中不必存在这 么一种变量,它可能就是中断向量的下标。
在 2.6 的内核中,中断相关的宏已经变化了,2.4 内核中的中断概念请看《Linux 内核源代码情景分 析 》。 在 2.6 内核的 entry.S
文件中,有一 个 interrupt 的定义, 它 放在.data
节中,然 后 ,在 include/asm-i386/hw_irq.h 中引用这个变量,最后在 arch/i386/kernel/i8259.c 中初始化这个变量。
下面两个代码片断是 2.4 内核关于这个 interrupt 变量的初始化:
1 #define IRQ(x,y) \
2 IRQ##x##y##_interrupt
3
4 #define IRQLIST_16(x) \
5 IRQ(x,0), IRQ(x,1), IRQ(x,2), IRQ(x,3), \
6 IRQ(x,4), IRQ(x,5), IRQ(x,6), IRQ(x,7), \
7 IRQ(x,8), IRQ(x,9), IRQ(x,a), IRQ(x,b), \
8 IRQ(x,c), IRQ(x,d), IRQ(x,e), IRQ(x,f)
9
10 void (*interrupt[NR_IRQS])(void) = {
11 IRQLIST_16(0x0),
12 ...
经过编译器的预处理,interrupt 这个函数指针数组变成:
1 void (*interrupt[NR_IRQS])(void) = {
2 IRQ0x00_interrupt,
3 IRQ0x01_interrupt,
4 IRQ0x02_interrupt,
5 ……
6 IRQ0x0f_interrupt,
7 }
从代码中看出,这样的初始化不太灵活,扩展性比较差。下面给出 2.6 内核关于 interrupt 的使用方式。 首先在 entry.S 中汇编代码如下:
在 hw_irq.h
中有这样的定义:extern void (*interrupt[NR_IRQS])(void);
在此,NR_IRQS
是 224
。具体 的初始化如下:
1 void __init init_IRQ(void)
2 {
3 int i;
4
5 /* all the set up before the call gates are initialised */
6 pre_intr_init_hook();
7
8 /*
9 * 扫描整个中断向量表
10 */
11 for (i = 0; i < (NR_VECTORS - FIRST_EXTERNAL_VECTOR); i++) {
12 int vector = FIRST_EXTERNAL_VECTOR + i;
13 if (i >= NR_IRQS)
14 break;