Linux内核源代码情景分析-中断半
一、中断初始化
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
版权声明:本文博主原创文章。博客,未经同意不得转载。