Linux中断初始化分析
简介:本篇报告首先从定义以及基本概念入手介绍中断的相关知识,接着重点分析了linux内核中中断初始化的过程,本文的所有代码均依据2.6.36版内核源码。
一、中断的基本概念
中断最初是用来克服CPU对I/O接口采用程序查询的服务方式从而导致处理器低效率,由此而引入的一种让CPU及时响应I/O的服务方式,更通俗的来讲,就是CPU在做自己的事情,突然之间有一个硬件信号打断了CPU的执行,并要求CPU为其执行相应的代码片段。为此计算机必须有相关的软硬件来为服务提供支持,硬件方面添加了两片8259A,软件方面则体现在操作系统里的相关代码。
要想更加明白中断的原理就必须先明确几个相关的概念:
u 中断源:顾名思义就是打断CPU执行的事件,可以是硬件的也可以是软件的,更详细的划分可以分成三类,第一类是CPU异常(执行指令出错)和非屏蔽中断(硬件错误),第二类是可屏蔽中断(I/O请求),第三类是软中断。
u 中断号:两片8259A芯片级连后有IRQ0~IRQ15(除没有IRQ2)共15根中断请求线,系统为每一根中断请求线分配一个中断号,而这15个中断号分别对应特定的硬件中断。
u 中断向量:随着硬件的发展,以及软件中断的需要,15个中断号根本无法满足这些中断请求,为此引出了中断向量的概念。按中断向量来为上面三类中断划分:第一类0~31,第二类32~47,第三类48~255(第128号中断向量作为系统调用),此时256个中断向量共享这15根中断线。
u 中断服务程序:为每一种中断提供服务的一段代码。
u 中断向量表:也称为中断描述符表,简单的来说就是为中断服务提供索引,这个索引包括中断向量,以及与之对应的中断服务程序的入口地址,由于中断向量共256个,也就对应表中的256个表项,每一个表项又称为门描述符,用8个字节来描述。
二、中断的核心数据结构
为了描述中断的初始化过程,光有上面这些概念还是远远不够的,为此还必须知道与中断相关的几个核心数据结构,在这里主要描述其中三个,分别是struct irq_desc、struct irqaction、struct irq_chip,下面给出最重要或与初始化过程相关一些成员。
struct irq_desc
{
irq_flow_handler_t handle_irq;/*高层次的中断事件处理函数*/
struct irq_chip *chip;/*指向中断控制器结构体*/
struct irqaction *action;/*提供中断处理相关信息的结构体,如果是采用共享中断方式,它指向的将是一个链表*/
unsigned int status;/*中断的状态,如关中断*/
unsigned int depth;/*中断的深度*/
const char *name;/*产生中断的名称*/
}
struct irqaction
{
irq_handler_t handler;/*指向中断服务程序的指针*/
unsigned long flags;/*中断标志,如标志共享中断方式*/
const char *name;/*产生中断的设备名称*/
void dev_id;/*采用共享中断方式时,用来标识设备的id*/
struct irqaction *next;/*采用共享中断时,指向下一个中断的指针*/
int irq;/*中断号*/
}
struct irq_chip
{
void (*startup)(unsigned int irq);
…...
}
通过对上面结构体一些成员的分析,可以看出最核心的就是struct irq_desc结构体,它是门描述符,即中断描述符的表项,它通过*chip成员与中断控制器结构体struct irq_chip关联,通过*action与描述中断行为的结构体struct irqaction关联,必须注意的是在头文件中定义了irq_desc[]数组,这个数组的长度是224,也就是可屏蔽中断和软中断的中断向量的长度,IRQn对应的默认向量变成n+32,在这里对第一类中断进行特殊处理。
三、进一步分析源代码
在2.6.36版内核中,描述中断初始化的源文件是arch/x86/kernel/irqinit.c,而定义中断初始化的函数则是其中的init_ISA_irqs,下面给出其代码并简述其含义:
void __init init_ISA_irqs(void)
{
int i;
#if defined(CONFIG_X86_64) || defined(CONFIG_X86_LOCAL_APIC)
init_bsp_APIC();
#endif
legacy_pic->init(0);
for (i = 0; i < legacy_pic->nr_legacy_irqs; i++) {
struct irq_desc *desc = irq_to_desc(i);
desc->status = IRQ_DISABLED;
desc->action = NULL;
desc->depth = 1;
set_irq_chip_and_handler_name(i, &i8259A_chip,handle_level_irq, "XT");
}
}
如果硬件架构采用X86系列的64位或X86系列的LOCAL_APIC,则采用init_bsp_APIC()函数来初始化芯片的相关设置,其他构架则采用默认的legacy_pic->init(0)函数来初始化芯片设置,struct legacy_pic结构体中主要是勾子函数,包括void (*init)(int auto_eoi),int (*irq_pending)(unsigned int irq)等成员。根据不同的体系构架最终调用的init()函数将有所不同,以X86体系为例,用legacy_pic结构体定义了一个变量,并对其进行赋值,代码如下:struct legacy_pic *legacy_pic = &default_legacy_pic;而对default_legacy_pic变量赋值的代码如下:
struct legacy_pic default_legacy_pic = {
.nr_legacy_irqs = NR_IRQS_LEGACY,
.chip = &i8259A_chip,
.mask_all = mask_8259A,
.restore_mask = unmask_8259A,
.init = init_8259A,
.irq_pending = i8259A_irq_pending,
.make_irq = make_8259A_irq,
};
其中在X86体系下NR_IRQS_LEGACY的值为16,init_8259A函数则主要是对中断控制字的设置。
下面则主要是for循环的执行,共循环了16次,首先定义指向irq_desc数组成员的指针,这里对irq_to_desc(i)采用宏定义:#define irq_to_desc(irq) (&irq_desc[irq]),接下来设置desc结构体中的status、action、depth三个字段。最关键的则是 set_irq_chip_and_handler_name()函数的执行,这个函数的定义如下:
set_irq_chip_and_handler_name(unsigned int irq, struct irq_chip *chip,irq_flow_handler_t handle, const char *name)
{
set_irq_chip(irq, chip);
__set_irq_handler(irq, handle, 0, name);
}
这个函数接受的参数有中断号、中断控制器结构体、高层次的中断事件处理函数、产生的中断名称。
封装内的两个函数的定义如下:
int set_irq_chip(unsigned int irq, struct irq_chip *chip)
{
struct irq_desc *desc = irq_to_desc(irq);
unsigned long flags;
if (!desc) {
WARN(1, KERN_ERR "Trying to install chip for IRQ%d/n", irq);
return -EINVAL;
}
if (!chip)
chip = &no_irq_chip;
raw_spin_lock_irqsave(&desc->lock, flags);
irq_chip_set_defaults(chip);
desc->chip = chip;
raw_spin_unlock_irqrestore(&desc->lock, flags);
return 0;
}
__set_irq_handler(unsigned int irq, irq_flow_handler_t handle, int is_chained,const char *name)
{
struct irq_desc *desc = irq_to_desc(irq);
unsigned long flags;
if (!desc) {
printk(KERN_ERR"Trying to install type control for IRQ%d/n", irq);
return;
}
if (!handle)
handle = handle_bad_irq;
else if (desc->chip == &no_irq_chip) {
printk(KERN_WARNING "Trying to install %sinterrupt handler "
"for IRQ%d/n", is_chained ? "chained " : "", irq);
desc->chip = &dummy_irq_chip;
}
}
这两个函数判断在chip指针为空时会将其值赋为&no_irq_chip,这是不需要中断控制器的情况,而在chip指针不为空时将其值赋为&dummy_irq_chip,也就是有中断控制器的情况,也就是前16个门描述符表需要设置相关的控制器,而后面的则不需要进行控制器的设置。
初始化函数定义好了,现在来看看调用它的函数在哪?
void __init native_init_IRQ(void)
{
int i;
x86_init.irqs.pre_vector_init();
apic_intr_init();
for (i = FIRST_EXTERNAL_VECTOR; i < NR_VECTORS; i++) {
if (!test_bit(i, used_vectors))
set_intr_gate(i, interrupt[i-FIRST_EXTERNAL_VECTOR]);
}
if (!acpi_ioapic)
setup_irq(2, &irq2);
#ifdef CONFIG_X86_32
if (boot_cpu_data.hard_math && !cpu_has_fpu)
setup_irq(FPU_IRQ, &fpu_irq);
irq_ctx_init(smp_processor_id());
#endif
}
在这个函数中,如果将x86_init.irqs.pre_vector_init()展开,则会发现pre_vector_init是一个函数指针,在x86_init.c里有对它的赋值.pre_vector_inti = init_ISA_irqa,也就完成了调用init_ISA_irqs 函数的任务。我们现在主要来看for循环,先看FIRST_EXTERNAL_VECTOR 的值是0x20,也就是第32号中断向量,NR_VECTORS 的值为256,也就是设置第二类和第三类的各个门描述符。set_intr_gate(n,*add)则会调用_set_gate(int gate, unsigned type, void *addr,unsigned dpl, unsigned ist, unsigned seg),根据n设置IDT的第n个表项,并且会将段选择符设置成代码段的段选择符,还包括类型码,请求优先级dpl,以及中断服务程序的偏移地址。
以上所述是单处理机的初始化过程,多处理机的初始化涉及到中断向量的设置,以及对中断门的分配,其初始化函数在smp_intr_init()里定义,在native_init_IRQ函数里被apic_intr_init()函数调用,在这里对其初始化过程不作详细描述。
四、用流程图展现函数调用过程