资料来源于上海交通大学 并行与分布式系统研究所
2.4 中断与异常的概念
通用概念:
中断(Interrupt)
- 外部硬件设备所产生的信号
- 异步:产生原因和当前执行指令无关,如程序被磁盘读打断
异常(Exception)
- 软件的程序执行而产生的事件
- 包括系统调用(SystemCall)
- 用户程序请求操作系统提供服务
- 同步:产生和当前执行或试图执行的指令相关
不同体系结构术语的对应关系:
通用概念 | 产生原因 | AArch64 | X86-64 |
---|---|---|---|
中断 | 硬件异步 | 异步异常(重置,中断) | 中断(可屏蔽,不可屏蔽) |
异常 | 软件同步 | 同步异常(终止,异常指令) | 异常(Fault, trap, abort) |
之后提到的
“异常”均为通用概念意义
AArch64的中断(异步异常)
-
重置(Reset)
最高级别的异常,用以执行代码初始化CPU核心
由系统首次上电或控制软件、Watchdog等触发 -
中断(Interrupt)
CPU外部的信号触发,打断当前执行
一如计时器中断、键盘中断等
AArch64的(同步)异常
- 中止(Abort)
失败的指令获取或数据访问
如访问不可读的内存地址等 - 异常产生指令(Exceptiongeneratinginstructions)
-SVC:用户程序->操作系统
-HVC:客户系统->虚拟机管理器
-SMC:NormalWorld->SecureWorld
X86-64术语
-
中断(设备产生、异步)
可屏蔽:设备产生的信号,通过中断控制器与处理器相连,可被
暂时屏蔽(如,键盘、网络事件)
不可屏蔽:一些关键硬件的崩满(如,内存校验错误) -
异常(软件产生、同步)
错误(Fault):如缺页异常(可恢复)、段错误(不可恢复)等
陷阱(Trap):无需恢复,如断点(int3)、系统调用(int80)
中止(Abort):严重的错误,不可恢复(机器检查)
2.5 中断与异常的产生
中断控制器需要考虑的问题
- 如何指定不同中断的优先级
低优先级中断处理中,出现了高优先级的中断
嵌套中断 - 中断交给谁处理
- 如何与软件协同
AArch64中断的分类
-
IRQ(Interrupt Request)
普通中断,优先级低,处理慢 -
FIQ(Fast Interrupt Request)
- 一次只能有一个FIQ
- 快速中断,优先级高,处理快
- 常为可信任的中断源预备
以上IRQ, FIQ, 连接CPU的不同针脚, 可在中断控制器(Interrupt Controller)中配置;
- SError(SystemError)
原因难以定位、较难处理的异常,多由异步中止(Abort)导致
如从缓存行(Cacheline)写回至内存时发生的异常;
早期的ARM中断控制器
厂商指定模型
-
将中断路由给处理器的功能
由Soc厂商实现 -
FIQ直接发给处理器,IRC根据优先级进行分发
-
Freescale/NXP iMx233/iMx28系列中的
ICOLL
早期的ARM中断控制器
-
向量中断控制器 VIC(Vector Interrupt Controller)
-
ARM提出
放在AMBA(AdvancedMicrocontrollerBus Architecture)高速总线上 -
32种非向量中断(不同中断, 相同的处理入口)
-
16种向量中断(不同中断,不同的处理入口)
GIC:通用中断控制器
- 中断类型变多,将中断分发给不同的核(对称或非称)进行处理
主要功能
- 分发:管理所有中断、决定优先级、路由
- CPU接口:给每个CPU核有对应的接口;
GIC中断来源
SPI:共享外围中断
- 可以被路由到一个或多个核,找到可用的核进行处理
- Distributor可配置路由
- 如UART中断
- 中断ID:32-1019;
PPI:私有外围中断
- 指定核处理
- Distributor可配置路由
-如WatchingDog ->A5 core - 中断ID:16-31
SGI:软件产生中断
核间通信
中断ID:0-15
gic 中断信息获取,
MMIO(Memory mapping I/O)即内存映射I/O,它是PCI规范的一部分,I/O设备被放置在内存空间而不是I/O空间。从处理器的角度看,内存映射I/O后系统设备访问起来和内存一样。这样访问AGP/PCI-E显卡上的帧缓存,BIOS,PCI设备就可以使用读写内存一样的汇编指令完成,简化了程序设计的难度和接口的复杂性。
2.6 Linux 的中断处理理念
案例:Linux的中断处理理念
- 在中断处理中做尽量少的事
- 推迟非关键行为
- 结构:Top half & Bottom half
- Top half:做最少的工作后返回
- Bottom half: 推迟处理(softirg,tasklets,工作队列,内核线程)
Top half:
Bottom half: taskle, Softirq, work queue, kernel thread
TopHalf:马上做
-
最小的、公共行为:
保存寄存器、屏蔽其他中断 ;
恢复寄存器,返回原来场景; -
最重要:调用合适的由硬件驱动提供的中断处理handler
-
因为中断被屏蔽,所以不要做太多事情(时间、空间)
-
使用将请求放入队列,或者设置标志位将其他处理推退到bottom half
TopHalf:找到中断处理 handler
-
现代处理器中,多个I/O设备共享一个IRQ和中断向量
-
多个ISR(interrupt service routines)可以结合在一个向量上
-
调用每个设备对应该IRQ的ISR
BottomHalf:延迟完成
-
提供一些推迟完成任务的机制
-softirqs
-tasklets(建立在softirqs之上)
一工作队列
-内核线程 -
这些工作可以被中断
注意:中断处理没有进程上下文
为什么?
- 中断(和异常相比)和具体的某条指令无关
- 也和中断时正在跑的进程, 用户程序无关
- 中断处理handler不能睡眠!
中断处理中的一些约束
- 不能睡眠
一或者调用可能会睡眠的任务 - 不能调用schedule()调度
- 不能释放信号或调用可能睡眠的操作
- 不能和用户地址空间交换数据
2.7 中断与异常的处理
处理流程
正常执行
进入异常和中断处理
选择合适的handler
CPU发现中新或异常
处理
从handler中返回
中断与异常的处理使用同一套机制,差异仅在选择handler中体现。
中断和异常处理必做事项
进入中断或异常时
- 需保存处理器状态,方便之后恢复执行
- 需准备好在高特权级下进行执行的环境
- 需选择合适的异常处理器代码进行执行
- 需保证用户态和内核态之间的隔离
处理时:
- 需获得关于异常的信息,如系统调用参数、错误原因等
返回时:
需恢复处理器状态,返回低特权级,继续正常执行流
AArch64的中断和异常处理
查询异常向量表
(ExceptionVectorTable
选择handler
EL0下的正常执行流
进
发生中断或异常
处理
继续ELO下的正常执行
eret
- 以ELO-EL1为例,后同
发生-信息保存
- 异常或中断发生后,硬件会将错误码和部分上下文信息存储在寄存器中.
- 处理器状态(PSTATE)-> Saved Program Status Register(SPSREL1)
- 当前指令地址(PC)-> Exception Link Register(ELR_EL1)
异常发生原因-
1)Serror与异常:Exception Syndrome Register(ESR_EL1)
2)中断:GIC中的寄存器(使用MMIO读取) - 安全性问题
上述寄存器均不可在用户态(ELO)中访问
发生-进入EL1
-
硬件会适当修改处理器状态(PSTATE),进入EL1执行
-
问题:栈内存的安全性
-
进入EL1级别后,栈指针(SP)会自动换用SP_EL1
-
从而实现用户栈->内核栈
-
如需在EL1下使用SP_ELO 作为栈指针,可配置SPSel寄存器。
寻找handler的代码
使用异常向量表(ExceptionVectorTable)
- 每个异常级别存在独立的异常向量表
- 表项为异常向量(ExceptionVector),是处理异常或跳转到异常handler的小段汇编代码
- 地址位于VBAR_EL1寄存器中
- 选择表项取决于:
- 异常类型(同步、IRQ、FIQ、Serror)
- 异常发生的特权级
- 异常发生时的处理器状态(使用的栈指针/运行状态)
返回(Exception Return )
- eret指令
- ELR_EL1 ->PC,恢复PC状态.
- SPSR_EL1 -> PSTATE,恢复处理器状态
- 降至EL0,硬件自动使用SP_EL0作为栈指针
- 恢复执行
X86-64的中新和异常处理
- 进入异常
硬件会将上下文信息和错误码存储在内核栈上 - 用异常向量表导找handler
不分级
异常向量表中存handler的地址 - iret返回
- 恢复程序上下文
- 从内核态返回用户态
- 继续执行用户程序
2.8 系统调用
系统调用(Syscall:
-
指运行在用户空间的程序向操作系统内核请求需要更
高权限运行的服务 -
系统调用提供用户程序与操作系统之间的接口
Linux追踪系统调用:
-
每当有系统调用产生时,Linux可打印发生的系统调
用、系统调用的参数和系统调用的返回值。 -
ptrace()可追踪Linux中的系统调用情况
-
广泛应用在各种debugger中
-
命令行中
-
strace追踪系统调用
-itrace追踪库函数的调用
系统调用与安全
-
AArch64使用寄存器传参,个数有限
一 如ChCore的系统调用支持使用寄存器X0-X7最多8个参数 -
若系统调用需要更多参数如何处理?
一 使用结构体打包参数,并将结构体的指针作为参数
问题:内存安全性
一 作为参数的指针必须经过检测!
- 指向NULL-> kernel crash
- 指向内核内存 --> 安全漏洞
用户指针检测
- 完备的指针检测十分耗时
需要遍历用户进程的所有合法内存区域进行检测;
Linux解决方法:非全面检查
- Linux仅初步检测用户指针是否属于对应进程的用户内存区域的最大可能
边界
即使通过初步检测,用户指针仍然可能非法(如指向尚未分配的栈空间
等)
直接将非法的指针交给内核使用会导致内核出现页错误,内核态的页错
误通常以为着bug,内核会打印异常信息并中止用户进程
Linux采用了一些复杂机制来防止这一情况发生
处理用户指针问题
-
内核代码仅使用特定代码片段访问用户指针(如copy_from_user)
-
由访问用户指针而导致内核内存错误的代码段是确定的
-
当内核发生页异常(PageFault)时,内核会检查异常发生的PC.
-
若异常发生的PC属于访问用户指针的代码段,Linux尝试对其进行修复.
-
若不属于,则报告问题并终止用户程序
然而
Linux中很多地方违反了这一规定,导致了许多安全漏洞。
系统调用与性能
- 系统调用会造成大量性能开销
- 硬件优化:新的系统调用指令
- x86提出了syscall/sysenter/sysexit来代替int进行系统调用
软件优化
开放性问题