摘要
本文对Linux2.4.0中中断机制从源码层面进行简要介绍,阅读需要有一定基础,详细版本请参考这里。
这里主要介绍以下几个部分:
1.中断向量表IDT的初始化
主要是设置中断向量表中中断服务的类型,服务程序的入口地址,DPL等。
1.1 trap_init()初始化系统保留的中断向量,从0x00到0x1f共36个;init_IRQ()初始化用于外设的通用中断向量,0x20~0xff共224个,第0x80向量用于系统调用中断,不包含在内。
1.2设置通用中断向量的服务程序的入口地址interrupt[224]。
1.3对于用于外设的通用中断向量,需要结构irq_desc[224],每个元素代表一个中断请求队列,具有相同“中断请求号(0x20~0xff)”的中断请求需要挂到相应的irq_desc中。
2.中断请求队列的初始化(只说明通用中断)
某个设备的初始化程序将其中断服务程序通过request_irq()挂入到某个中断请求队列irq_desc中。
3.中断的响应和服务
3.1以IRQ0x03_interrup为例,首先将其中断请求号0x03-256压栈,调用common_interrupt,主要是保护现场,将所有寄存器压栈,将中断返回地址ret_from_intr压栈,调用主服务do_IRQ(struct pt_regs regs)
3.2从参数regs中获取关键的中断请求号irq,根据irq找到相应的中断请求队列irq_desc[irq],执行队列中的服务程序。
4.软中断
软中断的目的是为了一个中断服务占用过长cpu时间,将中断服务分成两部分,第一部分仅仅完成一些关键的操作,然后就开中断,中断服务第二部分可能耗时比较长,就将其放在开中断下进行。
我们知道正常的中断处理程序是在do_IRQ中完成的,而软中断的第二部分则是在每次中断处理完do_IRQ后判断是否有软中断请求,进行进入软中断第二部分的服务程序。
这里我们需要struct softirq_action softirq_vec[32]数据结构
struct softirq_action
{
void (*action)(struct softirq_action *);
void *data;
};
它和irq_desc[224]类似,但是刻印看出它并不是一个队列,里面仅仅保存的是不同软中断的入口函数指针,这些函数指针在softirq_init()中设置。
在软中断初始化时,系统执行 softirq_init()
open_softirq(TASKLET_SOFTIRQ, tasklet_action, NULL);
open_softirq(HI_SOFTIRQ, tasklet_hi_action, NULL);
open_softirq()
softirq_vec[nr].data = data;
softirq_vec[nr].action = action;
for (i=0; i<NR_CPUS; i++)
softirq_mask(i) |= (1<<nr);
spin_unlock_irqrestore(&softirq_mask_lock, flags);
将tasklet_action和tasklet_hi_action两种软中断初始化到softirq_vec[32]数组中。而且由于多处理器的原因,需要数据结构irq_cpustat_t irq_stat[NR_CPUS]
typedef struct {
unsigned int __softirq_active;//软中断请求寄存器
unsigned int __softirq_mask;//软中断屏蔽寄存器
unsigned int __local_irq_count;
unsigned int __local_bh_count;
unsigned int __syscall_count;
unsigned int __nmi_count; /* arch dependent */
} ____cacheline_aligned irq_cpustat_t;
来表示每个CPU的软中断请求寄存器,软中断屏蔽寄存器,可以看出软中断的中断是以软件的方式实现的。这样哪个CPU发生的软中断由自己进行处理。open_soft_irq()中最后for循环就是将第TASKLET_SOFTIRQ和HI_SOFTIRQ中断屏蔽位置1,表明允许中断。
对于tasklet_action和tasklet_hi_action两种软中断,每个cpu需要软中断服务程序队列头,使得可以将第二部分的中断服务程序挂到相应cpu队列头上。这个数据结构为tasklet_vec[NR_CPUS]和tasklet_hi_vec[NR_CPUS]
struct tasklet_head
{
struct tasklet_struct *list;
} __attribute__ ((__aligned__(SMP_CACHE_BYTES)));
我们说要将软中断服务程序挂到相应的cpu队列头中,这个数据结构就是tasklet_struct
struct tasklet_struct
{
struct tasklet_struct *next;//挂到某个cpu队头
unsigned long state;
atomic_t count;
void (*func)(unsigned long);//具体中断服务程序
unsigned long data;
};
当内核执行完do_IRQ()后,检查是否有软中断请求,并通过do_softirq()执行。
具体就是先获取当前cpu编号,通过
active = softirq_active(cpu) & mask;
查看当前cpu是否有可执行未屏蔽的软中断。active的每一位对应一个软中断号,与softirq_vec数组中的每一项对应,因此通过
h = softirq_vec;
mask &= ~active;
do {
if (active & 1)
h->action(h);
h++;
active >>= 1;
} while (active);
依次执行未屏蔽已发出请求的软中断,我们知道,之前softirq_vec数组中设置了两个软中断服务类型:tasklet_action和tasklet_hi_action,h->action(h)可能就执行了其中一个。我们以tasklet_action为例。
static void tasklet_action(struct softirq_action *a)
{
int cpu = smp_processor_id();
struct tasklet_struct *list;
local_irq_disable();
list = tasklet_vec[cpu].list;
tasklet_vec[cpu].list = NULL;
local_irq_enable();
while (list != NULL) {
struct tasklet_struct *t = list;
list = list->next;
if (tasklet_trylock(t)) {
if (atomic_read(&t->count) == 0) {
clear_bit(TASKLET_STATE_SCHED, &t->state);
t->func(t->data);
#ifdef CONFIG_SMP
smp_mb__before_clear_bit();
#endif
tasklet_unlock(t);
continue;
}
tasklet_unlock(t);
}
local_irq_disable();
t->next = tasklet_vec[cpu].list;
tasklet_vec[cpu].list = t;
__cpu_raise_softirq(cpu, TASKLET_SOFTIRQ);
local_irq_enable();
}
}
主要就是先获取是哪个cpu要执行软中断服务,获取相应cpu软中断服务队列头tasklet_vec[cpu].list,依次执行队列中tasklet_struct结构中func函数指针指向的真正的中断服务程序。
linux中断机制结构图
参考资料: