Linux内核异常和中断的代码分析

本篇博客的是基于linux 2.6版本、架构是基于ARM,底层硬件是基于s3c24xx
之前在分析内核启动过程时知道了内核在启动过程中执行的第一个C函数是start_kernel,这个函数做一系列的软件上的初始化,其中和中断有关的是如下两个函数
trap_init();
init_IRQ();

void __init trap_init(void)
{
	unsigned long vectors = CONFIG_VECTORS_BASE;
	extern char __stubs_start[], __stubs_end[];
	extern char __vectors_start[], __vectors_end[];
	extern char __kuser_helper_start[], __kuser_helper_end[];
	int kuser_sz = __kuser_helper_end - __kuser_helper_start;

	/*
	 * Copy the vectors, stubs and kuser helpers (in entry-armv.S)
	 * into the vector page, mapped at 0xffff0000, and ensure these
	 * are visible to the instruction stream.
	 */
	memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
	memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);
	memcpy((void *)vectors + 0x1000 - kuser_sz, __kuser_helper_start, kuser_sz);

	/*
	 * Copy signal return handlers into the vector page, and
	 * set sigreturn to be a pointer to these.
	 */
	memcpy((void *)KERN_SIGRETURN_CODE, sigreturn_codes,
	       sizeof(sigreturn_codes));

	flush_icache_range(vectors, vectors + PAGE_SIZE);
	modify_domain(DOMAIN_USER, DOMAIN_CLIENT);
}

void *memcpy(void *dest, const void *src, size_t count)
{
	char *tmp = dest;
	const char *s = src;

	while (count--)
		*tmp++ = *s++;
	return dest;
}

首先我们要将__vectors_start到__vectors_end这段内容复制到(void *)vectors所在的地址中去(也就是0xffff0000)
接下来的两行代码也是这个意思
根据注释the vectors, stubs and kuser helpers在entry-armv.S中,找到了它们的定义

	.globl	__vectors_start
__vectors_start:
	swi	SYS_ERROR0
	b	vector_und + stubs_offset
	ldr	pc, .LCvswi + stubs_offset
	b	vector_pabt + stubs_offset
	b	vector_dabt + stubs_offset
	b	vector_addrexcptn + stubs_offset
	b	vector_irq + stubs_offset
	b	vector_fiq + stubs_offset

	.globl	__vectors_end
__vectors_end:

可以看到异常向量表的代码基本上都是跳转到某个地址上去执行更为复杂的代码,更为复杂的代码就在__stubs_start到__stubs_end之间,由于代码比较多,我没有列出来
和中断相关的是b vector_irq + stubs_offset

	vector_stub	irq, IRQ_MODE, 4

	.long	__irq_usr			@  0  (USR_26 / USR_32)
	.long	__irq_invalid			@  1  (FIQ_26 / FIQ_32)
	.long	__irq_invalid			@  2  (IRQ_26 / IRQ_32)
	.long	__irq_svc			@  3  (SVC_26 / SVC_32)
	...
vector_\name:
	.if \correction
	sub	lr, lr, #\correction 
	.endif

	@
	@ Save r0, lr_<exception> (parent PC) and spsr_<exception>
	@ (parent CPSR)
	@
	stmia	sp, {r0, lr}		@ save r0, lr
	mrs	lr, spsr
	str	lr, [sp, #8]		@ save spsr

	@
	@ Prepare for SVC32 mode.  IRQs remain disabled.
	@
	mrs	r0, cpsr
	eor	r0, r0, #(\mode ^ SVC_MODE)
	msr	spsr_cxsf, r0

	@
	@ the branch table must immediately follow this code
	@
	and	lr, lr, #0x0f
	mov	r0, sp
	ldr	lr, [pc, lr, lsl #2]
	movs	pc, lr			@ branch to handler in SVC mode
	.endm

在用户态下产生的中断会跳进入 _irq_usr入口

__irq_usr:
	usr_entry

#ifdef CONFIG_TRACE_IRQFLAGS
	bl	trace_hardirqs_off
#endif
	get_thread_info tsk
#ifdef CONFIG_PREEMPT
	ldr	r8, [tsk, #TI_PREEMPT]		@ get preempt count
	add	r7, r8, #1			@ increment it
	str	r7, [tsk, #TI_PREEMPT]
#endif

	irq_handler
.macro	irq_handler
	get_irqnr_preamble r5, lr
1:	get_irqnr_and_base r0, r6, r5, lr
	movne	r1, sp
	@
	@ routine called with r0 = irq number, r1 = struct pt_regs *
	@
	adrne	lr, 1b
	bne	asm_do_IRQ

asm_do_IRQ

asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
{
	struct pt_regs *old_regs = set_irq_regs(regs);
	struct irq_desc *desc = irq_desc + irq;

	/*
	 * Some hardware gives randomly wrong interrupts.  Rather
	 * than crashing, do something sensible.
	 */
	if (irq >= NR_IRQS)
		desc = &bad_irq_desc;

	irq_enter();

	desc_handle_irq(irq, desc);

	/* AT91 specific workaround */
	irq_finish(irq);

	irq_exit();
	set_irq_regs(old_regs);
}

全局数组irq_desc的定义如下

struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = {
	[0 ... NR_IRQS-1] = {
		.status = IRQ_DISABLED,
		.chip = &no_irq_chip,
		.handle_irq = handle_bad_irq,
		.depth = 1,
		.lock = __SPIN_LOCK_UNLOCKED(irq_desc->lock),
#ifdef CONFIG_SMP
		.affinity = CPU_MASK_ALL
#endif
	}
};

在asm_do_IRQ函数中和中断处理密切相关的函数是desc_handle_irq(irq, desc)

static inline void desc_handle_irq(unsigned int irq, struct irq_desc *desc)
{
	desc->handle_irq(irq, desc);
}

desc_handle_irq工作是将desc中的字段handle_irq函数执行
handle_irq因为是一个函数指针,所以我们需要看它的初始化
在chip.c中

__set_irq_handler(unsigned int irq, irq_flow_handler_t handle, int is_chained,
		  const char *name)
{
	struct irq_desc *desc;
	unsigned long flags;

	...
	desc = irq_desc + irq;

	...

	/* Uninstall? */
	if (handle == handle_bad_irq) {
		if (desc->chip != &no_irq_chip)
			mask_ack_irq(desc, irq);
		desc->status |= IRQ_DISABLED;
		desc->depth = 1;
	}
	desc->handle_irq = handle;
	desc->name = name;

	if (handle != handle_bad_irq && is_chained) {
		desc->status &= ~IRQ_DISABLED;
		desc->status |= IRQ_NOREQUEST | IRQ_NOPROBE;
		desc->depth = 0;
		desc->chip->unmask(irq);
	}
	...
}

desc->handle_irq = handle; desc->handle_irq来自于__set_irq_handler这个函数传进来的handle
__set_irq_handler又被set_irq_handler调用
include/linux/irq.h

set_irq_handler(unsigned int irq, irq_flow_handler_t handle)
{
	__set_irq_handler(irq, handle, 0, NULL);
}

进一步可以找到在arch/arm/plat_s3c24xx中

/* s3c24xx_init_irq
 *
 * Initialise S3C2410 IRQ system
*/

void __init s3c24xx_init_irq(void)
{
	...
	set_irq_handler(irqno, handle_level_irq);
	...
	set_irq_handler(irqno, handle_edge_irq);
}

对于非级联中断,只有两种处理方式

handle_level_irq(unsigned int irq, struct irq_desc *desc)
{
	unsigned int cpu = smp_processor_id();
	struct irqaction *action;
	irqreturn_t action_ret;
	...
	/*
	 * If its disabled or no action available
	 * keep it masked and get out of here
	 */
	action = desc->action;
	...

	desc->status |= IRQ_INPROGRESS;
	desc->status &= ~IRQ_PENDING;
	spin_unlock(&desc->lock);

	action_ret = handle_IRQ_event(irq, action);
	if (!noirqdebug)
		note_interrupt(irq, desc, action_ret);

	spin_lock(&desc->lock);
	desc->status &= ~IRQ_INPROGRESS;
	if (!(desc->status & IRQ_DISABLED) && desc->chip->unmask)
		desc->chip->unmask(irq);
out_unlock:
	spin_unlock(&desc->lock);
}



handle_edge_irq(unsigned int irq, struct irq_desc *desc)
{
	const unsigned int cpu = smp_processor_id();

	spin_lock(&desc->lock);

	desc->status &= ~(IRQ_REPLAY | IRQ_WAITING);

	/*
	 * If we're currently running this IRQ, or its disabled,
	 * we shouldn't process the IRQ. Mark it pending, handle
	 * the necessary masking and go out
	 */
	...

	kstat_cpu(cpu).irqs[irq]++;

	/* Start handling the irq */
	desc->chip->ack(irq);

	/* Mark the IRQ currently in progress.*/
	desc->status |= IRQ_INPROGRESS;

	do {
		struct irqaction *action = desc->action;
		irqreturn_t action_ret;

		if (unlikely(!action)) {
			desc->chip->mask(irq);
			goto out_unlock;
		}

		/*
		 * When another irq arrived while we were handling
		 * one, we could have masked the irq.
		 * Renable it, if it was not disabled in meantime.
		 */
		...

		desc->status &= ~IRQ_PENDING;
		spin_unlock(&desc->lock);
		action_ret = handle_IRQ_event(irq, action);
		if (!noirqdebug)
			note_interrupt(irq, desc, action_ret);
		spin_lock(&desc->lock);

	} while ((desc->status & (IRQ_PENDING | IRQ_DISABLED)) == IRQ_PENDING);

	desc->status &= ~IRQ_INPROGRESS;
out_unlock:
	spin_unlock(&desc->lock);
}

发现这两个函数的核心都是执行action_ret = handle_IRQ_event(irq, action);

irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction *action)
{
	irqreturn_t ret, retval = IRQ_NONE;
	unsigned int status = 0;

	handle_dynamic_tick(action);

	if (!(action->flags & IRQF_DISABLED))
		local_irq_enable_in_hardirq();

	do {
		ret = action->handler(irq, action->dev_id);
		if (ret == IRQ_HANDLED)
			status |= action->flags;
		retval |= ret;
		action = action->next;
	} while (action);

	if (status & IRQF_SAMPLE_RANDOM)
		add_interrupt_randomness(irq);
	local_irq_disable();

	return retval;
}

核心是其中的do while循环,会把action链表上的所有action->handler都会执行,而action->handler是由request_irq函数注册到action链表中去的

int request_irq(unsigned int irq, irq_handler_t handler,
		unsigned long irqflags, const char *devname, void *dev_id)
{
	struct irqaction *action;
	int retval;

	/*
	 * Sanity-check: shared interrupts must pass in a real dev-ID,
	 * otherwise we'll have trouble later trying to figure out
	 * which interrupt is which (messes up the interrupt freeing
	 * logic etc).
	 */
	...

	action = kmalloc(sizeof(struct irqaction), GFP_ATOMIC);
	if (!action)
		return -ENOMEM;

	action->handler = handler;
	action->flags = irqflags;
	cpus_clear(action->mask);
	action->name = devname;
	action->next = NULL;
	action->dev_id = dev_id;

	select_smp_affinity(irq);



	retval = setup_irq(irq, action);
	if (retval)
		kfree(action);

	return retval;
}
EXPORT_SYMBOL(request_irq);

真正将action注册到链表中的函数是retval = setup_irq(irq, action);


/*
 * Internal function to register an irqaction - typically used to
 * allocate special interrupts that are part of the architecture.
 */
int setup_irq(unsigned int irq, struct irqaction *new)
{
	struct irq_desc *desc = irq_desc + irq;
	struct irqaction *old, **p;
	const char *old_name = NULL;
	unsigned long flags;
	int shared = 0;

	...

	/*
	 * The following block of code has to be executed atomically
	 */
	spin_lock_irqsave(&desc->lock, flags);
	p = &desc->action;
	old = *p;
	
	/*将struct irqaction *new添加到链表尾部中去*/
	if (old) {
		...

		/* add new interrupt at end of irq queue */
		do {
			p = &old->next;
			old = *p;
		} while (old);
		shared = 1;
	}

	*p = new;

	/* Exclude IRQ from balancing */
	if (new->flags & IRQF_NOBALANCING)
		desc->status |= IRQ_NO_BALANCING;

	if (!shared) {
		irq_chip_set_defaults(desc->chip);
	

		/* Setup the type (level, edge polarity) if configured: */
		if (new->flags & IRQF_TRIGGER_MASK) {
			if (desc->chip && desc->chip->set_type)
				desc->chip->set_type(irq,
						new->flags & IRQF_TRIGGER_MASK);
			else
				/*
				 * IRQF_TRIGGER_* but the PIC does not support
				 * multiple flow-types?
				 */
				printk(KERN_WARNING "No IRQF_TRIGGER set_type "
				       "function for IRQ %d (%s)\n", irq,
				       desc->chip ? desc->chip->name :
				       "unknown");
		} else
			compat_irq_chip_set_default_handler(desc);

		desc->status &= ~(IRQ_AUTODETECT | IRQ_WAITING |
				  IRQ_INPROGRESS);

		if (!(desc->status & IRQ_NOAUTOEN)) {
			desc->depth = 0;
			desc->status &= ~IRQ_DISABLED;
			if (desc->chip->startup)
				desc->chip->startup(irq);
			else
				desc->chip->enable(irq);
		} else
			/* Undo nested disables: */
			desc->depth = 1;
	}
	/* Reset broken irq detection when installing new handler */
	desc->irq_count = 0;
	desc->irqs_unhandled = 0;
	spin_unlock_irqrestore(&desc->lock, flags);

	new->irq = irq;
	register_irq_proc(irq);
	new->dir = NULL;
	register_handler_proc(irq, new);

	return 0;

	...
}

如下描述了irq_desc和irq_action之间的关系
在这里插入图片描述
来简单分析一下中断整个架构和流程
在内核启动时,执行的第一个C函数start_kernel中会进行一系列初始化,与异常和中断相关的是trap_init()和init_IRQ()
trap_init函数干了什么呢?我觉得内核的注释写得非常好,直接摘抄下来
Copy the vectors, stubs and kuser helpers (in entry-armv.S) into the vector page, mapped at 0xffff0000, and ensure these are visible to the instruction stream.
然后我们在entry-armv.S中找到了__vectors_start到__vectors_end的定义(就是所谓的异常向量表)。
所以可以简单地认为内核在启动过程中构造了异常向量表
在用户层面发生了什么呢?
当需要为使用中断,需要调用request_irq函数将用户定义的中断处理函数注册到action链表中去

重点来了,当产生中断内核会做怎样的处理?
首先根据异常向量表跳入到vector_irq + stubs_offset,反正最后汇编执行完会调用asm_do_IRQ
最终会调用desc->handle_irq(irq, desc)
如是我们就想知道handle_irq(irq, desc)到底是从何而来,最终我们就会发现是和用户注册的action密切相关

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值