博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Linux内核源代码情景分析-中断半
阅读量:6309 次
发布时间:2019-06-22

本文共 11569 字,大约阅读时间需要 38 分钟。

    一、中断初始化

    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");

    2、中断请求队列的初始化

    在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, &regs, action);//action是中断请求队列的头指针,irq为0,		spin_lock(&desc->lock);				if (!(desc->status & IRQ_PENDING))			break;		desc->status &= ~IRQ_PENDING;	}	desc->status &= ~IRQ_INPROGRESS;//处理完,把IRQ_INPROCESS位置0out:	/*	 * 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, &regs, 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_returnrestore_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_eax3:	iret;
state		=  0flags		=  4sigpending	=  8addr_limit	= 12exec_domain	= 16need_resched	= 20tsk_ptrace	= 24processor	= 52

版权声明:本文博主原创文章。博客,未经同意不得转载。

你可能感兴趣的文章
在构造函数中使用new时的注意事项
查看>>
Developing a plugin framework in ASP.NET MVC with medium trust
查看>>
在Windows下使用nmake+Makefile+编译ZThread库(附例子)
查看>>
Android应用程序资源的编译和打包过程分析
查看>>
深度优先搜索的图文介绍
查看>>
Oralce进程信息查看,Oracle的锁表与解锁
查看>>
浅谈千万级PV/IP规模高性能高并发网站架构
查看>>
头文件 string.h cstring string 区别
查看>>
js判断是否安装flash
查看>>
editplus快捷键大全
查看>>
移动touch事件之一
查看>>
约瑟夫环问题
查看>>
location.href的用户总结
查看>>
RDIFramework.NET ━ .NET快速信息化系统开发框架 ━ 工作流程组件Web业务平台
查看>>
js中()()问题
查看>>
iOS 导航栏rgb值与设置的有差异
查看>>
Win10 UWP系列:更新UWP时注意的问题——TargetDeviceFamily
查看>>
内部类与匿名内部类
查看>>
altera tcl
查看>>
洛谷P1607 [USACO09FEB]庙会班车Fair Shuttle
查看>>