注意:从EL0
到EL1
的三种方法就是异常中断系统调用
中断(Interrupt)
-
外部硬件设备所产生的信号
-
异步:产生原因和当前执行指令无关,如程序被磁盘读打断
异常(Exception)
-
软件的程序执行而产生的事件
-
包括系统调用(System Call
- 用户程序请求操作系统提供服务
-
同步:产生和当前执行或试图执行的指令相关
(中断是被外来的打断的,异常时内部自己发生的,但是arm里所有都叫异常,中断叫异步异常,异常叫同步异常)
通用概念 | 产生 原因 | AArch64 | x86-64 |
---|---|---|---|
中断 | 硬件 异步 | 异步异常 (重置/中断) | 中断 (可屏蔽/不可屏蔽) |
异常 | 软件 同步 | 同步异常 (终止/异常指令) | 异常 (Fault/Trap/Abort) |
异步异常
-
重置(Reset)
- 最高级别的异常,用以执行代码初始化CPU核心
- 由系统首次上电或控制软件、Watchdog等触发
-
中断(Interrupt)
- CPU外部的信号触发,打断当前执行
- 如计时器中断、键盘中断等
同步异常
- 中止(Abort)
- 失败的指令获取或数据访问
- 如访问不可读的内存地址等
- 异常产生指令(Exception generating instructions)
- SVC:用户程序 -> 操作系统
- HVC:客户系统 -> 虚拟机管理器
- SMC:Normal World -> Secure World
- 中断(设备产生、异步)
- 可屏蔽:设备产生的信号,通过中断控制器与处理器相连,可被 暂时屏蔽(如,键盘、网络事件)
- 不可屏蔽:一些关键硬件的崩溃(如,内存校验错误)
- 异常(软件产生、同步)
- 错误(Fault): 如缺页异常(可恢复)、段错误(不可恢复)等
- 陷阱(Trap): 无需恢复,如断点(int 3)、系统调用(int 80)
- 中止(Abort): 严重的错误,不可恢复(机器检查)
- 中断处理没有进程上下文
- 中断(和异常相比)和具体的某条指令无关
- 也和中断时正在跑的进程、用户程序无关
- 中断处理handler不能睡眠
约束:
- 不能睡眠,也不能调用可能会睡眠的任务
- 不能调用schedule()调
- 不能释放信号或调用可能睡眠的操作
- 不能和用户地址空间交换数据
中断与异常的处理使用同一套机制,差异仅在选择handler中提现
- 进入中断或异常时
- 需保存处理器状态,方便之后恢复执行
- 需准备好在高特权级下进行执行的环境
- 需选择合适的异常处理器代码进行执行
- 需保证用户态和内核态之间的隔离
- 处理时
- 需获得关于异常的信息,如系统调用参数、错误原因等
- 返回时
- 需恢复处理器状态,返回低特权级,继续正常执行流
-
发生 – 信息保存
- 异常或中断发生后,硬件会将错误码和部分上下文信息存储在寄存器中
- 处理器状态(PSTATE)-> Saved Program Status Register (SPSR_EL1
- 当前指令地址(PC)-> Exception Link Register(ELR_EL1)
- 异常发生原因 ->
- Serror与异常:Exception Syndrome Register(ESR_EL1)
- 中断:GIC中的寄存器(使用MMIO读取)
- 安全性问题
- 上述寄存器均不可在用户态(EL0)中访问
- 异常或中断发生后,硬件会将错误码和部分上下文信息存储在寄存器中
-
发生 – 进入EL1
- 硬件会适当修改处理器状态(PSTATE),进入EL1执行
- 问题:栈内存的安全性
- 进入EL1级别后,栈指针(SP)会自动换用SP_EL1
- 从而实现用户栈->内核栈
- 如需在EL1下使用SP_EL0作为栈指针,可配置SPSel寄存器
-
寻找handler的代码
使用异常向量表
-
返回(Exception Return)
- ELR_EL1 -> PC,恢复PC状态
- SPSR_EL1 -> PSTATE,恢复处理器状态
- 降至EL0,硬件自动使用SP_EL0作为栈指针
- 恢复执行
- 进入异常
- 硬件会将上下文信息和错误码存储在内核栈上
- 用异常向量表寻找handler
- 不分级
- 异常向量表中存handler的地址
- iret返回
- 恢复程序上下文
- 从内核态返回用户态
- 继续执行用户程
与aarch64
区别
x86-64
信息都存栈上,而aarch64
都存在寄存器里x86-64
不分级
- AArch64使用寄存器传参,个数有限
- 如ChCore的系统调用支持使用寄存器X0-X7最多8个参数
- 若系统调用需要更多参数如何处理?
- 使用结构体打包参数,并将结构体的指针作为参数
- 问题:内存安全性
- 作为参数的指针必须经过检测!
- 指向NULL -> kernel crash
- 指向内核内存 -> 安全漏
- 完备的指针检测十分耗时
- 需要遍历用户进程的所有合法内存区域进行检测
- 合法区域由链表(vma)管理
- Linux解决方法:非全面检查
- Linux仅初步检测用户指针是否属于对应进程的用户内存区域的最大可能 边界
- 即使通过初步检测,用户指针仍然可能非法(如指向尚未分配的栈空间 等)
- 直接将非法的指针交给内核使用会导致内核出现页错误,内核态的页错误通常意味着bug,内核会打印异常信息并中止用户进程
- Linux采用了一些复杂机制来防止这一情况发生
- 内核代码仅使用特定代码片段访问用户指针(如copy_from_user)
- 由访问用户指针而导致内核内存错误的代码段是确定的
- 或者可以该页表,设为read-only
- 当内核发生页异常(Page Fault)时,内核会检查异常发生的PC
- 若异常发生的PC属于访问用户指针的代码段,Linux尝试对其进行修复
- 若不属于,则报告问题并终止用户程序