查看原文
其他

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

小笨叔 奔跑吧Linux社区 2019-04-24

 老师在讲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章里就开始介绍异常处理了。一开始就给大家介绍异常向量表。


大家需要留意的是,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处理器会做了那些事情。


上图让笨叔翻译一下的话就是这样的:

  1. 硬件啊,你首先要确定要触发那个异常?预取abort还是data还是undefined,这个不应该是我们程序媛来拍脑袋

  2. 保存发生异常那个现场的CPSR到 异常模式的SPSR寄存器

  3. 保存返回地址(return address)到LR寄存器

  4. 设置CPSR.M域到相应的模式,并切换到该模式。设置相应的mask bits,防止异常嵌套

  5. PC指向向量表的对应的地方,然后go


  异常发生的时候是跑在异常模式的。ARM很奇怪,每个模式都有自己的堆栈,和中断一样。通常硬件就帮你带到了异常模式里,但是异常模式的堆栈大小有限啊,没有办法保存所有的上下文,那怎么办?通常软件是在异常模式里晃了一下就跑到SVC模式去的。


下面笨叔以runninglinuxkernel_4.0这个git tree代码为例,比如现在data abort发生了,代码会跳转到异常向量表里的vector_dabt里。(arch/arm/kerne/entry-armv.S)

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


vector_stub宏代码如下:

笨叔大致把它分成4部分来理解就简单多了。第一部分减掉刚才说的offset得到真正的LR返回地址,第二部分保存异常发生时 LR和SPSR寄存器到 异常的栈里,第三部分切换到SVC模式,第4部分判断异常发生是在用户态还是内核态,然后分别跳转到_dabt_svc和_dabt_usr里面。


vector_stub这个宏的具体每一行代码的解释可以看《奔跑吧linux内核》第621页。


下面以_data_svc为例。


svc_entry和svc_exit都是宏,具体代码分析见《奔跑吧linux内核》第5.1.4节内容


对应ARM v7处理器,最后会跑到v7_early_abort这个汇编函数里,


第16和17行啥意思呢?

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

这里巴拉巴拉的和你说了,有一个叫做FSR寄存器存放了异常状态信息,还有一个叫做FAR寄存器存放了异常发生的地址。这两个寄存器分别描述在哪里呢?可以看第B4.1.51和第B4.1.52节,里面有这两个寄存器详细描述。

其中最重要的是FS寄存器域,注意了,这里FS域是有两部分组成的,一个是BIT [0~3]和BIT[10],是不是有点奇葩呢。代码是这样的。

至少我们是对应上的。那读出来的FS域有啥用呢?大家看手册的第B3.13.3章,这里有一个表来描述FS域的用法。

这表里定义了好多种缺页异常的类型,比如有translation fault,access fault,domain fault,permission fault等。


do_DataAbort()函数如下,大家可以重点看第547行,这里fsr_fs()刚才提过了,fsr_info定义成一个table。


通过FSR寄存器的FS域来进行查表,然后跳转到对应的处理函数里。



大家可以对照代码和FS的表来看看什么情况下会跑到do_translation_fault()/do_sect_fault(),而什么情况下跑到do_page_fault。


ARM里面还有一种异常,叫做预取abort,代码路径和原理是类似的, 小伙伴可以自行阅读ARM v7手册和相应的代码来。


奔跑吧Linux社区坚持原创文章,原汁原味,绝不转载!喜欢的话,记得打赏和转发,谢谢!


预告:奔跑吧Linux社区第二季《进程、锁和中断三合一》,初级篇和旗舰篇很快要上线了,敬请关注!


[往期精彩]

[旗舰篇] 第一季旗舰篇资料汇总

不能不会的VIM+GIT

《奔跑吧Linux內核》繁體版預覽

[笨叔点滴12]面试必考:如果在中断处理函数里发生了缺页中断会怎样?为什么?

[笨叔点滴11] malloc惹的祸

[笨叔点滴10] 中断到“底”来了吗?

[笨叔点滴9] GNU GCC扩展2

[笨叔点滴8] GNU C语言的扩展

[笨叔点滴7] 再也回不去的C语言

[笨叔点滴6] 叔,这个git咋玩啊?

[笨叔点滴5] git rebase和git merge究竟有啥区别?

[笨叔点滴4]“栈”谁便宜了2

[笨叔点滴3] “栈”谁便宜了?

[笨叔点滴2] 为啥子ARM32体系结构中每个处理模式都有一个单独的栈?

[笨叔点滴1] 为什么do_page_fault函数里代码需要判断用户态还是内核态?

LinuxCon 2018北京游记(1)

LinuxCon2018北京笨叔笨游记 2

《奔跑吧linux内核》配套资源迁移到码云上

考点来了:4月18号视频更新

代码导读之如何使用qemu来单步调试head.S

代码导读之内存管理初始化 - 启动汇编

视频更新:内存管理代码框架导读

DMA那些事儿

私密VIP群答疑

高级运维必杀技:如何图形化单步调试RHEL/Centos 7里的内核?

首发:Meltdown漏洞分析与实践

[奔跑吧Linux内核] 故乡

致敬Beyond

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存