在学习资料满天飞的大环境下,知识变得非常零散,体系化的知识并不多,这就导致很多人每天都努力学习到感动自己,最终却收效甚微,甚至放弃学习。我的使命就是过滤掉大量的无效信息,将知识体系化,以短平快的方式直达问题本质,把大家从大海捞针的痛苦中解脱出来。
中断从哪里来,到哪里去?中断号是如何分配的?带着这些疑问一起探索吧~
4 中断处理流程
前面的内容涉及的都是中断的准备工作,本节才真正打通从硬件中断产生到中断处理的整个流程。
4.1 汇编部分
汇编部分的代码主要负责和处理器架构相关的部分,例如中断向量的选择、中断现场保护和恢复等。
4.1.1 中断向量重映射
我们以 trap_init 函数作为突破口,来按图索骥地找到中断向量的汇编代码。
能够想到从trap_init函数作为突破口,基于下面两点:
- 中断可以看作陷阱的一种。
- trap_init函数被start_kernel函数调用了。
trap_init函数主要的功能就是将vectors、stubs等代码搬移到0xffff0000地址开始的所在页。这个动作我们可以给它起个大气的名字—— 重映射/重定位 。其中,vectors为中断向量表,stubs为中断处理函数总入口。
那我们为什么必须要进行中断向量重映射呢?
ARM架构的CPU的异常向量基址由 CONFIG_VECTORS_BASE 决定,不开启MMU时配置为 0x00000000 ,否则配置为 0xffff0000 。我们这里是开启了MMU的,因此CPU的异常向量基址是0xfff0000。如果异常向量基址只能是0x00000000,可以选择将中断向量代码直接链接在该地址。正是因为0xfff0000的存在才使得中断向量重映射的步骤必须存在。
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;
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);
memcpy((void *)KERN_SIGRETURN_CODE, sigreturn_codes,
sizeof(sigreturn_codes));
flush_icache_range(vectors, vectors + PAGE_SIZE);
modify_domain(DOMAIN_USER, DOMAIN_CLIENT);
}
4.1.2 中断向量选择
好了,既然我们已经知道中断向量放在了__vectors_start开始的代码中,我们就一起来分析下 __vectors_start的代码。
代码如下所示,可以看到格式统一且简单,就是简单的跳转。对于外部中断发生时,硬件会自动控制PC指针指向 b vector_irq + stubs_offset 代码执行,也就是所谓的中断向量选择。
需要注意的一点是vector_irq代码在trap_init函数中进行过重定位,因此跳转时需要进行相对偏移的矫正,即加上偏移量stubs_offset。
.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
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:
4.1.3 中断现场保护和恢复
4.1.3.1 vector_irq
继续顺藤摸瓜,下一步就是找到函数vector_irq。我们直接搜索是找不到vector_irq函数的,因为vector_irq是通过宏定义的,代码如下:
.globl __stubs_start
__stubs_start:
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)
.long __irq_invalid @ 4
...
__vectors_end:
将 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, #(IRQ_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
.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
...
__vectors_end:
vector_irq根据进入中断前的模式又做了一次跳转。
4.1.3.2 __irq_usr
这里,我们选择__irq_usr进行分析。该函数包含了中断处理的大体框架:现场保护,中断处理和中断返回等。由于内容较多,不一一展开了,后面重点分析irq_handler。
.align 5
__irq_usr:
usr_entry
...
get_thread_info tsk
...
irq_handler
...
mov why, #0
b ret_to_user
.ltorg
4.1.3.3 irq_handler
irq_handler主要的工作就是获取两个参数:irq number和struct pt_regs *。然后调用asm_do_IRQ并将参数传递给它。
之后中断的处理工作就正式交接给C语言进行。
.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
4.2 C语言部分
4.2.1 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;
if (irq >= NR_IRQS)
desc = &bad_irq_desc;
irq_enter();
desc_handle_irq(irq, desc);
irq_finish(irq);
irq_exit();
set_irq_regs(old_regs);
}
desc_handle_irq源码如下:
static inline void desc_handle_irq(unsigned int irq, struct irq_desc *desc)
{
desc->handle_irq(irq, desc);
}
其中desc->handle_irq是一个函数指针(中断框架前面的文章中分析过),它的初始化在setup_arch中完成。
4.2.2 handle_IRQ_event
desc->handle_irq指向的中断处理函数又调用了handle_IRQ_event函数来调用用户注册的中断服务例程。
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;
}
至此,对中断处理在C语言部分的处理流程再做一个梳理:
- 根据中断号irq找到对应的中断描述符desc。
- 调用desc->handle_irq函数指针,该指针初始化在setup_arch函数中完成。
- 调用handle_IRQ_event函数,并在该函数中对用户注册的中断例程进行遍历执行。
恭喜你又坚持看完了一篇博客,又进步了一点点!如果感觉还不错就点个赞再走吧,你的点赞和关注将是我持续输出的哒哒哒动力~~
|