1.实模式
实模式是x86架构CPU在启动后的初始状态,也是16位处理器如8086或8088的运行模式。在实模式下,CPU可以直接访问物理内存的第一个1MB区域。
实模式的寻址方式是通过段地址和偏移地址两部分组合来实现的。一个完整的内存地址(通常为20位)在实模式下被分为两个部分:一个16位的段地址(Segment)和一个16位的偏移地址(Offset)。
要获取实际的物理地址(Physical Address),CPU会将段地址左移4位(相当于乘以16,因为在二进制下,每左移一位就是乘以2),然后将结果与偏移地址相加。
假设我们有一个段地址为0x1000,偏移地址为0x0050,那么实际的物理地址将被计算为:
Physical Address = (Segment << 4) + Offset
Physical Address = (0x1000 << 4) + 0x0050
Physical Address = 0x10000 + 0x0050
Physical Address = 0x10050
这就是实模式的寻址方式,它使得CPU可以访问到1MB的物理内存。注意,尽管实模式的寻址方式允许访问1MB内存,但实际上,由于IBM PC的历史原因,顶部的384KB被用于特殊目的,因此实模式程序通常只能使用640KB的常规内存。
2.保护模式
保护模式是x86架构中一种更高级别的运行模式,它提供了更强大的内存保护和特权级管理机制。在保护模式下,内存寻址和访问的过程相对复杂一些,但同时也提供了更高的安全性和灵活性。
在保护模式下,内存寻址的过程如下:
-
设置段选择器:首先,你需要从某个段寄存器(如CS、DS、ES等)中获取一个段选择器。这个段选择器指向全局描述符表(GDT)或者局部描述符表(LDT)中的某个条目。
-
寻找段描述符:然后,你通过段选择器指向的索引在描述符表(GDT或LDT)中找到对应的段描述符。这个描述符包含了段的基地址、限制和权限。
-
计算物理地址:接着,你将段描述符中的基地址与偏移地址相加,得到的结果就是物理地址。如果这个物理地址超出了段描述符中的限制,就会触发一个段错误(Segmentation Fault)。
-
访问控制:最后,当你尝试访问这个物理地址时,还会检查你的访问权限是否符合段描述符中的规定。如果不符合,就会触发一个保护错误(Protection Fault)。 理解段寄存器、段选择器、GDTR、段描述符以及内存寻址的过程。
在x86架构的CPU中,段寄存器(例如DS)存放的是一个段选择器(Segment Selector)。这个段选择器是一个16位的值,可以被划分为三个部分:请求特权级(RPL, Request Privilege Level)、表指示符(TI, Table Indicator)和索引(Index)。
以下是一个段选择器的示例:
15 3 2 0
+---------------------------------+ +-+
| 索引 | |TI|RPL|
+---------------------------------+ +-+
-
索引(Index):这是一个13位的值,它是一个指向GDT或LDT的索引。
-
表指示符(TI, Table Indicator):这是一个1位的值,它决定了索引是指向GDT还是LDT。如果TI=0,那么索引指向GDT,如果TI=1,那么索引指向LDT。
-
请求特权级(RPL, Request Privilege Level):这是一个2位的值,它代表了发起访问请求的特权级别。在x86架构中,特权级别有四个(0-3),其中0级是最高的特权级别,3级是最低的特权级别。
假设DS寄存器中的值为0x002B。在这个值中,索引为0x02,表指示符TI为1(指向LDT),请求特权级RPL为3级。这个段选择器代表了特权级3的程序访问LDT中的第三个描述符(索引从0开始)。
段选择器的具体格式可能会根据不同的CPU架构和操作系统有所不同。例如,在64位的x86架构(x86_64或AMD64)中,虽然段寄存器仍然存在,但是大部分段寄存器(除了FS和GS)的值都被忽略,而所有的内存访问都默认在0级特权下进行。
在x86架构中,局部描述符表(LDT)的基地址和界限存储在LDTR(局部描述符表寄存器)中。当我们需要根据LDT的索引获取段描述符时,就需要从LDTR寄存器中读取LDT的基地址。
段描述符的结构
当基于上述32位段描述符结构的分配,下面是一个示例的测试数据:
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| Base 31:24 | Limit 19:16 | AVL | P | DPL | S | Type |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| Base 23:16 | Limit 15:0 |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
测试数据示例:
Base 31:24: 0x05 // 段基址的高8位为 0x05
Limit 19:16: 0x0F // 段大小的高4位为 0x0F
AVL: 0 // 保留位,未使用
P: 1 // 段存在位,表示该段存在
DPL: 0b00 // 请求特权级为 Ring 0,即最高特权级
S: 1 // 描述符类型位,表示该段为代码段或数据段描述符
Type: 0b0011 // 段类型为数据段,可读写权限
Base 23:16: 0xAB // 段基址的中间8位为 0xAB
Limit 15:0: 0xFFFF // 段大小的低16位为 0xFFFF
这个过程大致如下:
-
读取LDTR:首先,我们从LDTR寄存器中读取LDT的基地址。
-
计算地址:然后,我们根据段选择器中的索引计算出段描述符在LDT中的地址。在x86架构中,每个段描述符占用8个字节,所以段描述符的地址等于LDT的基地址加上索引值乘以8。
例如,假设LDTR的值为0x1000,段选择器中的索引值为2,那么段描述符的地址就是0x1000 + 2 * 8 = 0x1010。
需要注意的是,虽然我们可以通过这种方式计算出段描述符的地址,但是这个地址并不能直接在普通的程序中使用。这是因为段描述符的访问是由CPU的内存管理单元(MMU)自动处理的,普通的程序不能直接访问物理地址。
段检查 需要进行以下检查:
- 段存在性检查(P):检查段描述符的存在位(P)是否为1,以确定段是否存在。
- 特权级检查(DPL):检查段描述符的请求特权级(DPL)与当前特权级是否匹配,以验证是否有权限访问该段。
- 段类型检查(S和Type):检查段描述符的S位和Type字段,验证描述符的类型是否正确。如果S位为1,表示这是一个代码段描述符或数据段描述符。
- 段限制检查(Limit):根据段描述符的大小限制(Limit)来验证访问的偏移是否在允许的范围内。
- 地址转换:根据段描述符的基址和偏移地址,将逻辑地址转换为物理地址。
地址计算
偏移地址是根据具体的代码或数据访问操作来提供的。下面是一个操作数据的例子,以说明如何使用偏移地址计算物理地址:
假设我们有一个逻辑地址,由段选择子和偏移地址组成,表示要访问数据的位置。
-
获取逻辑地址:假设逻辑地址为:段选择子为0x1234,偏移地址为0x5678。
-
根据段选择子获取段描述符:通过段选择子的索引,从全局描述符表(GDT)或局部描述符表(LDT)中获取对应的段描述符。
-
获取段基址:从段描述符中获取段基址(Base)。假设段基址为0xABCDEF.
-
偏移地址与段基址相加:将偏移地址0x5678与段基址0xABCDEF相加,得到线性地址。
线性地址 = 段基址 + 偏移地址 = 0xABCDEF + 0x5678 = 0xABD357 -
线性地址即为物理地址:由于假设没有启用页机制,线性地址直接就是物理地址。
物理地址 = 线性地址 = 0xABD357
这样,通过将偏移地址与段基址相加,我们计算出了访问数据时的物理地址。
需要注意的是,此处的示例仅用于说明计算物理地址的一般过程,并未考虑具体的处理器架构和操作系统的细节。实际的地址转换过程可能会有更多的复杂性和细节,包括地址溢出、段限制检查、特权级验证等。具体的实现和处理方式可能会有所不同。
2.段页门
os是面向cpu的段页门实现的。 总结如下。
-
段(Segmentation): 是内存访问隔离的工具。每个段代表了一块独立的内存区域,并且每个段可以设定自己的访问权限,以此隔离不同段之间的内存访问。
-
页(Paging): 是虚拟内存系统的基本单元。通过分页,可以将内存的物理地址空间与虚拟地址空间分离,实现灵活的内存访问和管理。
-
门(Gates): 用于实现权限的提升和降低。例如,当用户态的程序需要调用操作系统内核的服务时,需要通过一个系统调用门,使得程序能够安全地从用户模式切换到内核模式。
这三者合起来,就构成了操作系统的内存管理和保护机制,以支持多任务环境下的程序隔离和内存保护等功能。