声明:此文章为本人在知乎首发的原创文章
目录
与本文同时编写的内核项目现已开源至github
一、x86_64的处理器模式
在这里需要注意的是,实际上处理器处理位数和处理器模式没有关系,只不过在某个处理位数时,处理器被要求处于某个模式下,允许存在某个处理位数
的其他模式
这种中间状态。
处理器在刚刚通电或执行重启操作后,会向自己发送一个Reset
信号,收到Reset
后,处理器被设置为实地址
模式(简称实模式
),处理位数被设为16位。在这个模式中,处理器只能访问物理地址的前1MB空间,通过段寄存器左移4位后与一个16位地址相加得到的20位地址访问。
1. 32位模式
开启A20地址线即可开启32位模式,A20地址线通过读、写0xee
号io端口来开启、关闭;处理器还提供了A20快速门
,即向0x92
号io端口写入0x2
来开启A20地址线。不开启保护模式的32位模式是不稳定的,处理器只能暂时处于32位实模式
的状态,需要开启保护模式才能正常执行32位程序。
2. 保护模式
准备保护模式数据结构后将cr0
寄存器的PE
标志位置位即可开启。这个“保护模式数据结构”指的是GDT
,全局描述符表,表中可以添加段描述符
、系统描述符
。保护模式下的段寄存器通过访问段描述符
确定所在的段,而不是左移4位后直接与地址相加;保护模式有许多硬件直接触发的保护功能,系统描述符用于支持这些功能的触发。此外,保护模式中引入了特权级
,0
为最高的特权级,3
为最低的特权级,越高的特权级,保护功能越弱,程序越容易控制处理器的状态。内核通常在0
特权级运行,普通程序通常在3特权级运行。同时,在这个模式中也引入了分页机制(不是必须开启)。32位的基本分页机制使用一级页表进行寻址,能够映射4GB空间,开启PAE
后,在原先的以及页表上增加了两级页表,通过类似树状的数据结构使得相同大小的最高级页表能够映射64PB
的空间。PAE
开启后使用36位物理地址,使得物理地址寻址能力提高到了64GB
。
3. 虚拟8086模式
这是一个模拟出来的16位实模式,置位eflags寄存器的VM标志位即可开启。在这个模式中,16位程序在一个切换到此模式前设置好的一个1MB
大小的内存空间中运行。
3. IA-32e模式
由实模式
切换而来的32位保护模式
被称为IA-32
模式。由于64位模式是在IA-32
模式基础上经过功能增强和修改得到的,所以被称为IA-32e
模式。经过第二章中的一系列操作,即可开启。这些操作主要开启了这些功能:分页机制
、长模式地址扩展(LME)
、64位段描述符
。长模式地址扩展
中为分页机制又增加了一级或两级页表,使用4级或5级页表映射地址空间。同时使用48位物理地址,进一步提高物理地址寻址能力。也因为48位物理地址
称为长地址,IA-32e
模式又称长模式
。由于描述符表中支持32位段描述符
和64位段描述符
同时存在,所以可以通过暂时关闭LME
并修改段寄存器索引的段描述符,进入一种支持32位程序运行的IA-32e
的子模式,称为兼容模式
。虽然所谓的兼容模式
只是IA-32e
模式的一种支持32位程序运行的状态,但我们依然习惯地称IA-32e的支持64位程序运行的状态
和IA-32e的支持32位程序运行的状态
为IA-32e
的两个子模式长模式
和兼容模式
。
二、各种不同的地址空间
1. 物理地址空间
物理地址空间
中的物理地址直接索引内存中每个储存单元的地址。
2. 线性地址空间
分页机制的寻址方式
在4级页表寻址方式下,线性地址通过如图方式被映射成物理地址。所以由分页机制映射的地址空间就称为线性地址
。
注意,线性地址空间与物理地址空间并不一定一一对应(可以人为将一部分空间设置为一一对应的空间),且只有部分线性地址对应物理地址,因为线性地址空间能够映射的地址空间大小远大于人类目前制造出的内存大小。
3. 虚拟地址空间
分段机制的寻址方式
三、平坦内存模型
这是在ia-32e
模式中固定使用的分段模型。
平坦内存模型
规定,操作系统只使用一个段,这个段起始地址为线性地址0
,无段限长(或者说段限长就是线性地址空间可以映射的地址的上限);但是对于代码段寄存器
和其它段寄存器依然使用两个段描述符,两个段描述符的主要区别是代码段只有执行
和读
权限,其它段寄存器作为数据段,段描述符只有读写
权限。
分页机制出现后,分段机制逐渐显得复杂且混乱,并且分段机制在硬件电路上还有比较大的功耗,但是在ia-32模式中处理器仍然完全支持这种内存管理机制。英特尔和amd也意识到了这个问题,于是在新的ia-32e模式中,采取了平坦内存模型
这个方案,使得分段机制的硬件电路大幅缩小,并且内核开发者也能够在恰当设置分段机制后直接忽略它的存在,只考虑分页机制即可。
因此在ia-32e模式中,虚拟地址等价于线性地址,一般称为线性地址。
四、cannonical型地址
cannonical型地址
是线性地址的格式规范,IA-32e
模式中规定应使用这种地址。
由于线性地址在4级页表和5级页表分页中均没有达到64位,因此剩余的位有严格规定。
英特尔白皮书的原文这样描述cannonical型地址:
在64位模式中,一个从最高有效位到第63位均被设置为0或1的地址被认为是cannonical型的。
对于4级分页的线性地址,一个符合此描述的地址应该处于以下范围:0x0000_0000_0000_0000
~0x0000_7fff_ffff_ffff
和0xffff_8000_0000_0000
~0xffff_ffff_ffff_ffff
,根据前文,一个4级分页的线性地址最高有效就是47位,因此47到63位应该总是1或0。
这些位为0的地址空间称为低地址,为1的地址空间称为高地址。
五、分页机制
这里我们只讨论4级分页。
简单来说,cr3
寄存器储存着PML4
(即第4级页表)的首地址;但是实际上其中有些标志位,但是都被设为0:
英特尔白皮书卷3,4.5,表4-12
其中地址部分使用4KB对齐,这样可以直接将低12位的标志位清零作为PML4的地址而无需使用位移运算。
PML4以及下一级、下两级等等的页表都有512项,每项8字节,共占用4KB,刚好是一个页的大小。在线性地址中需要9个bit来索引一个页表项。
英特尔白皮书卷3,4.5,图4-11
以下是上图各标志位的含义:
R/W
:置为1时读写权限为读写,为0时读写权限为只读。U/S
:置为1时在用户态或内核态访问,为0时只能在内核态访问。A
:“访问过”,只有在CPU运行时,使用这个页表项寻址后这个位会置为1。D
:“脏页”,在此页写入过之后会置为1。G
:“全局页”,此页被置为1时,tlb不会清除此页的映射缓存。Ignored
/Ign.
:忽略,意为这些位无论是什么都不会造成影响。Reserved
/Rsvd.
:保留,意为这些位只能为0。
tlb: 会暂时存储线性地址的映射,用来加快分页地址转换的速度的硬件电路。写cr3寄存器会清除tlb中索引它的页表项中
G
标志位为0的所有地址。
页表的每一个表项都是上图所示的表项的一种,每个表项都类似指针,指向下一级页表或物理页,形成树状结构。
根据cannonical型地址
的定义,对于PML4来说,前256项映射的内存空间对应着低地址
,后256项映射的内存空间对应着高地址
。
我们可以用cannonical型地址
的高地址和地址来划分内核态和用户态的内存空间;linux
内核使用高地址作为内核空间,低地址作为用户空间;本人写的内核用低地址作为内核空间。
地址空间的使用没有任何硬性规定,我的内核使用低地址作为内核空间只是因为链接内核镜像以及切换处理器模式比较方便。
六、内核态与用户态
内核态
即处理器正在执行内核代码的状态,用户态
即处理器正在执行用户进程的代码的状态。
与本文同时编写的内核项目现已开源至github