《Cortex™-A系列编程者指南(V3.0)》第14章<其它异常处理>笔记

来源:互联网 发布:岁月的童话 知乎 编辑:程序博客网 时间:2024/05/21 10:31


  在本章中,我们会简单的看看用于中止、未定义指令和SVC指令的中断处理程序,看看Linux内核是如何处理中断的。复位处理程序在第15章启动代码里将会深入涉及。


14.1 中止处理程序


  中止处理程序的代码在系统之间可能是极为不同的。在很多嵌入式系统中,一个中止表示一个未期望的错误,处理程序会记录任何特征信息报告错误让应用(或系统)优雅地退出


  使用MMU支持虚拟内存的系统中,中止处理程序可以加载需要的虚拟页到物理内存。实际上,它试图修复引起最初中止的原因,然后返回到中止的指令并重新执行。第十章内存管理单元给出了Linux如何做的的进一步信息。


  CP15寄存器提供引起中断所访问的内存地址(错误地址寄存器,Fault Address Register)引起中断的原因(错误状态寄存器,Fault Status Register)。原因可能是缺少访问权限,一个外部中止或一个页表翻译错误。另外,链接寄存器(带-8或-4的调整,取决于中止是否由一个指令获取或一个数据访问引起)在中止异常之前给出执行指令的地址。通过检查这些寄存器,执行的最后指令和系统中可能的其它事情(例如,页表入口),中断处理程序可以确定需要采取的措施。


14.2 未定义指令处理


  如果处理器试图执行一条带如ARM架构规范描述的UNDEFINED的操作码的指令,未定义指令异常被获取,或者当一条协处理指令被执行但是没有协处理器识别它是一条可执行的指令


  在一些系统中,代码包含协处理器的指令(例如一个VFP协处理器),但是当前系统没有相应的VFP硬件是可能的。另外,也可能是VFP硬件不能处理特定指令,希望调用软件来模拟。或者是,VFP硬件被禁止,我们可以获取到异常从而使能并执行指令。


  这样的模拟器通过未定义指令向量调用。我们检查引起异常的指令操作码,决定采取的措施(例如,在软件中执行合适的浮点运算)。在一些情况中,这样的处理程序可能需要被菊链在一起(例如,可能有多个协处理器来模拟)。


  如果没有软件可以使用未定义的或协处理器指令,异常的处理程序应该记录合适的调试信息并杀死由于未期望事件导致失败的应用程序。
在一些情况中,未定义指令异常的额外用途是实现用户断点,看第29章调试获取关于断点的更多信息。(也可以在第7章看Linux上下文切换的描述。)


14.3 SVC异常处理


  超级用户调用(supervisor call, SVC)通常被用于允许用户模式代码访问操作系统函数。例如,如果用户代码希望访问系统的特权部分(例如,执行文件I/O),它通常会使用SVC指令来做。


  通过使用操作码中的注释字段在寄存器中参数可以被传递到SVC处理程序


  Linux内核中说明SVC使用的代码如样例14-1所示。


  SVC#0指令使得ARM处理器获取到SVC异常,这是访问内核函数的机制。寄存器R7定义我们需要调用的系统调用(这里是sys_write)。其它参数在寄存器中被传递;对于sys_write我们有R0告诉写在哪里,R1指向需要写的字符,R2给出字符串的长度。


  另一个使用SVC指令的例子可以被应用程序开发者看到。通过ARM使用SVC 0x123456(ARM状态)或SVC 0xAB(Thumb)开发的工具来表示半自动的调试函数(例如,在调试器窗口输出一个字符)。


14.4 Linux异常程序流


  Linux为异常处理使用一个跨平台框架在处理异常时不在不同的特权处理器模式之间作区分。因此,ARM实现使用一个异常处理桩来允许内核在SVC模式处理所有的异常。除了SVC和FIQ,所有的异常使用桩来切换到SVC模式并且陷入正确的异常处理程序


14.4.1 启动过程


  在启动过程中,内核会分配一个4KB的页作为向量页。它被映射到异常向量的位置,虚拟地址是0xFFFF0000或0x00000000。这是在文件arch/arm/mm/mmu.c中由devicemaps_init()完成的。


  在ARM系统中这是非常早期的调用。在此之后,trap_init (在arch/arm/kernel/traps.c),拷贝异常向量表异常桩(exception stubs)kuser helpers向量页。异常向量表显式地被拷贝到向量页的开始,异常桩被拷贝到地址0x200(kuser helpers被拷贝到页的顶部,在0x100 - kusr_sz),使用一系列的memcpy()操作,如样例14-2中所示。


  当拷贝完成,内核异常处理程序在它的运行时动态状态,准备处理异常。


14.4.2 中断调度


  有两种不同的处理程序,__irq_usr__irq_svc。这些保存所有的处理器寄存器,并使用一个宏get_irqnr_and_base表示是否有中断挂起。


  处理程序循环这个代码直到没有剩余中断。如果有中断,代码会跳转到位于arch/arm/kernel/irq.c的do_IRQ


  此时,在所有的架构中代码是相同的,我们调用一个C编写的合适的处理程序。


  然而,需要进一步考虑的一点。当中断完成,我们通常需要检查是否处理程序已完成某个需要调用调度器的事情。如果调度去决定进入一个不同的线程,最初被中断的保持睡眠直到被选择再次运行。


0 0