》老师讲ARM课程:小明,你说说中断和缺页中断有什么区别?小明:他们不是同一个妈妈老师:小明同学,走开……”昨天我们和大家聊了中断处理函数中遇到的缺页中断。我们只讨论了do_page_fault()的内核处理路径。基本上这个处理路径已经包含了主要和最难理解的场景,当然对于简单的do_translation_fault()/do_sect_fault()路径,我们认为代码比较简单易懂,所以忽略。那么今天就让我们重新认识一下ARMv7上的异常处理吧。要了解ARMv7上的异常处理,自然离不开ARM官方手册。这个在ARM官网上可以下载,一共2600多页,不过不用担心,ARMv8手册一共6666页,是不是有点吓人?ARMv8,以后有机会再介绍给大家。01打开ARMv7手册,B1.8章开始介绍异常处理。开头先介绍一下异常向量表。需要注意的是,ARM的芯片手册的介绍,把几种模式一起介绍,Hyp模式,Monitor模式,Secure模式,非安全模式,这会让初学者看的有点眼花缭乱。将这些模式分成不同的章节可能会更好。假设我们只关注Linux内核运行的场景,那么我们应该关注非安全模式。这里显示异常处理主要包括prefetchabort、dataabort和undefinedabort。实际上,dataabort和prefetchabort都与pagefault中断有关。还有一个放置向量表的问题。传统的向量表可以放在0x0或者0xffff_0000这两个位置。当然SCTL寄存器的V域可以根据程序员的要求设置在向量表所在的位置。然后在B.1.8.3章节介绍了如果发生异常,ARM处理器会做那些事情。傻大叔把上图翻译成这样:硬件,首先要确定触发哪个异常?Prefetchabortordataorundefined,这应该不是我们的程序元拍头把异常发生的场景的CPSR保存到异常模式的SPSR寄存器,保存返回地址(returnaddress)到LR寄存器,并将CPSR.M字段设置为对应的模式,并切换到该模式。设置相应的掩码位,防止异常嵌套PC指向向量表中相应的地方,然后在出现go异常时以异常模式运行。ARM很奇怪,每种模式都有自己的堆栈,中断也是如此。通常硬件会带你进入异常模式,但是异常模式的栈空间有限,没办法保存所有上下文,怎么办?通常软件在异常模式下抖动后运行到SVC模式。下面笨叔叔以运行linuxkernel_4.0的git树代码为例。例如,现在发生数据中止,代码将跳转到异常向量表中的vector_dabt。(arch/arm/kerne/entry-armv.S)上面代码中的1188行代码,vector_stub是一个宏。有朋友会问,为什么这个宏的最后一个参数是8,而vector_irq是4?大家这道题还是得靠ARMv7手册,谁让我们吃穿的。B1.8.3章节中有这样一张表,意思是每次异常发生时,LR寄存器中存储的值是异常发生点的地址加上一个偏移量,因为是流水线。但是我们OS中保存的LR需要减去这个偏移量,我们在vector_stub宏代码中可以看到。vector_stub宏代码如下:笨叔大致分为4个部分来理解,就简单多了。第一部分减去刚刚提到的偏移量得到真正的LR返回地址,第二部分在异常发生时将LR和SPSR寄存器保存到异常栈中,第三部分切换到SVC模式,第四部分判断是否异常发生在用户态的还是内核态,然后分别跳转到_dabt_svc和_dabt_usr。vector_stub宏的每行代码的解释可以在《奔跑吧linux内核》的第621页找到。下面以_data_svc为例。svc_entry和svc_exit都是宏。具体代码分析见《奔跑吧linux内核》的5.1.4小节。对应ARMv7处理器,最终会运行到汇编函数v7_early_abort。第16和17行是什么意思?涉及到的两个寄存器是FSR和FAR。我们先看芯片手册B3.13.1章节。这里我们介绍当异常发生时,我们如何知道发生了什么。因为硬件可以准确的获取到异常的原因和地址,而我们的程序员却不知道,所以需要通过寄存器来告诉我们对不对?这里巴拉巴拉告诉大家,有一个寄存器FSR,存放异常状态信息,还有一个寄存器FAR,存放异常发生的地址。这两个寄存器在哪里描述?关于这两个寄存器的详细说明,请参见B4.1.51和B4.1.52节。其中最重要的是FS寄存器域。注意这里的FS域由两部分组成,一个是BIT[0~3]和BIT[10],是不是有点奇怪?代码是这样的。至少我们是对应的。读FS域有什么用?请参考手册的B3.13.3章节,这里有一个表格来描述FS域的用法。该表定义了多种页面错误异常,如翻译错误、访问错误、域错误、权限错误等。do_DataAbort()函数如下。可以关注547行,这里fsr_fs()刚才已经讲过了,fsr_info定义为一个表。通过FSR寄存器的FS域查表,然后跳转到相应的处理函数。可以对比一下代码和FS表,看看什么情况下会走到do_translation_fault()/do_sect_fault(),什么情况下会走到do_page_fault。ARM中也有一个异常,叫做prefetchabort,代码路径和原理类似,可以自行阅读ARMv7手册和对应代码。我们跑吧!Linux社区坚持原创文章,正宗且绝不转载!喜欢的话记得打赏转发,谢谢!预告:奔跑吧Linux社区第二季《进程、锁和中断三合一》,初级篇和旗舰篇即将上线,敬请期待!
