[笨叔点滴13]哪些异常处理的事儿

“ 老师在讲ARM课程:小明,你来说说中断和缺页中断有啥区别?
小明:他们不是一个妈生的
老师:小明同学,滚。。。

昨天我们和大家聊了一下中断处理函数里遇到缺页中断的那些事儿,我们只讨论了do_page_fault()这条内核处理路径,基本上这条处理路径已经包含了主要和最难理解的场景,当然对于简单的do_translation_fault()/do_sect_fault()这条路径我们认为代码比较简单,很好理解,所以就忽略了。
那我们今天来重新认识一下ARM v7上的异常处理的那些事儿。要认识ARM v7上的异常处理,我们自然离不开ARM官方的手册了。这个大家在ARM官网上下载就行,一共2600多页,不过不用担心,ARM v8手册一共6666页,是不是有点恐怖? ARM v8的,我们以后有机会再和大家介绍吧。
01
翻开ARM v7手册,在第B1.8章里就开始介绍异常处理了。一开始就给大家介绍异常向量表。
[笨叔点滴13]哪些异常处理的事儿
文章图片

大家需要留意的是,ARM的芯片手册的介绍是把几个模式都揉在一起介绍的,如Hyp模式,Monitor模式,Secure模式,non-secure模式,这样会让初学者看起来有点混乱,若把这几个模式单独章节也许更好一些。假设我们只关注Linux kernel运行的场景,那我们关注non-secure模式就好了。这里显示异常处理主要有预取abort,data abort和undefined abort。其实和缺页中断相关的是data abort和预取abort。
还有一个向量表安放的问题,传统的向量表可以放在0x0或者0xffff_0000这两个位置,当然设置SCTL寄存器的V域,可以按照程序猿的要求来比较随意的安放这个向量表的位置。
然后在第B.1.8.3章里介绍了如果一个异常发生了,ARM处理器会做了那些事情。
[笨叔点滴13]哪些异常处理的事儿
文章图片

上图让笨叔翻译一下的话就是这样的:
硬件啊,你首先要确定要触发那个异常?预取abort还是data还是undefined,这个不应该是我们程序媛来拍脑袋
保存发生异常那个现场的CPSR到 异常模式的SPSR寄存器
保存返回地址(return address)到LR寄存器
设置CPSR.M域到相应的模式,并切换到该模式。设置相应的mask bits,防止异常嵌套
PC指向向量表的对应的地方,然后go
异常发生的时候是跑在异常模式的。ARM很奇怪,每个模式都有自己的堆栈,和中断一样。通常硬件就帮你带到了异常模式里,但是异常模式的堆栈大小有限啊,没有办法保存所有的上下文,那怎么办?通常软件是在异常模式里晃了一下就跑到SVC模式去的。
下面笨叔以runninglinuxkernel_4.0这个git tree代码为例,比如现在data abort发生了,代码会跳转到异常向量表里的vector_dabt里。(arch/arm/kerne/entry-armv.S)
[笨叔点滴13]哪些异常处理的事儿
文章图片

在上面代码中1188行代码,vector_stub是一个宏。有的小伙伴会问了,为啥这个宏最后的一个参数是8,而vector_irq是4呢?这个问题大家还是要依赖ARM v7手册啊,谁让它是我们的衣食父母呢。在第B1.8.3章里有这么一个表,意思是说在每个异常发生的时候,LR寄存器存放的值是发生异常那个点的地址加上了一个offset,因为流水线的原因。但是我们OS里保存的LR需要减去这个offset,我们在vector_stub宏代码里可以看到。
[笨叔点滴13]哪些异常处理的事儿
文章图片

vector_stub宏代码如下:
[笨叔点滴13]哪些异常处理的事儿
文章图片

笨叔大致把它分成4部分来理解就简单多了。第一部分减掉刚才说的offset得到真正的LR返回地址,第二部分保存异常发生时 LR和SPSR寄存器到 异常的栈里,第三部分切换到SVC模式,第4部分判断异常发生是在用户态还是内核态,然后分别跳转到_dabt_svc和_dabt_usr里面。
vector_stub这个宏的具体每一行代码的解释可以看《奔跑吧linux内核》第621页。
下面以_data_svc为例。
[笨叔点滴13]哪些异常处理的事儿
文章图片

svc_entry和svc_exit都是宏,具体代码分析见《奔跑吧linux内核》第5.1.4节内容。
对应ARM v7处理器,最后会跑到v7_early_abort这个汇编函数里,
[笨叔点滴13]哪些异常处理的事儿
文章图片

第16和17行啥意思呢?
这里面涉及到两个寄存器分别是FSR和FAR,我们先看芯片手册的第B3.13.1章,这里就介绍了当发生异常,我们怎么能知道发了啥事呢?因为硬件是能准确get到异常发生的原因和地址的,但是我们程序猿不知道,所以需要通过寄存器的方式来告诉我们,对吧?
[笨叔点滴13]哪些异常处理的事儿
文章图片

这里巴拉巴拉的和你说了,有一个叫做FSR寄存器存放了异常状态信息,还有一个叫做FAR寄存器存放了异常发生的地址。这两个寄存器分别描述在哪里呢?可以看第B4.1.51和第B4.1.52节,里面有这两个寄存器详细描述。
[笨叔点滴13]哪些异常处理的事儿
文章图片

其中最重要的是FS寄存器域,注意了,这里FS域是有两部分组成的,一个是BIT [0~3]和BIT[10],是不是有点奇葩呢。代码是这样的。
[笨叔点滴13]哪些异常处理的事儿
文章图片

至少我们是对应上的。那读出来的FS域有啥用呢?大家看手册的第B3.13.3章,这里有一个表来描述FS域的用法。
[笨叔点滴13]哪些异常处理的事儿
文章图片

这表里定义了好多种缺页异常的类型,比如有translation fault,access fault,domain fault,permission fault等。
do_DataAbort()函数如下,大家可以重点看第547行,这里fsr_fs()刚才提过了,fsr_info定义成一个table。
[笨叔点滴13]哪些异常处理的事儿
文章图片

通过FSR寄存器的FS域来进行查表,然后跳转到对应的处理函数里。
[笨叔点滴13]哪些异常处理的事儿
文章图片

大家可以对照代码和FS的表来看看什么情况下会跑到do_translation_fault()/do_sect_fault(),而什么情况下跑到do_page_fault。
ARM里面还有一种异常,叫做预取abort,代码路径和原理是类似的, 小伙伴可以自行阅读ARM v7手册和相应的代码来。
奔跑吧Linux社区坚持原创文章,原汁原味,绝不转载!喜欢的话,记得打赏和转发,谢谢!
[笨叔点滴13]哪些异常处理的事儿
文章图片

【[笨叔点滴13]哪些异常处理的事儿】预告:奔跑吧Linux社区第二季《进程、锁和中断三合一》,初级篇和旗舰篇很快要上线了,敬请关注!

    推荐阅读