Linux异常处理体系结构

相关数据结构介绍

先来看跟中断处理体系相关的三个数据结构(我们抽取出主要的部分,实际上不止这些成员,详细看include/linux/irq.h):
1

struct irq_desc {
	irq_flow_handler_t	handle_irq;   /*当前中断的处理函数入口*/
	struct irq_chip		*chip;        /*低层的硬件访问*/
	struct irqaction	*action;	/* IRQ action list 用户提供的中断处理*/
	unsigned int		status;		/* IRQ status IRQ状态 */
	const char		   *name;     /*中断名称*/
} ____cacheline_internodealigned_in_smp;

2

struct irqaction {
	irq_handler_t handler;   /*用户注册的中断处理函数*/
	unsigned long flags;    /*中断标志,比如是否共享中断,电平触发还是边沿触发*/
	cpumask_t mask;        /*用于对称处理器系统*/
	const char *name;     /*用户注册的中断名字,cat/proc/interrupts可看到*/
	void *dev_id;         /*用户传给上面的handler参数,还可以用来区分共享中断*/
	struct irqaction *next;  
	int irq;                /*中断号*/
	struct proc_dir_entry *dir;
};

3

struct irq_chip {
	const char	*name;                              /*名字*/
	unsigned int	(*startup)(unsigned int irq);  /*启动中断,如果不设置,缺省为"enable"*/
	void		(*shutdown)(unsigned int irq);     /*关闭中断,如果不设置,缺省为"disable"*/
	void		(*enable)(unsigned int irq);      /*使能中断,如果不设置,缺省为"umask"*/
	void		(*disable)(unsigned int irq);     /*禁止中断,如果不设置,缺省为"mask"*/

	void		(*ack)(unsigned int irq);        /*相应中断,通常是清楚当前中断使得可以接收下一个中断*/
	void		(*mask)(unsigned int irq);       /*屏蔽中断源*/
	void		(*mask_ack)(unsigned int irq);   /*屏蔽和响应中断源*/
	void		(*unmask)(unsigned int irq);    /*开启中断源*/
};

了解上面三个基本的数据结构后我们来看到arch/arm/plat-s3c24xx\irq.c这个文件的s3c24xx_init_irq的函数,在该文件中我们可以看到以下代码段

for (irqno = IRQ_EINT0; irqno <= IRQ_EINT3; irqno++) {
		irqdbf("registering irq %d (ext int)\n", irqno);
		set_irq_chip(irqno, &s3c_irq_eint0t4);
		set_irq_handler(irqno, handle_edge_irq);
		set_irq_flags(irqno, IRQF_VALID);
	}

	for (irqno = IRQ_EINT4; irqno <= IRQ_EINT23; irqno++) {
		irqdbf("registering irq %d (extended s3c irq)\n", irqno);
		set_irq_chip(irqno, &s3c_irqext_chip);
		set_irq_handler(irqno, handle_edge_irq);
		set_irq_flags(irqno, IRQF_VALID);
	}	

用第二for循环来分析,主要是使用set_irq_chip(),set_irq_handler(),set_irq_flags()这三个函数来初始化irq_desc结构体中chip,handle_irq,flags,初始化也就是把他指向某个结构体或者函数等,这个三个函数都通过==desc = irq_desc(为struct irq_desc类型的全局数组) + irq(irqno传进的形参)==这个语句将根据irqno这个中断号标识从irq_desc这个数组取出desc,desc为struct irq_desc*类型,将其chip成员设置&s3c_irqext_chip,其handle_irq成员设置为handle_edge_irq所对应的函数,其status成员设置成IRQF_VALID,完成的工作如下图所示,填充了irq_desc数组各项的
handle_irq成员和chip成员
在这里插入图片描述

异常的处理

现在有了对以上重要的三个数据结构的了解,我们来看下linux中异常处理的过程,linux内核初始化阶段通过trap_init()函数完成了把异常向量拷贝到0xFFFF0000开始的地方

arch/arm/kernel/traps.c

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);
}
arch/arm/kernal/entry-armv.S
	.equ	stubs_offset, __vectors_start + 0x200 - __stubs_start

	.globl	__vectors_start
__vectors_start:
	swi	SYS_ERROR0                  /*复位异常*/
	b	vector_und + stubs_offset  /*未定义异常*/
	ldr	pc, .LCvswi + stubs_offset /*swi异常*/
	b	vector_pabt + stubs_offset /*指令预处理异常*/
	b	vector_dabt + stubs_offset /*数据访问异常*/
	b	vector_addrexcptn + stubs_offset 
	b	vector_irq + stubs_offset  /*irq异常*/
	b	vector_fiq + stubs_offset  /*fiq异常*/

	.globl	__vectors_end
__vectors_end:


vector_stub	und, UND_MODE

	.long	__und_usr			@  0 (USR_26 / USR_32)  
	.long	__und_invalid			@  1 (FIQ_26 / FIQ_32)
	.long	__und_invalid			@  2 (IRQ_26 / IRQ_32)
	.long	__und_svc			@  3 (SVC_26 / SVC_32)
	.long	__und_invalid			@  4
	.long	__und_invalid			@  5
	.long	__und_invalid			@  6
	.long	__und_invalid			@  7
	.long	__und_invalid			@  8
	.long	__und_invalid			@  9
	.long	__und_invalid			@  a
	.long	__und_invalid			@  b
	.long	__und_invalid			@  c
	.long	__und_invalid			@  d
	.long	__und_invalid			@  e
	.long	__und_invalid			@  f

	.align	5

以上第一段代码存在于arch/arm/kernel/traps.c里面,该C文件的trap_init()在源码init/main.c来调用,用来设置各种异常的处理向量,所谓的异常处理向量就是存在特定地址上的跳转指令,当异常发生时,CPU就会跳到特定地址上,再特定地址上的跳转指令去执行更复杂的代码,比如保护现场,恢复现场等等…代码中的vectors等于0xffff0000,地址__vectors_start~__vectors_end之间的代码就是异常向量,它们被复制到
0xffff0000, __stubs_start~ __stubs_end之间就是那些更复杂的代码的跳转地址,它被复制到0xffff0000+0x200的地方,看到第二段代码,所表示的就是异常向量表的详细内容,其中的"stubs_offset"用来重新定位跳转的位置(因为该异常向量表会被复制到0xffff0000处),以vector_und为例,当发生未定义异常时,系统会跳转到b vector_und + stubs_offset这里,根据vector_und + stubs_offset取到_und_sur,则变成了b _und_usr,
_und_usr是处理函数的入口地址,是调用相应的C函数,各种异常的处理流程都是大概这样一个过程,它们的异常C处理函数可以分为5类,分布再不同的文件里
在这里插入图片描述
异常处理体系结构图
在这里插入图片描述

中断的处理

上述我们提到过irq_desc结构数组,它的成员"struct irq_chip *chip",“struct irqaction *action”,对于2440开发板,调用arch/arm/plat-s3c24xx\irq.c的s3c24xx_init_irq的函数来初始化,这三种数据结构构成了中断处理体系的框架,这三者的关系如下图所示
在这里插入图片描述
发生中断时,CPU执行异常向量vector_irq的代码,在vector_irq里面,最终会调用中断处理的总入口asm_do_IRQ,asm_do_IRQ根据中断号调用irq_desc数组项中的handle_irq,handle_irq会使用chip成员中的函数来设置硬件,比如清除中断,禁止中断,重新使能中断等,handle_irq逐个调用用户在action链表中注册的处理函数,用户卸载中断时就是从action链表中去除不需要的项,情景分析:
按键中断发生时,函数处理流程:
首先进入异常模式执行b vector_irq+offset --> 然后vecrot_stubs做一些保护现场工作并切换到svc模式–>跳到跳转表__usr_irq执行再接管“现场”数据,之后调用asm_do_IRQ,他根据中断号找到irq_desc[irq].handle_irq–>假设irq_desc[irq].handle_irq指向handle_edge_irq,这个函数就会进行上面黄色标注的工作。
在这里插入图片描述

接下来我们讲讲用户注册中断处理函数的过程,我们知道在写驱动程序常常用到的一个注册中断的函数就是request_irq()了,它的原型在kernal/irq/manager.c里面有定义
int request_irq(unsigned int irq, irq_handler_t handler,unsigned long irqflags, const char *devname, void *dev_id)
irq:是要申请的硬件中断号

handler:是向系统注册的中断处理函数,是一个回调函数,中断发生时,系统调用这个函数,dev_id参数将被传递给它。
irqflags:是中断处理的属性,若设置了IRQF_DISABLED (老版本中的SA_INTERRUPT,本版zhon已经不支持了),则表示中断处理程序是快速处理程序,快速处理程序被调用时屏蔽所有中断,慢速处理程序不屏蔽;若设置了IRQF_SHARED(老版本中的SA_SHIRQ),则表示多个设备共享中断,若设置了IRQF_SAMPLE_RANDOM(老版本中的SA_SAMPLE_RANDOM,表示对系统熵有贡献,对系统获取随机数有好处。(这几个flag是可以通过或的方式同时使用的)

devname:设置中断名称,通常是设备驱动程序的名称 在cat /proc/interrupts中可以看到此名称。

dev_id:在中断共享时会用到,一般设置为这个设备的设备结构体或者NULL。

返回值:request_irq()返回0表示成功,返回-INVAL表示中断号无效或处理函数指针为NULL,返回-EBUSY表示中断已经被占用且不能共享。

request_irq函数首先使用4个参数构造一个irqation结构,将新建的irqation结构链入irq_desc[irq]结构的action链表中,当使用free_irq时根据中断号irq和dev_id从anction链表中找到该表项,将它移除

发布了83 篇原创文章 · 获赞 3 · 访问量 1255

猜你喜欢

转载自blog.csdn.net/qq_41936794/article/details/105084824