Linux系统调用源码分析(三)

前文讲了Linux系统调用的定义、函数实现及系统处理的流程。现在我们继续以ARMv8为例讲一下与系统调用相关的异常处理的内容,包括异常的级别与类型,异常向量表,系统调用的异常处理源码等。

图片

 

一、异常的级别与类型

按ARM官方文档中的定义,“Exceptions are conditions or system events that require some action by privileged software (an exception handler) to ensure smooth functioning of the system”。异常是需要特权软件(异常处理程序)执行某些操作以确保程序顺利运行的某些条件或系统事件

异常处理会导致程序执行流的中断,分为同步异常异步异常。它们之间最明显的区别是同步异常的触发指令是确定的,每次程序执行到那里都会触发,比如程序执行系统调用svc指令异步异常的触发指令是不确定的,程序有可能在任何时候触发异步异常,通常我们把异步异常称为中断

中断又分为普通中断和快速中断,外设通过拉低中断线将触发普通中断,内核处理普通中断时又把它分为上半部和下半部,时间紧迫的放在上半部处理,计算量大或耗时较长的放在下半部处理。快速中断的例子资料不多,不作解析。

系统错误也是一种异步中断,CPU执行指令的过程中会遇到各种各样的问题,如果不能归为同步异常的错误统统归为系统错误异常来处理。

还有一种特殊的异常被称为Reset异常,是CPU实现时根据具体的配置而预定的,可以读取RVBAR_ELn获取地址,通常CPU在上电初始化时执行此异常程序

 

ARMv8定义了四种异常级别,从低到高分别是EL0-User,EL1-Supervisor,EL2-Hypervisor,EL3-Secure Monitor,如下图所示:

图片

EL0-User,一般的应用程序运行在此级别。

EL1-Suervisor,操作系统运行在此级别,Linux内核,设备驱动等都运行在此级别。

EL2-Hypervisor,虚拟机系统运行在此级别,在此级别Guest客户机的虚拟内存需要做多一层Stage2的地址转换。

EL3-Secure monitor,安全级别异常,用的较少,不作解析。

程序通过SVC,HVC,SMC指令向高级别异常迁移,因为异常级别只能从低级别往高级别迁移,所以没有指令可以从高级别往低级别迁移,只有当异常处理完成后,通过ERET指令返回到原来的异常级别。

 

二、异常向量

异常向量CPU处理异常时的程序入口地址,所有的向量聚在一起的首地址就是异常向量表的地址。当异常触发时,CPU根据系统寄存器VBAR_ELx的值找到异常向量表的地址,然后根据异常类型确定偏移量,最后找到入口地址并执行之。

程序在初始化时,设置vbar_el1

adr_l x8, vectors

msr vbar_el1, x8

VBAR_EL1的值是异常级别EL1的向量表基地址。CPU一共有三个此类寄存器对应相应的级别,分别是VBAR_EL1,VBAR_EL2,VBAR_EL3。可能有朋友会问,为什么没有VBAR_EL0,原因是异常级别只能从低级别往高级别迁移,EL0级别最低不会是异常迁移的标的级别,此级别只能往高级别迁移,或者从高级别返回来。

每个级别的向量表分成四组情形来处理触发异常时的不同情景,每一种情形又分成四种类型处理不同的异常。

第一组

当系统寄存器SPSel值为0时,CPU使用此组向量。系统使用SP_EL0而不是SP_ELx为堆栈指针,Linux没有使用此情形,如果意外触发当无效异常来处理。

第二组

SPSel值为1时,CPU在每个EL中使用各自的SP_ELx作为堆栈指针。Linux初始化时设置SPSel为1,即发生异常时取VBAR_ELx+0x200为异常处理入口地址。

第三组

当异常从aarch64低级别迁移到当前级别时使用此组向量。Linux系统调用就是从EL0迁移到EL1的情形,即发生异常时,取VBAR_ELx+0x400为异常处理入口地址。

第四组

当异常从aarch32低级别迁移到当前级别时使用此组向量,如果Linux定义了CONFIG_COMPAT,则实现兼容aarch32模式,否则当作无效异常处理。

 

三、系统调用的异常处理源码

Linux异常向量的实现代码

图片

380-383行,定义向量入口,处理当SPSel值0时触发的异常,Linux没有采用统一SP的情形,所以把这组异常都当作无效异常来处理。即便如此,内核也要实现此组异常处理函数,哪怕只是让CPU“死掉”也好,如:die("Oops - bad mode", regs, 0);

385-388行,定义向量入口,处理当SPSel值1时触发的异常,此组异常触发时没有级别的迁移,表示在内核态运行的过程中发生的异常。

390-393行,定义异常级别从aarch64迁移到此级别的异常,程序在用户态调用系统调用时触发此组异常,内核由此入口处理系统调用。

395-405行,定义异常级别从aarch32迁移到次级别的异常,如果内核在编译时定义了CONFIG_COMPAT宏,则处理此类异常,否则当无效异常处理。

 

下面我们看一下上图390行的同步异常(系统调用属于同步异常)是如何实现的:

图片

74行,定义kernel_ventry汇编宏,带三个参数,el-异常级别,lable-异常处理符号,regsize寄存器位宽。

76行,定义了ARM64_UNMAP_KERNEL_AT_EL0使能从EL1返回EL0的时候,把kernel unmap掉的功能,这样让user不可见内核。

80行,读取tpidrro_el0寄存器,取得当前线程ID

81行,把tpidrro_el0寄存器清0

89行,当前堆栈指针sp的内容减去S_FRAME_SIZE(值为sizeof(struct pt_regs)),由于Linux堆栈是向低地址方向发展的,所以此行代码的功能相当于在堆栈中拉出S_FRAME_SIZE大小的空间,用于保存当前的执行现场(保存通用寄存器的值)

90行,\el,\label的意思是引用传入宏的参数,\()的意思与C语言的#一样表示连接字符串,所以,把参数代入后此行代码等同与b el0_sync

 

el0_sync就是el0的同步异常处理程序(代码归代码,当前异常级别为EL1),此代码前文已经分析过了,在此不再赘述。

 

以上就是ARMv8的异常处理的分析,朋友们别被异常两个字吓到,其实这就是程序执行时的特权级别,为的就保护系统的资源(系统级寄存器及指令),和异常两字联系到的不正常没什么必然的联系。

 

原文:https://mp.weixin.qq.com/s/9drYMAzgozbFrb6Pxv3OvA

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值