Lec 03 系统指令集架构
(参考来源:上海交通大学并行与分布式系统研究所+操作系统课程ppt)
Creative Commons Attribution 4.0 License
Contents
3.1 回顾:特权级的必要性
- 一台计算机上同时运行多个应用程序,如何保证不同应用间的隔离?
- 如果所有的应用均能完全控制硬件计算资源,则会导致混乱
例如:某个应用希望关机,某个应用希望格式化硬盘 - 因此必须先让应用降权,不允许直接改变全局的系统状态
例如:中断是否打开
- 方案:必须要有不同的权限级——至少两种权限
- 低权限:不允许改变全局系统状态,用来运行应用
- 高权限:集中运行能改变全局系统状态的操作,形成了操作系统
- 特权操作:操作设备(读取文件、发送网络包…)、调整CPU频率、提供进程间通信…
3.2 ARM v8.4特权级别(Exception Level)
3.2.1 系统状态寄存器:PSTATE
- 抽象进程状态信息(PSTATE)
- 条件码 (Condition flags)
e.g. NZCV - 执行状态 (Execution state controls)
e.g. CurrentEL:CPU当前特权级别 - 异常掩码 (Exception mask bits)
e.g. DAIF - 访问控制(Access control bits)
例如PAN(Privileged Access Never)
3.2.2 用户ISA和系统ISA
- 用户ISA
- 通用寄存器
- (用户)栈寄存器
- 条件码寄存器
- 运算指令等
- 系统ISA
- 系统寄存器
- 系统指令
3.2.3 用户态(EL0)与内核态(EL1)
- 用户态(User-mode)
1.只能使用用户 ISA - 内核态(Kernel-mode)
2.可以同时使用系统 ISA 和用户ISA - 操作系统往往同时包含内核态与用户态的代码
3.如:Unix包含内核态的kernel 与 用户态的 shell - aarch64中常见寄存器在不同特权级中的可见情况:
3.3 特权级切换(EL0与EL1)
3.3.1 用户态与内核态之间的控制流跳转
- 初始时CPU处于用户态(EL0)执行应用程序,如何改变CPU控制流从用户态进入内核态?
- 已知的两种改变控制流的方式:
(1) 跳转指令,如 b
(2) 过程调用与返回指令,如 bl 和 ret - 这两种方式只能在同一种模式之间跳转
- 需要新的指令(在控制流跳转的同时进行特权级切换):
例如:svc
或eret
3.3.2 进行特权级切换的必要性
- 操作系统的职责之一:
- 服务应用、管理应用
- 特权级切换的必要性:
- 将CPU控制权移交给内核
- 服务:应用程序向操作系统请求服务
- 管理:操作系统能够切换不同应用程序执行
否则,错误/恶意程序死循环怎么办(操作系统终止恶意程序(故意/无意))
3.3.3 异常处理特权级切换
- 同步异常: 执行当前指令触发异常
- 第一类:用户程序主动发起:svc指令(OS利用eret指令返回)
- 第二类:非主动,例如用户程序意外访问空指针:普通ldr指令(OS“杀死”出错程序)
- 异步异常: CPU收到中断信号
- 从外设发来的中断,例如屏幕点击、鼠标、收到网络包
- CPU时钟中断,例如定时器超时
OS处理完异常后一定返回到被打断执行的用户程序吗?
不一定。原因如下。
3.3.4 异常处理函数
- 异常处理函数属于操作系统的一部分
- 运行在内核态的代码
- 异常处理函数完成异常处理后,将通过下述操作之一转移控制权:
- 回到发生异常时正在执行的指令
- 回到发生异常时的下一条指令
- 切换到其它进程执行
3.3.5 CPU寻找异常处理函数:异常向量表
- 操作系统内核预先在一张表中准备好不同类型异常的处理函数
- 基地址存储在VBAR_EL1寄存器中
- 系统寄存器
- CPU在异常发生时自动跳转到相应处理函数
- 同步异常:主动下陷svc、指令执行出错
- 异步异常:中断(IRQ、FIQ)、SError
3.3.6 CPU执行逻辑
- CPU的执行逻辑很简单
- 以PC的值为地址从内存中获取一条指令并执行
- PC+=4,goto 1(简化,表示跳转/函数调用)
- 执行过程中可能发生两种情况
- 指令执行出现异常,比如svc、缺页(同步异常)
- 外部设备触发中断(异步异常)
- 这两种情况在ARM平台均称为「异常」
- 均会导致CPU陷入内核态,并根据异常向量表找到对应的处理函数执行
- 处理函数执行完后,执行流需要恢复到之前被打断的地方继续运行
3.3.7 操作系统关于异常处理的任务
一、实现对异常向量表的设置
- 该设置是系统初始化的重要工作之一:在开启中断和启动第一个应用之前
msr vbar_el1, x0
(是内核态才能使用的指令,内核才能访问的寄存器)
二、实现对不同异常(中断)的处理函数
- 处理应用程序出错的情况:如访问空指针
Q:内核如果自己运行出错怎么办?
A:同样内核异常处理,但无需下陷(已经处于内核态)。有些平台在三次递归后会抛出triple-fault
- 一类特殊的同步异常:系统调用,由应用主动触发
Q:内核如何识别出是系统调用(而不是其他异常)?
A:执行mrs x1, esr_el1
,内核通过 ESR_EL1 寄存器读取陷入内核的原因 - 处理来自外部设备的中断:如收取网络包、获取键盘输入等
3.4 用户态和内核态的切换
3.4.1 处理器状态变化
3.4.2 处理器的任务
- 将发生异常事件的指令地址保存在ELR_EL1中
- 将异常事件的原因保存在ESR_EL1
例如,是执行svc指令导致的,还是访存缺页导致的 - 将处理器的当前状态(即PSTATE)保存在SPSR_EL1
- 栈寄存器不再使用SP_EL0(用户态栈寄存器),开始使用SP_EL1
内核态栈寄存器,需要由操作系统提前设置 - 修改PSTATE寄存器中的特权级标志位,设置为内核态
- 找到异常处理函数的入口地址,并将该地址写入PC,开始运行操作系统
根据VBAR_EL1寄存器保存的异常向量表基地址,以及发生异常事件的类型确定
? 为什么操作系统不能直接使用应用程序在用户态的栈呢?
- 安全问题。当操作系统的栈和用户态程序栈混合时,会使得操作系统的栈被用户态程序访问,导致内核级操作可以被应用操作。
? 处理器的这些操作都是必要的么?
- PC寄存器的值必须由处理器保存
否则当操作系统开始执行时,PC将被覆盖 - 栈的切换也必须由硬件完成
否则操作系统有可能使用用户态的栈,导致安全问题
3.4.3 eret:从内核态返回到用户态
- 将SPSR_EL1中的处理器状态写入PSTATE中
处理器状态也从 EL1 切换到 EL0 - 栈寄存器不再使用SP_EL1,开始使用SP_EL0
注意:SP_EL1的值并没有改变
下一次下陷时,操作系统依然会使用这个内核栈 - 将ELR_EL1中的地址写入PC,并执行应用程序代码
3.4.4 操作系统在切换过程中的任务
- 主要任务:将属于应用程序的 CPU 状态保存到内存中
用于之后恢复应用程序继续运行 - 应用程序需要保存的运行状态称为处理器上下文
(1) 处理器上下文(Processor Context):应用程序在完成切换后恢复执行所需的最小处理器状态集合
(2) 处理器上下文中的寄存器具体包括:
1.通用寄存器 X0-X30
2.特殊寄存器,主要包括PC、SP和PSTATE
3.系统寄存器,包括页表基地址寄存器等