相关数据结构介绍
先来看跟中断处理体系相关的三个数据结构(我们抽取出主要的部分,实际上不止这些成员,详细看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链表中找到该表项,将它移除