目录
举例 2:外部中断 IRQ_EINT4;到外部中断 IRQ_EINT23
③handle_IRQ_event 这是真正的正理过程:处理中断。
0.1Linux 中断顶部和底部概念
为保证系统的实时性,中断服务程序必须足够简短, 但实际应用中某些时候当发生中断时必须处理大量的事务,这时候如果都在中断服务程序中完成,则会严重降低中断的实时性。
基于这个原因, Linux 系统中提出一个概念:把中断服务程序划分为两部分—顶半部和底半部。
顶半部:
完成尽可能少的比较紧急的功能,它往往只是简单的读取寄存器中的中断状态并清除中断标志后就进行“登记中断” (也就是将底半部处理程序挂在到设备的底半部执行队列中)的工作。
特点: 响应速度快,耗时短。
底半部:
中断处理的大部分工作都在底半部,它几乎做了中断处理程序的所有事情。
特点: 处理相对来说不是非常紧急的事件。
底半部机制: 主要有 tasklet、工作队列和软中断。
1.将 2440 作为单片机使用时:裸机程序时
1,按键按下时。
2, CPU 发生中断。
强制的跳到异常向量处执行(中断是异常的一种)。
3,“入口函数”是一条跳转指令。跳到某个函数:(作用)
①,保存被中断处的现场(各种寄存器的值)。
②,执行中断处理函数。
③,恢复被中断的现场。
2.LINUX 中处理中断的过程:
1.写程序时先设置异常入口:
源码\jz2440源码\硬件部件实验代码\int\head.S
中断处理完后,要返回去继续执行之前被中断的那个程序。
保存寄存器就是保存中断前那个程序的所用到的寄存器。
然后是处理中断,最后是恢复。
异常向量在哪里:
2.LINUX 的异常向量在哪里:
ARM 架构的 CPU 的异常向量基址可以是 0x0000 0000,也可以是 0xffff0000, LINUX 内核
使用后者,只需要在某个寄存器里设置下,就可以将异常基址定位到这里来。这个地址并不
代表实际的内存,是虚拟地址。当建立了虚拟地址与物理地址间的映射后,得将那些异常向
量,即相当于把那些跳转指令(如:HandleSWI 等)复制拷贝到这个 0xffff0000 这个地址处去。(“那些跳转指令”是指 head.S 中那些跳转)。
这个过程是在 trap_init 这个函数里做。
/arch/arm/kernel
trap_init 函数将异常向量复制到 0xffff0000 处,部分代码如下:
如上:
将 __vectors_start, __vectors_end - __vectors_start 这段代码拷贝到 vectors 来。
vectors 是“CONFIG_VECTORS_BASE” 是个配置项(内核的配置选项)。
在 linux 源码顶层目录下: vim .config, 搜索“CONFIG_VECTORS_BASE”。
这个__vectors_start在哪里搜索一下
__vectors_start 在 entry-armv.S 中定义,也是些跳转指令。
3.中断跳转
可见和单片中的一样(都是跳转指令)。
A:假设发生了 vector_und (undefined)异常未定义指令后,会跳转到 vector_und 加一个偏移地址 stubs_offset(b vector_und + stubs_offset)。这个 vector_und 地址标号源代码里
没有,它是一个宏在这个文件的上面有:
将这个宏展开:
这里是下一级的跳转,跳转表如下:
后面 __und_usr 等标号中,去保存那些寄存器,作处理。处理完后,再恢复那些寄存器,即恢复那些被中断的程序。
B:下面是一个实例:在 entry-armv.S 源码中的第 1069 行:
发生中断便跳转到这里。这个地址标号“vector_irq”在代码中也没有。也是一个宏来定义的:
也是和上面的“vector_stub und, UND_MODE”一样,是用下面的宏展开:
vector_stub irq, IRQ_MODE, 4 这里是一个宏,将这个宏和宏参数替换到下面的宏定
义中去:
“name”为“irq” .
“correction”为“4”。
.macro vector_stub, name, mode, correction=0
.align 5
vector_\name: 宏替换后: vector_irq:
.if \correction 宏替换后: .if 4
sub lr, lr, #\correction 宏替换后: sub lr, lr, #4
.endif
这里宏替换后就变成:
vector_irq:
.if 4
sub lr, lr, #4
.endif
"sub lr, lr, #4"这是计算返回地址,比较裸机程序:
源码\jz2440源码\硬件部件实验代码\int\head.S
下一级跳转的跳转表:
上面的跳转表,从名字上猜测:
__irq_usr : 应该是用户态发生中断时,就跳转到“__irq_usr”标号中去。
__irq_svc:在管理模式里发生中断时,就跳转到“__irq_svc”这个标号中去。
查看标号“__irq_usr”:
这个宏 usr_entry 应该是保存那些寄存器。搜索“usr_entry”的宏定义如下:
在这个栈(sp)中将寄存器保存进去。
接着再看: __irq_svc 标号的实现,会有个:
"irq_handler"也是一个宏:
从上面的宏定义可以看到,最终会调用一个 asm_do_IRQ .就是处理函数,比较复杂的代码就用 C 语言实现
最后是调用:
4.总结:
linux 内核中处理异常的流程,最后调用到“asm_do_IRQ()”
异常向量:
首先“trap_init”构造了“异常向量”。
异常向量是什么?
异常向量就是将这段代码“__vectors_start”拷贝到 0xffff0000“vectors”处:
异常向量就在这里。这“异常向量”也是某些跳转。如:“b vector_irq + stubs_offset”因
为向量已重新定位了,所以得加上“stubs_offset”偏移地址。“vector_irq”是链接地址,要
加上一个偏移地址才能正确的跳转。
vector_irq 做的事:
它是由一个宏定义的。做的事和单片机程序一样。
- 计算返回地址: sub lr, lr, #4
- b, 保存寄存器值:
c,调用处理函数(如: __irq_usr 若用户态发生中断,就跳转到这个标号处。)
d, 处理函数又去调用“宏”:如"__irq_usr"标号处理是“usb_entry”宏,此宏先保存环境变量诸多寄存器。然后就调用宏“irq_handler”。此宏的定义会调用函数“asm_do_IRQ”。
如: __irq_usr:
usr_entry(这个宏也是保存些寄存器)
irq_handler(从__irq_usr 后调用这个函数,它也是一个宏。)
asm_do_IRQ(irq_hander 这个宏是做 asm_do_IRQ 函数)
e:恢复(调用完 asm_do_IRQ 函数后)
3. LINUX 的中断框架:内核中断框架
了解“asm_do_IRQ”,理解中断处理的过程。
A,单片机下的中断处理:
1,分辨是哪个中断。
2,调用处理函数(哪个中断就调用哪个处理函数)。
2, 清中断。
1,上面是先读寄存器“INTOFFSET”,看是哪个中断。
2,中间是中断处理。
3,最后是清中断。
内核的也差不多。
B,内核的中断处理:
以上单片机的 3 个过程,都是在 asm_do_IRQ 中实现的。最终在“handle_irq”中实现的。
1,首先是根据传进来的 IRQ 中断号(参 1),
struct irq_desc *desc = irq_desc + irq;
irq_desc 是一个数组。在“Handle.c”中定义:
从名字上看可知这是一 个“中断描述”数组。以中断号“NR_IRQS”为下标。
struct irq_desc *desc = irq_desc + irq;
在这个数组下标处取到一个“数组项”。
irq_enter();不用管。
然后进入处理:
desc_handle_irq(irq, desc);
asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
-->struct irq_desc *desc = irq_desc + 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(irq, desc);
即为:
(irq_desc + irq)->handle_irq(irq, desc);
desc 是全局数组“irq_desc(中断号为下标) ”
裸机程序中的 3 过程是在这个“handle_irq”中实现的。
2,查找“handle_irq”时,
发现它有在“/kernel/irq/Chip.c”中的“__set_irq_handler()”函数中有被用到。
接着查“__set_irq_handler()”这个函数有谁调用过。在 Irq.h 中:
再搜索“set_irq_handler”时在“arch/arm/plat-s3c24xx/Irq.c”中找到一个函数:
下面是chip.c中
是以形参“irq”这索引,在“irq_desc”中断描述数组中找到一项,将这一项给“desc” .
将通过“形参 irq”索引找到的那一项“desc”的“handle_irq”指向传进函数“__set_irq_handler”
的形参“irq_flow_handler_t handle”。
这样显然在“void __init s3c24xx_init_irq(void)”函数中就构造了这个数组的很多项。
结构体“desc”的原型的成员:
在这个“irq_desc”结构中有“handle_irq”等于“某个函数”;如下举例,看此结构成员 handle_irq等于什么函数, "*chip" 等于什么函数。
举例: IRQ_EINT0 到 IRQ_EINT3:
在 Irq.c 中/*external interrupts*/外部中断中。
IRQ_EINT0 到 IRQ_EINT3 (外部中断 0 到外部中断 3)。设置了上面结构中的“handle_irq”和“*chip”
1,成员“handle_irq”:
如上外部中断 0“IRQ_EINT0”(从名字猜测这是中断号),在\arch-s3c2410\Irqs.h 中有定义。
那么“16”这个数组项(irq_desc[16])的“handle_irq”就等于这个函数“handle_edge_irq”边缘触发。
(set_irq_handler(irqno, handle_edge_irq);)
从“handle_edge_irq”名字中“edge”可猜测到是处理那些边缘触发的中断。
2,成员“*chip”:
对于上面的“set_irq_chip(irqno, &s3c_irqext_chip);”也是找到相应的数组项:
所以这里“chip”等于“s3c_irq_eint0t4”是 s3c 外部中断 0-4.
举例 2:外部中断 IRQ_EINT4;到外部中断 IRQ_EINT23
Irq_desc[外部中断 4 到 外部中断 23]
Handle_irq = handle_edge_irq;
Chip = s3c_irqext_chip;
3.总结:
Irq_desc[外部中断 0 ~ 3 ]:
{
Handle_irq = handle_edge_irq;
Chip = s3c_irq_eint0t4;
}
Irq_desc[外部中断 4 到 外部中断 23]:
{
Handle_irq = handle_edge_irq;
Chip = s3c_irqext_chip;
}
以上就是初始化,在“void __init s3c24xx_init_irq(void)”中实现的。
中断处理 C 函数入口 “asm_do_IRQ”会调用到事先初始的“handle_irq”函数。如外部中断调用“handle_edge_irq” .是处理边缘触发的中断。
void __init s3c24xx_init_irq(void)
-->for (irqno = IRQ_EINT0; irqno <= IRQ_EINT3; irqno++) {
set_irq_chip(irqno, &s3c_irq_eint0t4);
set_irq_handler(irqno, handle_edge_irq);
}
for (irqno = IRQ_EINT4; irqno <= IRQ_EINT23; irqno++) {
set_irq_chip(irqno, &s3c_irqext_chip);
set_irq_handler(irqno, handle_edge_irq);
}
在函数“/kernel/irq/Chip.c”中,可以看到“handle_edge_irq”做的事情。
void fastcall handle_edge_irq(unsigned int irq, struct irq_desc *desc)
-->kstat_cpu(cpu).irqs[irq]++; /* 发生了多少次中断的计数 */
-->desc->chip->ack(irq); /* Start handling the irq 开始处理中断*/
-->if (unlikely(!action)) { /*这里是出错处理。 action 是链表。若此链表为空 */
desc->chip->mask(irq); /* 若链表为空则屏蔽中断 */
goto out_unlock;
}
-->action_ret = handle_IRQ_event(irq, action); /* 这是真正的中断处理过程 */
action_ret = handle_IRQ_event(irq, action);
其中"irq"是“handle_edge_irq”参 1;
“action”是“struct irqaction *action = desc->action;”参 2 结构中的成员。
handle_edge_irq(unsigned int irq, struct irq_desc *desc)
总结:
handle_edge_irq
-->desc->chip->ack(irq). 清中断
-->handle_IRQ_event(irq, action). 处理中断
“handle_edge_irq”的处理过程:处理边缘触发的中断。
① desc->chip->ack(irq):清中断
假设是如下外部中断, irq.c 中 “s3c24xx_init_irq()”初始化中断中:
在“chip: s3c_irqext_chip”定义如下: desc->chip 就是“s3c_irqext_chip”:
S3c_irqext_chip 名字上猜测是 irq(中断)ext(外部)。
那么: desc->chip->ack:这个成员"ack"即:
.ack = s3c_irqext_ack,
static void s3c_irqext_ack(unsigned int irqno)
-->mask = __raw_readl(S3C24XX_EINTMASK); /* 读出外部中断屏蔽寄存器 */
-->__raw_writel(bit, S3C24XX_EINTPEND); /* 外部中断等待寄存器‘pending’。这里是清
- */
从这个函数的具体实现,可知它应该是“清中断”。
②如果链表“action”是空的,就将它屏蔽。(出错处理)
③handle_IRQ_event 这是真正的正理过程:处理中断。
取出链表 action 中的成员,执行“action->handler()”。
④总结:“中断框架”(按下按键)。
1,CPU 自动进入“异常模式”。
调用“异常处理函数”。
linux-2.6.22.6\arch\arm\kernel\entry-armv.S 中
2,在“异常处理函数”中如跳到“b vector_irq + stubs_offset”。
“vector_irq”是一个宏定义的。找到这个“宏”,假设它调用“__irq_usr”。
vector_stub irq, IRQ_MODE, 4
此宏展开:
Vector_irq:
Sub lr, lr, #4
@
@ 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
假若是调用的列表中的“__irq_usr”。
3,调用到列表中的“__irq_usr”后
可以具体分析这个“__irq_usr”中处理的情况:
__irq__usr 是用户态中断。
“usr_entry”这是它的入口,其中保存寄存器(中断现场)。
再调用“irq_handler”,这也是一个宏。
最终会调用到“asm_do_IRQ”这个 C 函数。
4,“asm_do_IRQ”调用“irq_desc[IRQ 中断下标]
以中断为下标取出里面的一项“handle_irq””
desc->handle_irq(irq, desc);指向谁:
举例说明:在: linux-2.6.22.6\arch\arm\plat-s3c24xx\Irq.c 中, s3c24xx_init_irq(void)初始化中断时,若是外部中断 0-3,或外部中断 4-23 时,,指向“handle_edge_irq 函数”
,指向“handle_edge_irq 函数”
5,“handle_edge_irq”做的事:
①, desc->chip->ack() 清中断
②, handle_IRQ_event() 处理中断
③,取出“action”链表中的成员,执行“action->handler”。
6,核心在“irq_desc” 结构数组:
分析“irq_desc[]”结构数组:里面有“action 链表”,有“chip”等。
handle_irq:是函数指针。在上面的例子中这个函数指针指向了“handle_edge_irq”。
首先是清中断,然后把链表“action”中的成员取出,一一执行里面的“action->handler”
*chip :是芯片相关底层的一些处理函数。在例子中是指向"&s3c_irqext_chip”
这个结构体“irq_chip”作些底层的操作:
起动、关闭、使用、禁止、 ack 响应中断(响应中断即清中断而已)、
*action:中断的动作列表。
里面有: handler(处理函数),还有“flags”等。
以上 1-6 都是系统做好的,这便是系统的“中断处理框架”。
但是我 们写 中断处 理时 ,是想执行自已的中断代码。那么我们的代码就应该在
“action->handler”这里。
我们要用“request_irq()”告诉内核我们的处理函数是什么。
7.LINUX 内核中断框架:
首先内核中有个数组“irq_desc[]” IRQ 的描述结构数组,这个结构数组是以“中断号”为下标。
结构中有:
handle_irq:中断入口函数。这个入口函数中,是做清中断,再调用 action 链表中的各种处理函数。
chip: 指向底层硬件的访问函数,它做屏蔽中断、使能中断,响应中断等。
action:链表头,它指向“irqaction 结构”,这个结构中存放“handler 用户注册的中断处理
函数”等。
我们想使用自已的中断处理函数时,就是在内核的这个中断框架里在其中的“action 链表”
中添加进我们的“中断函数”。
4.分析“request_irq()” :
“request_irq()”在 kernel\irq\Manage.c 中。
在发生对应于第 1 个参数 irq 的中断时,则调用第 2 个参数 handler 指定的中断服务函
数(也就是把 handler() 中断服务函数注册到内核中 )。
irq:中断号。
handler:处理函数。
irqflags:上升沿触发,下降沿触发,边沿触发等。指定了快速中断或中断共享等中断处理属性.
*devname:中断名字。通常是设备驱动程序的名称。改值用在 /proc/interrupt 系统 (虚拟)
文件上,或内核发生中断错误时使用。
第 5 个参数 dev_id 可作为共享中断时的中断区别参数,也可以用来指定中断服务函数需
要参考的数据地址。
返回值:
函数运行正常时返回 0 ,否则返回对应错误的负值。
①分配一个“irqaction”结构。
②最后调用“setup_irq()”函数。设置中断
参 1:要安装中断处理程序的中断号。
参 2: irq 号中断的中断操作行为描述结构指针。
这里是上面 kmalloc 分配的 action 结构。
p = &desc->action;
desc 是“irq_desc[]”里面的数组项。这里即是找到了这个数组项。
action 是链表头,下面是判断这个链表开始有没有结构。若链表中原来一个中断结构。
则要判断是否“共享中断”。
若一个“action”链表头中挂接有多个项,就表明这个中断是“共享中断”。
“共享中断”:表示中断来源有很多种,但它们共享同一个引脚。
if (!((old->flags & new->flags) & IRQF_SHARED) || /* 是否是共享中断。若 action 链表头里挂
接了多个项就表明此中断为共享中断 */
((old->flags ^ new->flags) & IRQF_TRIGGER_MASK)) {
old_name = old->name;
goto mismatch;
}
如果之前那个老的 action 结构不是作“共享中断”时,我们就不能将申请的新结构放进这个老的“action”链表中去。就不能再次链接新的结构进去了。就表示出“goto mismatch;”出错。
这是将新的结构放进去。
接着“chip->set_type”将对应的引脚设置为“中断引脚”(外部中断,中断触发方式)。
然后“desc->chip->startup(irq);”和“desc->chip->enable(irq);”。这是最后“使能中断”
request_irq 是注册中断服务程序。
free_irq(irq,dev_id)来卸载这个中断服务程序。
参 1:要卸载中断处理程序的中断号。定位“action”链表。
参 2:使用 irq 号中断的设备的 ID 号或者设备驱动灵气指针。在“action” 链表中找到要卸载的表项。
同一个中断的不同中断处理函数必须使用不同的 dev_id 来区分。
构造了 action 结构,再放入 action 结构链表。再将对应的引脚设置成中断引脚。最后使能
中断。
原来假设事例中的 chip 结构:
int s3c_irqext_type(unsigned int irq, unsigned int type)
若为外部中断 0-3,则将上面的相关引脚配置成中断引脚。
③卸载中断处理函数:
④总结:
①,要注册用户中断处理函数时,用:
request_irq(中断号,处理函数,硬件触发试,中断名字,dev_id).
{
分配一个 irqaction 结构。结构指向 request_irq 中的参数。
将上面的 irqaction 结构放到 irq_desc[irq]数组项中的 action 链表中。
设置引脚成为中断引脚。
使能中断。
}
②,卸载中断处理函数。
void free_irq(unsigned int irq, void *dev_id)
{
将其从 action 链表中删除结构。
禁止中断(在 action 链表中没有成员结构 irqacton 时了,要是共享中断中删除一个结构还有
其他结构时,中断也不会禁止)。
}