【Linux中断子系统】学习中断必备的基础知识之中断与异常

本文深入探讨了ARM架构下的异常与中断概念,详细解析了异常向量表的初始化过程和地址模式设置,以及异常处理函数的跳转机制。

异常

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 映射在 0xffff00000x00000000,一般来说是 高地址模式 ,这就是我们说的 异常向量表 内存地址。对的,其实触发异常后我们的pc 指针是跳到 虚拟内存地址 的 0xffff0000

接着我们看看 early_trap_init, 很明显我们在这里可以看到有一个拷贝过程,而这个过程是将 __vectors_start__vectors_end 之间的内容拷贝到 vectors 所指向的内存,其实读者们都想到了,__vectors_start就是 异常向量表 的地址。那么到了这里,我们就知道 异常向量表 的映射问题。

再接着,我们来看看异常向量表长什么样,第二个代码段就给出了 __vectors_start 所在位置的 异常向量表 ,而我们也可以看到它也是按照 CPU 的异常向量表的排布编写的,其中 vector_irqvector_fiq 就是我们今天要说的 中断

打破砂锅问到底,最后我们看看代码中是在哪里设置 向量表地址模式。这里要补充一个知识,向量表的地址模式是由协处理CP15中的决定的 ,有了这个条件,我们就可以看看代码是怎么写的。在代码段 __errata_finish 中会调用 v7_crval,而 v7_crval 则声明了 3 个变量,这 3 个变量就是要用于配置 MMU 的。按照笔者的理解,代码是大意为:将 v7_crval 声明的 3 个变量装载到 r3 指向的地址中,然后再将它们装载到 r3-r6 这几个寄存器,其中 r3 的值就是高低地址模式的掩码。

到了这里,我们就可以解决我们的主要问题了。以上没有很详尽的交代所有的细节,因为牵涉到的知识点比较多,笔者主要是梳理思路,以在学习的过程中有迹可循,在结合其余的知识就可以比较的理解代码。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值