一、中断初始化
1、中断向量表IDT的初始化
void __init init_IRQ(void)
{
int i;
#ifndef CONFIG_X86_VISWS_APIC
init_ISA_irqs();
#else
init_VISWS_APIC_irqs();
#endif
/*
* Cover the whole vector space, no vector can escape
* us. (some of these will be overridden and become
* 'special' SMP interrupts)
*/
for (i = 0; i < NR_IRQS; i++) {//NR_IRQS为224
int vector = FIRST_EXTERNAL_VECTOR + i;//FIRST_EXTERNAL_VECTOR为0x20
if (vector != SYSCALL_VECTOR)//SYSCALL_VECTOR为0x80
set_intr_gate(vector, interrupt[i]);
}
......
}
对从0x20到224的中断向量,设置中断处理程序,set_intr_gate如下:
void set_intr_gate(unsigned int n, void *addr)
{
_set_gate(idt_table+n,14,0,addr);
}
在IDT中设置了门描述符,如下图:
Selector为_KERNEL_CS。P为1;DPL为00;DT为0;TYPE为14,中断门。Offset就是interrupt[i]的偏移。
那么interrupt[i]是什么函数呢?经过若干宏定义的展开,如下:
void (*interrupt[NR_IRQS])(void) = {
IRQ0x00_interrupt,IRQx01_interrupt,.....IRQx0F_interrupt};
IRQ0x00_interrupt,经过若干宏定义的展开,如下:
asmlinkage void IRQ0X00_interrupt();
__asm__( \
"\n" \
"IRQ0X00_interrupt: \n\t" \
"pushl $0x00 - 256 \n\t" \
"jmp common_interrupt");
在init_IRQ调用init_ISA_irqs,如下:
void __init init_ISA_irqs (void)
{
int i;
init_8259A(0);
for (i = 0; i < NR_IRQS; i++) {//NR_IRQS为224
irq_desc[i].status = IRQ_DISABLED;
irq_desc[i].action = 0;
irq_desc[i].depth = 1;
if (i < 16) {
/*
* 16 old-style INTA-cycle interrupts:
*/
irq_desc[i].handler = &i8259A_irq_type;//将开头16个中断请求队列的handler指针设置成指向数据结构
} else {
/*
* 'high' PCI IRQs filled in on demand
*/
irq_desc[i].handler = &no_irq_type;
}
}
}
irq_dec[i]的成员变量action就是这个中断请求队列的头。i从0~15一共16个中断请求队列。其成员变量handler对该共用"中断通道"的控制,enable和disable用来开启和关断其所属通道,ack用于对中断控制器的响应,而end用于每次中断服务返回的前夕。
这些数据结构定义如下:
struct hw_interrupt_type {
const char * typename;
unsigned int (*startup)(unsigned int irq);
void (*shutdown)(unsigned int irq);
void (*enable)(unsigned int irq);
void (*disable)(unsigned int irq);
void (*ack)(unsigned int irq);
void (*end)(unsigned int irq);
void (*set_affinity)(unsigned int irq, unsigned long mask);
};
typedef struct hw_interrupt_type hw_irq_controller;
/*
* This is the "IRQ descriptor", which contains various information
* about the irq, including what kind of hardware handling it has,
* whether it is disabled etc etc.
*
* Pad this out to 32 bytes for cache and indexing reasons.
*/
typedef struct {
unsigned int status; /* IRQ status */
hw_irq_controller *handler;
struct irqaction *action; /* IRQ action list */
unsigned int depth; /* nested irq disables */
spinlock_t lock;
} ____cacheline_aligned irq_desc_t;
extern irq_desc_t irq_desc [NR_IRQS];
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
};
struct irqaction {
void (*handler)(int, void *, struct pt_regs *);
unsigned long flags;
unsigned long mask;
const char *name;
void *dev_id;
struct irqaction *next;
};
二、将irqaction数据结构的irq0,链入相应的中断请求队列
void __init time_init(void)
{
......
setup_irq(0, &irq0);
......
}
其中irq0,如下:
static struct irqaction irq0 = { timer_interrupt, SA_INTERRUPT, 0, "timer", NULL, NULL};
结合上面的struct irqaction去理解。
setup_irq,如下:
int setup_irq(unsigned int irq, struct irqaction * new)//irq为中断请求号
{
int shared = 0;
unsigned long flags;
struct irqaction *old, **p;
irq_desc_t *desc = irq_desc + irq;//找到对应的通道
/*
* Some drivers like serial.c use request_irq() heavily,
* so we have to be careful not to interfere with a
* running system.
*/
if (new->flags & SA_SAMPLE_RANDOM) {
/*
* This function might sleep, we want to call it first,
* outside of the atomic block.
* Yes, this might clear the entropy pool if the wrong
* driver is attempted to be loaded, without actually
* installing a new handler, but is this really a problem,
* only the sysadmin is able to do this.
*/
rand_initialize_irq(irq);
}
/*
* The following block of code has to be executed atomically
*/
spin_lock_irqsave(&desc->lock,flags);
p = &desc->action;//找到通道对应的中断处理队列
if ((old = *p) != NULL) {//如果中断请求队列中已经有元素了
/* Can't share interrupts unless both agree to */
if (!(old->flags & new->flags & SA_SHIRQ)) {//那么需要原元素和新元素的flags都为SA_SHIRQ,表示与其他中断源公用该中断请求通道
spin_unlock_irqrestore(&desc->lock,flags);
return -EBUSY;
}
/* add new interrupt at end of irq queue */
do {
p = &old->next;
old = *p;
} while (old);//链入对应的位置
shared = 1;
}
*p = new;//如果中断请求队列没有元素,则直接把irq0链入中断请求队列
if (!shared) {//第一个元素链入后
desc->depth = 0;
desc->status &= ~(IRQ_DISABLED | IRQ_AUTODETECT | IRQ_WAITING);//status为0
desc->handler->startup(irq);
}
spin_unlock_irqrestore(&desc->lock,flags);
register_irq_proc(irq);
return 0;
}
三、中断响应
我们拿时钟中断,举例说明。假设已经发生了时钟中断。
1、执行中断处理函数之前
如果中断发生在用户态,则会形成如下图:
(1)、CPU从中断控制器取得中断向量,然后根据具体的中断向量(本例中为0x20),从中断向量表IDT中找到相应的表项,而该表项应该是一个中断门。
首先把用户态堆栈的SS,用户堆栈的ESP,EFLAGS,用户空间的CS,EIP存入到系统堆栈中(从TSS中获取)。
(2)、CPU根据中断门的设置到达了该通道的总服务程序的入口。
asmlinkage void IRQ0X00_interrupt();
__asm__( \
"\n" \
"IRQ0X00_interrupt: \n\t" \
"pushl $0x00 - 256 \n\t" \
"jmp common_interrupt");
把中断号-256压入堆栈。
#define BUILD_COMMON_IRQ() \
asmlinkage void call_do_IRQ(void); \
__asm__( \
"\n" __ALIGN_STR"\n" \
"common_interrupt:\n\t" \
SAVE_ALL \
"pushl $ret_from_intr\n\t" \
SYMBOL_NAME_STR(call_do_IRQ)":\n\t" \
"jmp "SYMBOL_NAME_STR(do_IRQ));
#define SAVE_ALL \
"cld\n\t" \
"pushl %es\n\t" \
"pushl %ds\n\t" \
"pushl %eax\n\t" \
"pushl %ebp\n\t" \
"pushl %edi\n\t" \
"pushl %esi\n\t" \
"pushl %edx\n\t" \
"pushl %ecx\n\t" \
"pushl %ebx\n\t" \
"movl $" STR(__KERNEL_DS) ",%edx\n\t" \
"movl %edx,%ds\n\t" \
"movl %edx,%es\n\t"
执行完成SAVE_ALL后,就形成了如上图一样的堆栈。此时cs已经是_KERNEL_CS了,ds和es为_KERNEL_DS。
然后把ret_from_intr也压入堆栈,并执行do_IRQ。
2、执行中断处理函数
asmlinkage unsigned int do_IRQ(struct pt_regs regs)//就是上面堆栈中的内容
{
/*
* We ack quickly, we don't want the irq controller
* thinking we're snobs just because some other CPU has
* disabled global interrupts (we have already done the
* INT_ACK cycles, it's too late to try to pretend to the
* controller that we aren't taking the interrupt).
*
* 0 return value means that this irq is already being
* handled by some other CPU. (or is disabled)
*/
int irq = regs.orig_eax & 0xff; //取得了中断号,为0
int cpu = smp_processor_id();
irq_desc_t *desc = irq_desc + irq;//找到对应的通道
struct irqaction * action;
unsigned int status;
kstat.irqs[cpu][irq]++;
spin_lock(&desc->lock);
desc->handler->ack(irq);//我已经处理了
/*
REPLAY is when Linux resends an IRQ that was dropped earlier
WAITING is used by probe to mark irqs that are being tested
*/
status = desc->status & ~(IRQ_REPLAY | IRQ_WAITING);
status |= IRQ_PENDING;//status为IRQ_PENDING
/*
* If the IRQ is disabled for whatever reason, we cannot
* use the action we have.
*/
action = NULL;
if (!(status & (IRQ_DISABLED | IRQ_INPROGRESS))) {//status为IRQ_PENDING,执行下面代码
action = desc->action;//找到通道对应的中断处理队列
status &= ~IRQ_PENDING; //status为0,把IRQ_PENDING位清零了
status |= IRQ_INPROGRESS; // status为IRQ_INPROCESS
}
desc->status = status;//desc->status为IRQ_INPROCESS
/*
* If there is no IRQ handler or it was disabled, exit early.
Since we set PENDING, if another processor is handling
a different instance of this same irq, the other processor
will take care of it.
*/
if (!action)//如果action为NULL,直接退出
goto out;
/*
* Edge triggered interrupts need to remember
* pending events.
* This applies to any hw interrupts that allow a second
* instance of the same irq to arrive while we are in do_IRQ
* or in the handler. But the code here only handles the _second_
* instance of the irq, not the third or fourth. So it is mostly
* useful for irq hardware that does not mask cleanly in an
* SMP environment.
*/
for (;;) {
spin_unlock(&desc->lock);
handle_IRQ_event(irq, ®s, action);//action是中断请求队列的头指针,irq为0,
spin_lock(&desc->lock);
if (!(desc->status & IRQ_PENDING))
break;
desc->status &= ~IRQ_PENDING;
}
desc->status &= ~IRQ_INPROGRESS;//处理完,把IRQ_INPROCESS位置0
out:
/*
* The ->end() handler has to deal with interrupts which got
* disabled while the handler was running.
*/
desc->handler->end(irq);//开中断
spin_unlock(&desc->lock);
if (softirq_active(cpu) & softirq_mask(cpu))//处理中断下半部
do_softirq();
return 1;
}
struct pt_regs {
long ebx;
long ecx;
long edx;
long esi;
long edi;
long ebp;
long eax;
int xds;
int xes;
long orig_eax;
long eip;
int xcs;
long eflags;
long esp;
int xss;
};
handle_IRQ_event,代码如下:
int handle_IRQ_event(unsigned int irq, struct pt_regs * regs, struct irqaction * action)
{
int status;
int cpu = smp_processor_id();
irq_enter(cpu, irq);
status = 1; /* Force the "do bottom halves" bit */
if (!(action->flags & SA_INTERRUPT))//如果这个标志位置0,那么要在开中断的情况下执行
__sti();//开中断
do {
status |= action->flags;
action->handler(irq, action->dev_id, regs);//依次执行中断请求队列上的中断处理函数
action = action->next;
} while (action);
if (status & SA_SAMPLE_RANDOM)
add_interrupt_randomness(irq);
__cli();//关中断
irq_exit(cpu, irq);
return status;
}
在本例中,中断处理函数为timer_interrupt(action->handler)。我们看到大部分中断处理函数都是在关中断下执行的。但是action->flags的SA_INTERRUPT置0,是在开中断的情况下执行的。
如果执行中断处理函数时,处于开中断的情况,而且此时恰好是同一通道的中断,也就是irq中断号(假设都为0)一样。由于上一次中断还没有退出,此时desc->status为IRQ_INPROGRESS。我们看这段代码:
status = desc->status & ~(IRQ_REPLAY | IRQ_WAITING);
status |= IRQ_PENDING;//此时status为IRQ_PENDING| IRQ_INPROGRESS
/*
* If the IRQ is disabled for whatever reason, we cannot
* use the action we have.
*/
action = NULL;
if (!(status & (IRQ_DISABLED | IRQ_INPROGRESS))) {//不执行下面程序
action = desc->action;
status &= ~IRQ_PENDING;
status |= IRQ_INPROGRESS;
}
desc->status = status;//desc->status为IRQ_PENDING| IRQ_INPROGRESS
if (!action)//action为NULL,退出
goto out;
for (;;) {
spin_unlock(&desc->lock);
handle_IRQ_event(irq, ®s, action);
spin_lock(&desc->lock);
if (!(desc->status & IRQ_PENDING))//由于新的中断执行时desc->status为IRQ_PENDING| IRQ_INPROGRESS,所以继续执行for循环
break;
desc->status &= ~IRQ_PENDING;//desc->status为IRQ_INPROGRESS
}
这样就把发生在同一通道上的中断嵌套化解成为一个循环了。
我们继续分析中断处理函数,timer_interrupt,代码如下:
static void timer_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
int count;
......
do_timer_interrupt(irq, NULL, regs);
write_unlock(&xtime_lock);
}
static inline void do_timer_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
......
do_timer(regs);
......
}
void do_timer(struct pt_regs *regs)
{
(*(unsigned long *)&jiffies)++;
#ifndef CONFIG_SMP
/* SMP process accounting uses the local APIC timer */
update_process_times(user_mode(regs));//与进程调度有关
#endif
mark_bh(TIMER_BH);//中断下半部相关
if (TQ_ACTIVE(tq_timer))
mark_bh(TQUEUE_BH);
}
执行完中断处理函数之后,返回do_IRQ,会检查是否有中断下半部需要执行,如果需要执行,则调用do_softirq,下半部是在开中断的情况下开始执行的。
3、执行中断处理函数之后
do_IRQ执行完毕后,会调用ret,返回到ret_from_intr执行,代码如下:
ENTRY(ret_from_intr)
GET_CURRENT(%ebx) //将指向当前进程的task_struct结构的指针置入寄存器EBX
movl EFLAGS(%esp),%eax # mix EFLAGS and CS
movb CS(%esp),%al
testl $(VM_MASK | 3),%eax //看发生中断时是否处于用户态
jne ret_with_reschedule //如果处于用户态,那么执行ret_with_reschedule
jmp restore_all
ret_with_reschedule:
cmpl $0,need_resched(%ebx)//查看该task_struct结构中位移为need_resched处的内容
jne reschedule
cmpl $0,sigpending(%ebx)//查看该task_struct结构中位移为sigpending处的内容
jne signal_return
restore_all:
RESTORE_ALL
signal_return:
sti # we can get here from an interrupt handler
testl $(VM_MASK),EFLAGS(%esp)
movl %esp,%eax
jne v86_signal_return
xorl %edx,%edx
call SYMBOL_NAME(do_signal)//处理信号
jmp restore_all
restore_all:
RESTORE_ALL
#define RESTORE_ALL \ //返回中断前
popl %ebx; \
popl %ecx; \
popl %edx; \
popl %esi; \
popl %edi; \
popl %ebp; \
popl %eax; \
1: popl %ds; \
2: popl %es; \
addl $4,%esp; \ //跳过orig_eax
3: iret;
state = 0
flags = 4
sigpending = 8
addr_limit = 12
exec_domain = 16
need_resched = 20
tsk_ptrace = 24
processor = 52