异常
1.1 异常与中断
异常 和 中断 这 2 个词相信在各位学习Linux的读者严重不是很陌生。这 2 者在笔者理解来是属于 CPU 的概念,每个 CPU 都会发生 异常 和 中断。
-
异常:在《ARM嵌入式系统开发》中的定义是 需要中止指令正常执行的任何情形。而这样的情形在 ARM 架构中一共有 7 种,分别是 复位、中断请求(irq)、快速中断请求(fiq)、软件中断(swi/svc)、数据访问中止、预取指中止 及 未定义指令。当 CPU 触发异常时,对自行切换到每种异常相应的 模式,然后跳到 异常向量表 去执行我们相应的异常处理。
-
中断:我们可以在上面中看到,中断只是 异常 的一种情况,它可以由 软件 和 硬件 产生,同理的,它也会跳到 异常向量表 中的地方去执行我们的中断处理,只是这个中断处理不像沃我们理解中的 中断处理函数,它处理的比这复杂得多。
1.2 异常向量表
在初步了解完 异常 和 中断 后,我们先别急着去看 中断,我们先看看 异常向量表,这个异常向量表与我们执行中断的时候绕不过去的一个环节。下面我们就来看一看它是怎么样的。
异常向量表 是指 异常发生时,ARM内核跳转地址组成的表,从这个定义中就可以知道,这个表是可以又我们软件指定,而这个 跳转地址 其实也就是我们的 异常处理函数 的地址。
那么在这里可能有几个疑问 异常向量表的跳转地址是多少,如何跳转到对应异常的处理函数。
1.1.1 异常向量表地址
首先,在 ARM架构 中,异常向量表 是有固定的地址的,每当 CPU 发生异常时,都会自动的跳转到该地址,我们把这个地址简称为 异常地址。异常地址 有 2 种模式
- 低地址模式:在该模式下,异常向量表 的地址为 0x00000000
- 高地址模式:在该模式下,异常向量表 的地址为 0xffff0000
模式的设置可以通过 CP15协处理 来设置,相关知识请各位读者自行了解,这里先不多说。
1.1.2 异常向量表跳转
我们上面说了异常有多个, 每个异常都有自己的对应的处理函数。而 ARM 的异常向量表跳转地址如下图所示,下图以低地址模式为例。可以看到,每一个 异常 都有自己的跳转地址,而在 CPU 触发异常后,会根据相应的异常而跳转到不同的地址,从而执行不同异常处理函数

1.1.3 异常向量表代码简析
我们来看看 异常向量表 的初始化。我们先看看代码调用关系视图,从宏观理解代码的调用关系
start_kernel(main.c)
->setup_arch(setup.c)
->paging_init(mmu.c)
->devicemap_init(mmu.c)
->early_trap_init(traps.c)
下面我们根据调用关系逐步看一下代码的初始化过程
/* 异常向量表的处理过程 */
void __init setup_arch(char **cmdline_p)
{
const struct machine_desc *mdesc;
mdesc = setup_machine_fdt(__atags_pointer);
...
paging_init(mdesc)
...
}
void __init paging_init(const struct machine_desc *mdesc)
{
...
devicemaps_init(mdesc);
...
}
static void __init devicemaps_init(const struct machine_desc *mdesc)
{
...
void *vectors;
vectors = early_alloc(PAGE_SIZE * 2);
early_trap_init(vectors);
...
/*
* Create a mapping for the machine vectors at the high-vectors
* location (0xffff0000). If we aren't using high-vectors, also
* create a mapping at the low-vectors virtual address.
*/
map.pfn = __phys_to_pfn(virt_to_phys(vectors));
map.virtual = 0xffff0000;
map.length = PAGE_SIZE;
#ifdef CONFIG_KUSER_HELPERS
map.type = MT_HIGH_VECTORS;
#else
map.type = MT_LOW_VECTORS;
#endif
create_mapping(&map);
if (!vectors_high()) {
map.virtual = 0;
map.length = PAGE_SIZE * 2;
map.type = MT_LOW_VECTORS;
create_mapping(&map);
}
/* Now create a kernel read-only mapping */
map.pfn += 1;
map.virtual = 0xffff0000 + PAGE_SIZE;
map.length = PAGE_SIZE;
map.type = MT_LOW_VECTORS;
create_mapping(&map);
}
void __init early_trap_init(void *vectors_base)
{
#ifndef CONFIG_CPU_V7M
unsigned long vectors = (unsigned long)vectors_base;
extern char __stubs_start[], __stubs_end[];
extern char __vectors_start[], __vectors_end[];
unsigned i;
vectors_page = vectors_base;
....
/*
* 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 + 0x1000, __stubs_start, __stubs_end - __stubs_start);
kuser_init(vectors_base);
flush_icache_range(vectors, vectors + PAGE_SIZE * 2);
#else /* ifndef CONFIG_CPU_V7M */
/*
* on V7-M there is no need to copy the vector table to a dedicated
* memory area. The address is configurable and so a table in the kernel
* image can be used.
*/
#endif
}
/* 异常向量表在代码中的位置:arch/arm/kernel/entry_armv.S */
.L__vectors_start:
W(b) vector_rst
W(b) vector_und
W(ldr) pc, .L__vectors_start + 0x1000
W(b) vector_pabt
W(b) vector_dabt
W(b) vector_addrexcptn
W(b) vector_irq
W(b) vector_fiq
.data
.align 2
.globl cr_alignment
cr_alignment:
.space 4
#ifdef CONFIG_MULTI_IRQ_HANDLER
.globl handle_arch_irq
handle_arch_irq:
.space 4
#endif
/* 异常向量表地址的设置代码 */
/* 代码位于proc-v7.S */
__errata_finish:
adr r3, v7_crval
ldmia r3, {r3, r6}
ARM_BE8(orr r6, r6, #1 << 25) @ big-endian page tables
#ifdef CONFIG_SWP_EMULATE
orr r3, r3, #(1 << 10) @ set SW bit in "clear"
bic r6, r6, #(1 << 10) @ clear it in "mmuset"
#endif
mrc p15, 0, r0, c1, c0, 0 @ read control register
bic r0, r0, r3 @ clear bits them
orr r0, r0, r6 @ set them
/* 代码位于proc-v7-2level.S */
v7_crval:
crval clear=0x2120c302, mmuset=0x10c03c7d, ucset=0x00c01c7c
在初始化 异常向量表 的过程中还有很多其他机制的初始化,为了简化思路,笔者将其他的无关代码删去。
我们直接看到 devicemaps_init ,在这里我们看到它分配了 2 个内存页,并把地址赋值给 vectors,然后接着在后面我可以看到对 vectors 进行内存映射的操作,根据内核的宏定义来将 vectors 映射在 0xffff0000 或 0x00000000,一般来说是 高地址模式 ,这就是我们说的 异常向量表 内存地址。对的,其实触发异常后我们的pc 指针是跳到 虚拟内存地址 的 0xffff0000。
接着我们看看 early_trap_init, 很明显我们在这里可以看到有一个拷贝过程,而这个过程是将 __vectors_start 和 __vectors_end 之间的内容拷贝到 vectors 所指向的内存,其实读者们都想到了,__vectors_start就是 异常向量表 的地址。那么到了这里,我们就知道 异常向量表 的映射问题。
再接着,我们来看看异常向量表长什么样,第二个代码段就给出了 __vectors_start 所在位置的 异常向量表 ,而我们也可以看到它也是按照 CPU 的异常向量表的排布编写的,其中 vector_irq 和 vector_fiq 就是我们今天要说的 中断。
打破砂锅问到底,最后我们看看代码中是在哪里设置 向量表 的 地址模式。这里要补充一个知识,向量表的地址模式是由协处理CP15中的决定的 ,有了这个条件,我们就可以看看代码是怎么写的。在代码段 __errata_finish 中会调用 v7_crval,而 v7_crval 则声明了 3 个变量,这 3 个变量就是要用于配置 MMU 的。按照笔者的理解,代码是大意为:将 v7_crval 声明的 3 个变量装载到 r3 指向的地址中,然后再将它们装载到 r3-r6 这几个寄存器,其中 r3 的值就是高低地址模式的掩码。
到了这里,我们就可以解决我们的主要问题了。以上没有很详尽的交代所有的细节,因为牵涉到的知识点比较多,笔者主要是梳理思路,以在学习的过程中有迹可循,在结合其余的知识就可以比较的理解代码。
本文深入探讨了ARM架构下的异常与中断概念,详细解析了异常向量表的初始化过程和地址模式设置,以及异常处理函数的跳转机制。

5654

被折叠的 条评论
为什么被折叠?



