指令中指定的逻辑地址首先由分段硬件转换为线性地址。然后,此线性地址由分页单元转换为物理地址。
在分页单元的地址转换中存在两个级别的间接寻址。一个页目录包含指向 1024 个页表的指针。每个页表包含指向 1024 个页面的指针。寄存器 CR3 包含页目录的物理基地址,并作为 TSS 的一部分存储在task_struct中,因此在每次任务切换时加载。
32 位线性地址的划分如下
31 ...... 22 | 21 ...... 12 | 11 ...... 0 |
---|---|---|
DIR (目录) | TABLE (表) | OFFSET (偏移量) |
CR3 + DIR | 指向 table_base (表基址)。 |
table_base + TABLE | 指向 page_base (页基址)。 |
physical_address = (物理地址 =) | page_base + OFFSET |
页目录(页表)是页对齐的,因此较低的 12 位用于存储有关条目指向的页表(页)的有用信息。
页目录和页表条目的格式
31 ...... 12 | 11 .. 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|---|
ADDRESS (地址) | OS (操作系统) | 0 | 0 | D (脏位) | A (已访问位) | 0 | 0 | U/S (用户/超级用户) | R/W (读/写) | P (存在位) |
D (脏位) | 1 表示页面是脏页(对于页目录条目未定义)。 |
R/W (读/写) | 0 表示用户只读。 |
U/S (用户/超级用户) | 1 表示用户页面。 |
P (存在位) | 1 表示页面存在于内存中。 |
A (已访问位) | 1 表示页面已被访问(通过老化设置为 0)。 |
OS (操作系统) | 这些位可用于 LRU 等,并由操作系统定义。 |
当页面被交换出去时,页表条目的位 1-31 用于标记页面在交换空间中的存储位置(位 0 必须为 0)。
通过设置 CR0 中的最高位来启用分页。[在 head.S 中?] 在地址转换的每个阶段,都会验证访问权限,并且内存中不存在的页面和保护冲突会导致页错误。然后,故障处理程序(在 memory.c 中)会调入新页面或取消页面的写保护,或执行需要完成的任何操作。
位 | 已清除 | 已设置 |
0 | 页面不存在 | 页面级保护 |
1 | 由于读取导致的错误 | 由于写入导致的错误 |
2 | 超级用户模式 | 用户模式 |
转换后备缓冲区 (TLB) 是一个硬件缓存,用于缓存最近使用的虚拟地址的物理地址。当转换虚拟地址时,386 首先在 TLB 中查找以查看它需要的信息是否可用。如果不可用,它必须进行几次内存引用才能访问页目录,然后再访问页表,然后才能真正访问页面。对于每个逻辑内存引用,地址转换需要三次物理内存引用,这将严重影响系统性能,因此需要 TLB。
如果加载 CR3 或任务切换更改 CR0,则会刷新 TLB。在 Linux 中,通过调用invalidate()显式刷新 TLB,这只是重新加载 CR3。
段寄存器用于地址转换,以从逻辑(虚拟)地址生成线性地址。
linear_address = segment_base + logical_address (线性地址 = 段基址 + 逻辑地址)
然后,线性地址通过分页硬件转换为物理地址。
系统中的每个段都由一个 8 字节的段描述符描述,其中包含所有相关信息(基址、限长、类型、特权)。
段是
系统段的特性
为了跟踪所有这些段,386 使用一个全局描述符表 (GDT),该表由系统在内存中设置(由 GDT 寄存器定位)。GDT 包含每个任务状态段、每个局部描述符表以及常规段的段描述符。Linux GDT 仅包含两个正常段条目
LDT[n] != LDTn
LDT[n] | = 当前任务的 LDT 中的第 n 个描述符。 |
LDTn | = GDT 中第 n 个任务的 LDT 的描述符。 |
内核段具有基址 0xc0000000,这是内核在线性视图中所在的位置。在使用段之前,必须将该段的描述符内容加载到段寄存器中。386 具有一套复杂的段访问标准,因此您不能简单地将描述符加载到段寄存器中。此外,这些段寄存器具有程序员不可见的部分。可见部分通常称为段寄存器:cs、ds、es、fs、gs 和 ss。
程序员使用一个 16 位值(称为选择器)加载这些寄存器之一。选择器唯一地标识其中一个表中的段描述符。硬件验证访问并加载相应的描述符。
目前,Linux 在很大程度上忽略了 386 提供的(过度?)复杂的段级保护。它偏向于分页硬件和相关的页级保护。应用于用户进程的段级规则是
段选择器加载到段寄存器(cs、ds 等)中,以选择系统中的常规段之一作为通过该段寄存器寻址的段。
段选择器格式
15 ...... 3 | 2 1 | 0 |
---|---|---|
index (索引) | TI (表指示器) | RPL (请求特权级) |
示例
Linux 中使用的选择器
TI (表指示器) | index (索引) | RPL (请求特权级) | selector (选择器) | segment (段) | |
---|---|---|---|---|---|
0 | 1 | 0 | 0x08 | 内核代码 | GDT[1] |
0 | 2 | 0 | 0x10 | 内核数据/堆栈 | GDT[2] |
0 | 3 | 0 | ??? | ??? | GDT[3] |
1 | 1 | 3 | 0x0F | 用户代码 | LDT[1] |
1 | 2 | 3 | 0x17 | 用户数据/堆栈 | LDT[2] |
进入系统调用时
有一个段描述符用于描述系统中的每个段。有常规描述符和系统描述符。这是一个完整的描述符。奇怪的格式主要是为了保持与 286 的兼容性。请注意,它占用 8 个字节。
63-54 | 55 | 54 | 53 | 52 | 51-48 | 47 | 46 | 45 | 44-40 | 39-16 | 15-0 |
---|---|---|---|---|---|---|---|---|---|---|---|
Base (基址) 31-24 | G (粒度) | D (脏位) | R (保留位) | U (保留位) | Limit (限长) 19-16 | P (存在位) | DPL (描述符特权级) | S (段描述符类型) | TYPE (类型) | Segment Base (段基址) 23-0 | Segment Limit (段限长) 15-0 |
Explanation (解释)
R (保留位) | reserved (保留) (0) |
DPL (描述符特权级) | 0 表示内核,3 表示用户 |
G (粒度) | 1 表示 4K 粒度(在 Linux 中始终设置) |
D (脏位) | 1 表示默认操作数大小为 32 位 |
U (保留位) | programmer definable (程序员可定义) |
P (存在位) | 1 表示存在于物理内存中 |
S (段描述符类型) | 0 表示系统段,1 表示正常代码或数据段。 |
Type (类型) | 有很多可能性。对于系统和正常描述符的解释不同。 |
Linux 系统描述符
TSS: P=1, DPL=0, S=0, type=9, limit = 231,可容纳 1 个tss_struct.
LDT: P=1, DPL=0, S=0, type=2, limit = 23,可容纳 3 个段描述符。
基址在fork()期间设置。每个任务都有一个 TSS 和 LDT。
Linux 常规内核描述符: (head.S)
code: P=1, DPL=0, S=1, G=1, D=1, type=a, base=0xc0000000, limit=0x3ffff (代码段:P=1, DPL=0, S=1, G=1, D=1, 类型=a, 基址=0xc0000000, 限长=0x3ffff)
data: P=1, DPL=0, S=1, G=1, D=1, type=2, base=0xc0000000, limit=0x3ffff (数据段:P=1, DPL=0, S=1, G=1, D=1, 类型=2, 基址=0xc0000000, 限长=0x3ffff)
task[0] 的 LDT 包含: (sched.h)
code: P=1, DPL=3, S=1, G=1, D=1, type=a, base=0xc0000000, limit=0x9f (代码段:P=1, DPL=3, S=1, G=1, D=1, 类型=a, 基址=0xc0000000, 限长=0x9f)
data: P=1, DPL=3, S=1, G=1, D=1, type=2, base=0xc0000000, limit=0x9f (数据段:P=1, DPL=3, S=1, G=1, D=1, 类型=2, 基址=0xc0000000, 限长=0x9f)
剩余任务的默认 LDT (exec())
code: P=1, DPL=3, S=1, G=1, D=1, type=a, base=0, limit= 0xbffff (代码段:P=1, DPL=3, S=1, G=1, D=1, 类型=a, 基址=0, 限长= 0xbffff)
data: P=1, DPL=3, S=1, G=1, D=1, type=2, base=0, limit= 0xbffff (数据段:P=1, DPL=3, S=1, G=1, D=1, 类型=2, 基址=0, 限长= 0xbffff)
内核段的大小为 0x40000 页(4KB 页,因为 G=1 = 1 吉字节)。类型意味着代码段的权限是读-执行,数据段的权限是读-写。
与分段关联的寄存器。
段寄存器的格式:(只有选择器对程序员可见)
16-bit (16 位) | 32-bit (32 位) | 32-bit (32 位) | |
---|---|---|---|
selector (选择器) | physical base addr (物理基地址) | segment limit (段限长) | attributes (属性) |
GDTR(和 IDTR)的格式
32-bits (32 位) | 16-bits (16 位) |
---|---|
Linear base addr (线性基地址) | table limit (表限长) |
TR 和 LDTR 从 GDT 加载,因此具有其他段寄存器的格式。任务寄存器 (TR) 包含当前执行任务的 TSS 的描述符。跳转到 TSS 选择器的执行会导致状态保存在旧 TSS 中,TR 加载新描述符,寄存器从新 TSS 恢复。这是 schedule 用于切换到各种用户任务的过程。请注意,字段tss_struct.ldt包含该任务的 LDT 的选择器。它用于加载 LDTR。(sched.h)
一些汇编器宏在 sched.h 和 system.h 中定义,以简化描述符的访问和设置。每个 TSS 条目和 LDT 条目占用 8 个字节。
操作 GDT 系统描述符
版权所有 (C) 1992, 1993, 1996 Michael K. Johnson, johnsonm@redhat.com。
版权所有 (C) 1992, 1993 Krishna Balasubramanian 和 Douglas Johnson