5.分析内核中断运行过程,以及中断3大结构体:irq_desc、irq_chip、irqaction(详解)
本节目标:
分析在linux中的中断是如何运行的,以及中断3大结构体:irq_desc、irq_chip、irqaction
在裸板程序中(参考stmdb和ldmia详解):
1.按键按下,
2.cpu发生中断,
3.强制跳到异常向量入口执行(0x18中断地址处)
3.1使用stmdb将寄存器值保存在栈顶(保护现场)
stmdb sp!, { r0-r12,lr }
3.2执行中断服务函数
3.3 使用ldmia将栈顶处数据读出到寄存器中,并使pc=lr(恢复现场)
ldmia sp!, { r0-r12,pc }^ //^表示将spsr的值复制到cpsr,因为异常返回后需要恢复异常发生前的工作状态
在linux中:
需要先设置异常向量地址(参考linux应用手册P412):
在ARM裸板中异常向量基地址是0x00000000,如下图:
而linux内核中异常向量基地址是0xffff0000(虚拟地址),
位于代码arch/cam/kernel/traps.c,代码如下:
void __init trap_init(void) { /* CONFIG_VECTORS_BASE :内核配置项,在.config文件中,设置的是0Xffff0000*/ /* vectors =0xffff0000*/ unsigned long vectors = CONFIG_VECTORS_BASE; ... ...
/*将异常向量地址复制到0xffff0000处*/ 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); ... ... }
上面代码中主要是将__vectors_end - __vectors_start之间的代码复制到vectors (0xffff0000)处,
__vectors_start为什么是异常向量基地址?
通过搜索,找到它在arch/arm/kernel/entry_armv.S中定义:
__vectors_start: swi SYS_ERROR0 //复位异常,复位时会执行 b vector_und + stubs_offset //undefine未定义指令异常 ldr pc, .LCvswi + stubs_offset //swi软件中断异常 b vector_pabt + stubs_offset //指令预取中止abort b vector_dabt + stubs_offset //数据访问中止abort b vector_addrexcptn + stubs_offset //没有用到 b vector_irq + stubs_offset //irq异常 b vector_fiq + stubs_offset //fig异常
其中stubs_offset是链接地址的偏移地址, vector_und、vector_pabt等表示要跳转去执行的代码
1.以vector_irq中断为例, vector_irq是个宏,它在哪里定义呢?
它还是在arch/arm/kernel/entry_armv.S中定义,如下所示:
vector_stub irq, IRQ_MODE, 4//irq:名字 IRQ_MODE:0X12 4:偏移量
上面的vector_stub 根据参数irq, IRQ_MODE, 4来定义” vector_ irq”这个宏(其它宏也是这样定义的)
2.vector_stub又是怎么实现出来的定义不同的宏呢?
我们找到vector_stub这个定义:
.macro vector_stub, name, mode, correction=0 //定义vector_stub有3个参数 .align 5 vector_\name: //定义不同的宏,比如vector_ irq .if \correction //判断correction参数是否为0 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 //读出spsr str lr, [sp, #8] @ save spsr @ @ Prepare for SVC32 mode. IRQs remain disabled. @ 进入管理模式 mrs r0, cpsr //读出cpsr eor r0, r0, #(\mode ^ SVC_MODE) msr spsr_cxsf, r0 @ @ the branch table must immediately follow this code @ and lr, lr, #0x0f //lr等于进入模式之前的spsr,&0X0F就等于模式位 mov r0, sp ldr lr, [pc, lr, lsl #2] movs pc, lr @ branch to handler in SVC mode
3.因此我们将上面__vectors_start里的b vector_irq + stubs_offset 中断展开如下:
.macro vector_stub, name, mode, correction=0 //定义vector_stub有3个参数 .align 5 vector_stub irq, IRQ_MODE, 4 //这三个参数值代入 vector_stub中 vector_ irq: //定义 vector_ irq /*计算返回地址(在arm流水线中,lr=pc+8,但是pc+4只译码没有执行,所以lr=lr-4) */ sub lr, lr, #4 @ @ Save r0, lr_<exception> (parent PC) and spsr_<exception> @ (parent CPSR) @保存r0和lr和spsr stmia sp, {r0, lr} //存入sp栈里 mrs lr, spsr //读出spsr str lr, [sp, #8] @ save spsr @ @ Prepare for SVC32 mode. IRQs remain disabled. @ 进入管理模式 mrs r0, cpsr //读出cpsr eor r0, r0, #(\mode ^ SVC_MODE) msr spsr_cxsf, r0 @ @ the branch table must immediately follow this code @ and lr, lr, #0x0f //lr等于进入模式之前的spsr,&0X0F就等于模式位 mov r0, sp ldr lr, [pc, lr, lsl #2] //如果进入中断前是usr,则取出PC+4*0的内容,即__irq_usr @如果进入中断前是svc,则取出PC+4*3的内容,即__irq_svc movs pc, lr //跳转到下面某处,且目标寄存器是pc,指令S结尾,最后会恢复cpsr. .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) .long __irq_invalid @ 4 .long __irq_invalid @ 5 .long __irq_invalid @ 6 .long __irq_invalid @ 7 .long __irq_invalid @ 8 .long __irq_invalid @ 9 .long __irq_invalid @ a .long __irq_invalid @ b .long __irq_invalid @ c .long __irq_invalid @ d .long __irq_invalid @ e .long __irq_invalid @ f
从上面代码中的注释可以看出:
- 1).将发生异常前的各个寄存器值保存在SP栈里,若是中断异常,则PC=PC-4,也就是CPU下个要运行的位置处
- 2).然后根据进入中断前的工作模式不同,程序下一步将跳转到_irq_usr 、或__irq_svc等位置。
4.我们先选择__irq_usr作为下一步跟踪的目标:
4.1其中__irq_usr的实现如下(arch\arm\kernel\entry-armv.S):
__irq_usr: usr_entry //保存数据到栈里 get_thread_info tsk irq_handler //调用irq_handler b ret_to_user
4.2.irq_handler的实现过程,arch\arm\kernel\entry-armv.S
.macro irq_handler get_irqnr_preamble r5, lr get_irqnr_and_base r0, r6, r5, lr // get_irqnr_and_base:获取中断号,r0=中断号 movne r1, sp //r1等于sp (发生中断之前的各个寄存器的基地址) adrne lr, 1b bne asm_do_IRQ //调用asm_do_IRQ, irq=r0 regs=r1
irq_handler最终调用asm_do_IRQ
4.3 asm_do_IRQ实现过程,arch/arm/kernel/irq.c
该函数和裸板中断处理一样的,完成3件事情:
1).分辨是哪个中断;
2).通过desc_handle_irq(irq, desc)调用对应的中断处理函数;
3).清中断
asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs) //irq:中断号 *regs:发生中断前的各个寄存器基地址 { struct pt_regs *old_regs = set_irq_regs(regs); /*根据irq中断号,找到哪个中断, *desc =irq_desc[irq]*/ struct irq_desc *desc = irq_desc + irq; // irq_desc是个数组(位于kernel/irq/handle.c) if (irq >= NR_IRQS) desc = &bad_irq_desc; irq_enter(); desc_handle_irq(irq, desc); // desc_handle_irq根据中断号和desc,调用函数指针,进入中断处理, irq_finish(irq); irq_exit(); set_irq_regs(old_regs); }
上面主要是执行desc_handle_irq函数进入中断处理
其中desc_handle_irq代码如下:
desc->handle_irq(irq, desc);//相当于执行irq_desc[irq]-> handle_irq(irq, irq_desc[irq]);
它会执行handle_irq成员函数,这个成员handle_irq又是在哪里被赋值的?
搜索handle_irq,找到它位于kernel/irq/chip.c,__set_irq_handler函数下:
void __set_irq_handler(unsigned int irq, irq_flow_handler_t handle, int is_chained,const char *name) { ... ... desc = irq_desc + irq; //在irq_desc结构体数组中找到对应的中断 ... ... desc->handle_irq = handle; //使handle_irq成员指向handle参数函数 }
继续搜索__set_irq_handler函数,它被set_irq_handler函数调用:
static inline void set_irq_handler(unsigned int irq, irq_flow_handler_t handle) { __set_irq_handler(irq, handle, 0, NULL); }
继续搜索set_irq_handler函数,如下图
发现它在s3c24xx_init_irq(void)函数中被多次使用,显然在中断初始化时,多次进入__set_irq_handler函数,并在irq_desc数组中构造了很多项 handle_irq函数
我们来看看irq_desc中断描述结构体到底有什么内容:
struct irq_desc { irq_flow_handler_t handle_irq; //指向中断函数, 中断产生后,就会执行这个handle_irq struct irq_chip *chip; //指向irq_chip结构体,用于底层的硬件访问,下面会介绍 struct msi_desc *msi_desc; void *handler_data; void *chip_data; struct irqaction *action; /* IRQ action list */ //action链表,用于中断处理函数 unsigned int status; /* IRQ status */ unsigned int depth; /* nested irq disables */ unsigned int wake_depth; /* nested wake enables */ unsigned int irq_count; /* For detecting broken IRQs */ unsigned int irqs_unhandled; spinlock_t lock; ... ... const char *name; //产生中断的硬件名字 } ;
其中的成员*chip的结构体,用于底层的硬件访问, irq_chip类型如下:
struct irq_chip { const char *name; 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 (*mask)(unsigned int irq); //屏蔽中断源 void (*mask_ack)(unsigned int irq); //屏蔽和响应中断 void (*unmask)(unsigned int irq); //开启中断源 ... ... int (*set_type)(unsigned int irq, unsigned int flow_type); //将对应的引脚设置为中断类型的引脚 ... ... #ifdef CONFIG_IRQ_RELEASE_METHOD void (*release)(unsigned int irq, void *dev_id); //释放中断服务函数 #endif };
其中的成员struct irqaction *action,主要是用来存用户注册的中断处理函数,
一个中断可以有多个处理函数 ,当一个中断有多个处理函数,说明这个是共享中断.
所谓共享中断就是一个中断的来源有很多,这些来源共享同一个引脚。
所以在irq_desc结构体中的action成员是个链表,以action为表头,若是一个以上的链表就是共享中断
irqaction结构定义如下:
struct irqaction { irq_handler_t handler; //等于用户注册的中断处理函数,中断发生时就会运行这个中断处理函数 unsigned long flags; //中断标志,注册时设置,比如上升沿中断,下降沿中断等 cpumask_t mask; //中断掩码 const char *name; //中断名称,产生中断的硬件的名字 void *dev_id; //设备id struct irqaction *next; //指向下一个成员 int irq; //中断号, struct proc_dir_entry *dir; //指向IRQn相关的/proc/irq/ };
上面3个结构体的关系如下图所示:
我们来看看s3c24xx_init_irq()函数是怎么初始化中断的,以外部中断0为例(位于s3c24xx_init_irq函数):
s3c24xx_init_irq()函数中部分代码如下:
/*其中IRQ_EINT0=16, 所以irqno=16 */ for (irqno = IRQ_EINT0; irqno <= IRQ_EINT3; irqno++)
{ irqdbf("registering irq %d (ext int)\n", irqno);
/*在set_irq_chip函数中会执行: desc = irq_desc + irq; desc->chip = chip;*/ set_irq_chip(irqno, &s3c_irq_eint0t4); //所以(irq_desc+16)->chip= &s3c_irq_eint0t4 /* set_irq_handler 会调用__set_irq_handler 函数*/ set_irq_handler(irqno, handle_edge_irq); //所以(irq_desc+16)-> handle_irq = handle_edge_irq
set_irq_flags(irqno, IRQF_VALID); }
初始化了外部中断0后,当外部中断0触发,就会进入我们之前分析的asm_do_IRQ函数中,调用(irq_desc+16)-> handle_irq也就是handle_edge_irq函数。
我们来分析下handle_edge_irq函数是如何执行中断服务的:
void fastcall 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); /*判断这个中断是否正在运行(INPROGRESS)或者禁止(DISABLED)*/ if (unlikely((desc->status & (IRQ_INPROGRESS | IRQ_DISABLED)) || !desc->action))
{ desc->status |= (IRQ_PENDING | IRQ_MASKED); mask_ack_irq(desc, irq); //屏蔽中断 goto out_unlock; } 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; } if (unlikely((desc->status & (IRQ_PENDING | IRQ_MASKED | IRQ_DISABLED)) == (IRQ_PENDING | IRQ_MASKED))) { desc->chip->unmask(irq); desc->status &= ~IRQ_MASKED; } 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); }
上面handle_edge_irq()函数主要执行了:
1. desc->chip->ack(irq); //开始处理这个中断
在s3c24xx_init_irq()函数中chip成员指向了s3c_irq_eint0t4(),
所以desc->chip->ack(irq)就是执行handle_edge_irq(irq)函数,handle_edge_irq函数如下:
s3c_irq_ack(unsigned int irqno) { unsigned long bitval = 1UL << (irqno - IRQ_EINT0); __raw_writel(bitval, S3C2410_SRCPND); //向SRCPND寄存器写入bitval ,清SRCPND中断 __raw_writel(bitval, S3C2410_INTPND); //向INTPND寄存器位写入bitval ,清INTPND中断 }
所以desc->chip->ack(irq); 主要执行清中断之类的
2.handle_IRQ_event(irq, action); //真正的处理过程
handle_IRQ_event()代码如下:
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); //执行action->handler if (ret == IRQ_HANDLED) status |= action->flags; retval |= ret; action = action->next; //指向下个action成员 } while (action); //取出action所有成员 if (status & IRQF_SAMPLE_RANDOM) add_interrupt_randomness(irq); local_irq_disable(); return retval; }
所以handle_IRQ_event()函数主要是取出action链表中的成员,然后执行irq_desc->action->handler(irq, action->dev_id);
action链表是irq_desc中断描述符结构体的 成员
本节常用函数总结:
trap_init(): 初始化异常向量的虚拟基地址,一般为0XFFFF0000
s3c24xx_init_irq():初始化各个中断
set_irq_chip(irqno, &s3c_irq_eint0t4):设置irq_desc[irqno]->chip等于第二个参数
set_irq_handler(irqno, handle_edge_irq); 设置irq_desc[irqno]->handle_irq等于第二个参数
asm_do_IRQ():中断产生后,会进入这个函数,最终执行 desc->handle_irq(irq, desc);
handle_edge_irq(irq, desc):执行中断函数,主要是执行以下两步骤:
(1) desc->chip->ack(irq):相应中断,也就是清中断,使能再次接受中断
(2) handle_IRQ_event(irq, action):执行中断的服务函数,desc->action->handler
中断运行总结:
当产生一个中断异常
1.进入异常向量vector,比如中断异常: vector_irq + stubs_offset
2.比如中断异常之前是用户模式(正常工作),则进入 __irq_usr,然后最终进入asm_do_IRQ函数,
3.然后执行irq_desc [irq]->handle_irq(irq, irq_desc [irq]);
通过刚才的分析,外部中断0(irq_desc[16])的handle_irq成员等于handle_edge_irq函数,
所以就是执行handle_edge_irq(irq, irq_desc [irq]);
4.以外部中断0为例,在handle_edge_irq函数中主要执行两步:
->4.1 desc->chip->ack //使用chip成员中的ack函数来清中断
->4.2 执行action链表 irq_desc->action->handler
这4步都是系统给做好的(中断的框架),当我们想自己写个中断处理程序,去执行自己的代码,就需要写irq_desc->action->handler,然后通过request_irq()来向内核申请注册中断
中断运行分析完毕后,接下来开始分析如何通过函数来注册卸载中断
人间有真情,人间有真爱。