CPU操作模式、内存模式与特权级
0 前言
CPU的操作模式,一言难尽,可以理解为CPU的身份,而特权级就表示CPU的权利,比如,皇帝和臣子,不同的身份具有不同的权限,皇帝可以给臣子尚方宝剑,来提升权力。
CPU的内存模式,可以理解为访问资源的方式,不同的路径也许都能访问相同的资源,但是不同路径的安保程度不同。
CPU的特权级表示CPU对资源控制的权限级别,运行在不同特权级下的CPU,对物理资源具有不同的访问权限。
大部分CPU都具有操作模式和特权级,但只有高级CPU才配备的内存模式,如单片机,大部分型号是没有内存模式的,高级点的单片机内存保护单元(MPU),arm的cotex A系列,x86系列,都具有内存模式,由内存管理单元实现(MMU)。
例如ARM的cotex-m系列的cpu:
- 特权级下的cpu可以访问所有物理资源,即所有寄存器和外设,而非特权级下的cpu,有一些关键的cpu寄存器是无法访问的,例如CONTROL寄存器,特权级下的CPU可以通过修改CONTROL寄存器,进入非特权级,一旦进入非特权级,则无法再次修改CONTROL寄存器回到特权级。
- cpu操作模式有两种:线程模式(thread mode)和处理者模式(handler mode)。处理器模式固定被视为特权级,不受CONTROL寄存器控制,中断(含异常)是进入处理器模式的唯一方法。
- 系统代码(通常被视作经过大量测试、稳定的代码)运行在特权级,而应用代码(通常被视作不稳定、不安全的代码)运行在应用层,这在一定程度上实现了程序隔离。非特权级CPU想要回到特权级,可以触发中断,在中断程序中修改CONTROL寄存器,等返回中断,程序自然就回到特权级。
同样对于x86架构(含推论),也存在操作模式和特权级,只是复杂些,另外还存在内存寻址模式。
1 内存寻址
1.1 内存管理单元(MMU,memory manage unit)
1.1.1 基本组成
x86的MMU由分段单元和分页单元组成:
- 分段单元:将程序访问的地址(称为逻辑地址)做段映射,得到的地址,叫线性地址,在保护模式下,所有代码访问的地址均是逻辑地址,这其中保护程序计数器(pc)取指的地址。分段单元负责将逻辑地址转换成线性地址
- 分页单元:将线性地址进一步做分页映射,得到最终的物理地址
分段单元中包含6个段寄存器,其中3个专用寄存器,3个通用寄存器:
- cs 代码段寄存器,用于代码寻址,关联指令,call、jmp等,以及程序计数器。
- ss 栈段寄存器,用于堆栈寻址,关联指令push、pop等。
- ds 数据段寄存器,用于数据寻址,关联指令,mov等。
- es,fs,gs 通用段寄存器。
1.1.2 硬件分段
分段单元的用法跟3种内存寻址模式有关,详见后面内存寻址模式一节。
平坦模式
分段单元关闭,确切的说,分段单元将逻辑地址直接等于线性地址。
实地址模式
线性地址由逻辑地址+上端寄存器左移4位得到。这个模式是为兼容8086老程序而存在,具体参考后面实地址模式说明。
分段模式
x86的段寄存器是个16位的寄存器,高13位表选择符,中间1位表描述符表指示器(TI,table indicator),最后2位表示请求者特权级(RPL,requestor privilege level),请求者特权级在后面特权级一节讲到。
bit 15~3 | bit 2 | bit 1~0 |
---|---|---|
index | TI | RPL |
- index指示,该段的段描述符在描述符表中的索引位置,段描述符是一个8字节的数据结构,用于描述段的一些基本信息,如段的访问权限,段基地址和大小,因此称为段描述符;
- TI指示,该段描述符是在全局描述符表(GDT,global descriptor table),还是局部描述符表(LDT,local descriptor table),与之关联的两个物理寄存器就是gdtr和ldtr,分别存放着全局描述符表和局部描述符表的线性地址(注意是线性地址)。
- RPL则是之前说过的请求者特权级。
硬件层面
- 先根据TI决定描述符在全局描述符表还是局部描述符表,从gdtr/ldtr取得描述符表的线性基地址
- 然后根据index索引偏移指定地址数,得到段描述符的线性地址
- 最后线性地址经过分页单元得到物理地址,取得段描述符的实际内容,从内容中取得段的线性基地址
ps:注意别搞混淆了,段 是 段,段描述符 是 段描述符,段描述符是描述段用的。
1.1.3 x86的硬件分页
分页模式是通过cr0寄存器的PG标志来开启,PG=0时,分页模式被关闭了,线性地址被直接解释成物理地址。
二级分页
当分页开启的时候,硬件分页默认采用二级分页,也就是当分页单元接受到一个32位线性地址时,它被按如下规则进行解释
10bit 31~22 | 10bit 21~12 | 12bit 11~0 |
---|---|---|
页目录索引(pdi) | 页表索引(pti) | 页内偏移(pi) |
由页内偏移字段的位数知,一页的大小为4kB,页表和页目录本身大小也是一个页,页表和页目录中存放着一个个等大小的条目,页表的称为页表项,页目录的称为页目录项,或者简单理解为表项即可,不管几级页表,表项等大小,默认情况下表项为32位,当开启物理地址扩展(PAE,physical address extension)时,表项扩展位64位。
表项中字段(关键字段已用红色标出)
字段 | 占用位长 | 作用 |
---|---|---|
present | 1 bit | 标志页是否在主存,如果不在主存,访问时,将产生缺页异常,线性地址被暂存到cr2寄存器 |
field | 20bit | 物理地址的高20bit |
accessed | 1 bit | 标志位,每当分页单元访问该页时,则自动设置它(分页单元从不重置它,由操作系统重置),当页被交换到磁盘时,可由操作系统使用,如Linux用于判断是否是缺页异常,还是无权限 |
dirty | 1bit | 标志位,每当写入页时,分页单元自动设置该位(分页单元从不重置它,由操作系统重置) |
read/write | 1bit | 读写权限 |
user/supervisor | 2bit | 访问页需要的特权级 |
PCD(page cache disable) | 1bit | 设置该页是否启用硬件高速缓存,Linux永远开启 |
PWT(page write through) | 1bit | 设置该页是否采用通写策略(硬件缓存加速的一种策略),Linux永远开启 |
page size | 1bit | 页大小标志位,仅用于页目录项,设置是否启用扩展分页,扩展分页下,页表索引10bit和页内偏移12bit合并,形成一个22bit的页内偏移,因此页大小由4kB变成4MB,同时field字段的20bit只有高10bit具有意义 |
global | 1bit | 用来防止常用页被从TLB高速缓存中刷新出去 |
二级目录硬件分页寻址
ps1:分页单元这,输出的地址都是是物理地址
ps2:硬件cr3寄存器中存放着页目录的线性基地址
1.1.4 x86的物理地址扩展(PAE)
- PAE通过设置 cr4的PAE标志位激活
- 物理上,intel将地址总线从32位扩展为36位
- 页表项中的field字段20位已不够用,因此页表项被扩展成64位,其中field字段扩展成24位,页表大小固定4kB(PAE下开启扩展分页时为2MB),因此页表项数为512(之前为1024)。
- 增加一级寻址,称为页目录指针表,线性地址按如下解释
2bit | 9bit | 9bit | 12bit |
---|---|---|---|
页目录指针表索引(pdpti) | 页目录索引(pdi) | 页表索引(pti) | 页内偏移(pi) |
ps1:硬件cr3寄存器中存放着页目录指针表的线性基地址
ps2:由于逻辑地址仍然是32位,而用户进程不能修改cr3寄存器,因此用户进程只能访问4GB空间,而内核程序可以通过修改cr3,访问64GB的内存空间。
1.1.5 x64的硬件分页
x64是64位地址总线,因此不需要PAE功能,不同架构的硬件分页可能不同,x64的线性地址解释如下
reserved | 9bit | 9bit | 9bit | 9bit | 12bit |
---|---|---|---|---|---|
页全局目录索引(pgdi) | 页上级目录索引(pudi) | 页中间目录索引(pmdi) | 页表索引(pti) | 页索引(pi) |
ps1:寻址逻辑图可以参考二级页表的寻址图,只须进行简单扩展即可
ps2:硬件cr3寄存器中存放着页全局目录的线性基地址
1.1.6 x86的硬件高速缓存
高速缓存的结构如下
高速缓存支持两种更新策略:
- 通写(write through),即写缓存,又写内存。
- 回写(write back),仅写缓存。
高速缓存通过 cr0 寄存器的 CD 标志开启,同时也可通过页表项的PCD字段进行二次控制,CD 相当于全局开关,PCD相当于以页为粒度的二级开关。
1.2 内存寻址模式
x86有三个内存寻址模式,即平坦模式、分段模式和实地址模式。
1.2.1 平坦模式(flat memory mode)
分段单元被关闭,程序访问的地址被直接解释成线性地址,如果分页单元被禁用(CR0 PG标志位为0),则线性地址又直接被解释成物理地址。
1.2.2 分段模式(segmented memory mode)
分段单元被开启,程序访问的地址被解释成逻辑地址,经过分段单元得到线性地址。
1.2.3 实地址模式(real-address memory mode)
分段单元被开启,但使用方式与分段模式不同,段寄存器的内容被当作段偏移,线性地址空间大小只有1M,16的段寄存器值左移4位,再加上程序访问的地址的低16位,得到线性地址,最后线性地址被直接解释成物理地址。程序访问地址只有16位,因此,每个段的大小也只有64KB。这个地址模式存在的原因,是为了兼容执行历史上为8086机器编写的程序,8086是16位机,或者说准16位机,它有20位地址线,16位数据总线,cpu寄存器16位宽,代码访问的地址也是16位,这就是为什么上面程序访问的地址只取16位,最后生成的物理地址有20位。
1.3 分段和分页开关
要使用分段和分页,首先要进入保护模式(详见后面操作模式切换),保护模式下会启动段/页 保护机制(详见后面特权级)。
分段开关:进入保护模式自动开启,将段描述符中基地址设置为0,相当于关闭了分段,但段保护功能正常。
分页开关:设置 CR0.PG 位为1开启,CR0.WP 位为写保护是能位,页表项(页描述符)中的 read/write 字段可以控制单个页的写保护开关。
2 特权级(内核态与用户态)
当保护模式开启时(CR0.PE=1),内存在寻址访问会别保护,这其中就包括特权级校验(保护模式还有读写权限校验)
2.1 4种特权级
保护模式下有4种特权级,每个段寄存器最低2位表示,请求者特权级(RPL,requester privilege level),2位刚好对应4种取值0~3,表示4种特权级,0最高,3最低,其中代码段寄存器cs的RPL比较特殊,它代表当前CPU的特权级(CPL,current privilege level)。多数操作系统,如Linux,只使用0和3两个特权级,表内核态和用户态。
bit 15~3 | bit 2 | bit 1~0 |
---|---|---|
index | TI | RPL |
2.2 特权级校验
段描述符中还有一个字段,描述符特权级(DPL,descriptor privilege level),表是访问该段需要的最低特权级,如果是取指,则仅需要cs的RPL≤DPL,如果是访问数据,则同时需要cs的RPL和数据段寄存器的RPL均小于等于DPL。
ps:中断是个例外,因为中断的目的就是用来提权,因此,中断发生时,在访问中断处理程序入口地址时,需要CPL>=DPL,详见我另一篇文章《x86架构学习之中断》。
2.3 特权级穿越
想要从低特权级跨越到高特权,需要使用x86提供的 门 机制,你可以理解为,x86有4个区域(4个特权级),每两个区域之间有一扇门,用于在不同区域之间穿越。这些门有4种:
- 调用门,远跳转指令使用,参考x86架构学习之调用指令call
- 任务门,由硬件任务切换使用(非操作系统任务,x86有自己的任务架构,操作系统可以基于它实现多任务系统,也可不用,如Linux就没有使用硬件任务,因为使用硬件任务系统,会导致可移植性变差,x86就不兼容8086的硬件任务)。
- 陷阱门,由中断或异常使用,具体看操作系统的配置,如Linux用它处理异常。
- 中断门,由中断或异常使用,具体看操作系统的配置,如Linux用它处理中断。
3 操作模式(处理器模式)
3.1 操作模式种类
CPU操作模式决定了CPU的哪些指令集和架构特性可以使用。
当编写 IA-32 或 Intel 64位处理器的代码时,程序员需要知道处理器所处的操作模式,操作模式与内存寻址模式的关系如下:
- 保护模式,当在保护模式时,处理器能使用三种内存寻址模式中的任意一种(实地址模式一般只在virtual-8086模式下使用),保护模式下,能够直接执行 实地址模式的 8086 代码,这种场景被称为virtual-8086模式,这并不是一个操作模式,而是保护模式的一个特性。保护模式下会对分段和分页进行保护:
- 分段保护:特权级校验,访问范围校验(limit checking),描述符类型校验(type checking)。
- 分页保护:特权级校验,访问范围校验,读写权限校验。
- 实(地址)模式,处理器仅使用实地址模式的内存寻址模式,用于兼容老8086程序,处理器刚上电或复位时,是处于实模式。
- 系统管理模式(SMM),此模式下,处理器会转换到一个隔离的地址空间,称为系统管理RAM(SMRAM),SMM用来处理硬件平台相关的系统级的事务,比如电源管理,不用于应用或一般系统软件,它好处就是,在早期就与操作系统和应用程序隔离开,对于应用或操作系统来说,是不可见的。这个模式下,内存寻址模式与实地址模式相似。
- IA-32e模式(长模式,仅64位机),这不是真正的操作模式,是64位模式和兼容模式的统称,为了跟32位机区分开,或称为32位机的扩展模式。
- 兼容模式,此模式支持大多数老32位、16位程序直接在64位机上运行,但是老的运行在virtual-8086模式的,和使用了硬件任务管理的应用程序,无法在该模式下运行。兼容模式由代码段寄存器的L位控制开关,因此64位的操作系统,可以让64位程序运行在64位模式,让老的32位或16位程序运行在兼容模式。兼容模式和32位的保护模式相似,只能访问4GB线性地址空间,只能使用16位宽和32位宽的指令,同时继承了32位保护模式的物理地址扩展(PAE,physical address extension)(这在64位模式是不需要的)
- 64位模式,此模式下,分段部分关闭(部分段寄存器分段功能无效,权限校验还在),CS, DS, ES, SS段寄存器指定的段基地址被视为0,即内存寻址模式为平坦模式,但是FS和GS除外,他们的分段功能仍然生效。64位模式下,分段模式和实地址模式在该操作模式下是不可用的。同样64位模式由基于代码段的操作系统开启。此模式下,程序能够访问:
• 64位平坦空间。
• 8个额外的通用寄存器,即R8~R15。
• 8个单指令多数据流水线(SIMD,single-instruction multiple-data)扩展 (Intel SSE, SSE2, and SSE3, and SSSE3)用到的寄存器,SIMD是为加速MMX(multimedia extension)指令族而引入的硬件电路。
• 允许按64位宽访问cpu寄存器(例如 mov rax [mem] 必须是64位模式,前缀r表64位)。
3.2 操作模式切换
- 实(地址)模式,上电复位就在这个模式,这是默认模式。
- 保护模式,通过设置寄存器 CR0.PE 使能位,来开启保护模式, CR0.PE=0 则回到实模式。寄存器 EFLAGS.VM 位决定当前是否在 virtual-8086 模式。
- 系统管理模式,任何时候,CPU收到SMI中断信号,就会进入该模式。
- IA-32e模式,保护模式下,通过开启分页,并设置寄存器 IA32_EFER.LME 位进入 IA-32e 模式, IA32_EFER.LMA 也标志着当前是否在 IA-32e 模式。只有进入 IA-32e 模式,才能进入下面子模式。
- 兼容模式,寄存器 CS.L = 0。
- 64位模式,寄存器 CS.L = 1。
intel 64架构的CPU支持所有IA-32架构CPU的操作模式。