linux内存管理之分页机制

linux(X86)将进程的地址空间分成一段一段的,利用这些段来存储数据或者代码。x86 硬件包括几个可编程的寄存器,称为 段寄存器 (segment register),段选择器保存于其中。这些寄存器为 cs(代码段)、ds(数据段)和 ss(堆栈段)。这些寄存器中都存储了段选择符。每个段选择符都是16位。每个段选择符包括:

1、一个 13 位的索引,用来标识 GDT 或 LDT 中包含的对应段描述符条目。

2、TI (Table Indicator) 标志指定段描述符是在 GDT 中还是在 LDT 中,如果该值是 0,段描述符就在 GDT 中;如果该值是 1,段描述符就在 LDT 中。

3、RPL (request privilege level) 定义了在将对应的段选择器加载到段寄存器中时 CPU 的当前特权级别。

这些段描述符可以存储在一个 GDT(全局描述符表,global descriptor table)中,也可以存储在一个 LDT(本地描述符表,local descriptor table)中。

在GDT/LDT中,每个表项就是一个段描述符,每个表项是8个字节,其中12位用来表示段的属性。

wKioL1OIQvazNaWkAADD_hn1l_4620.jpg

下面我们分析一下分段机制在linux中的实现:

Linux 使用以下段描述符:

内核代码段

内核数据段

用户代码段

用户数据段

TSS 段

默认 LDT 段

GDT 中的内核代码段 (kernel code segment) 描述符中的值如下:

Base = 0x00000000

Limit = 0xffffffff (2^32 -1) = 4GB

G(粒度标志)= 1,表示段的大小是以页为单位表示的

S = 1,表示普通代码或数据段

Type = 0xa,表示可以读取或执行的代码段

DPL 值 = 0,表示内核模式

与这个段相关的线性地址是 4 GB,S = 1 和 type = 0xa 表示代码段。选择器在 cs 寄存器中。Linux 中用来访问这个段选择器的宏是 _KERNEL_CS。

内核数据段 (kernel data segment) 描述符的值与内核代码段的值类似,惟一不同的就是 Type 字段值为 2。这表示此段为数据段,选择器存储在 ds 寄存器中。Linux 中用来访问这个段选择器的宏是 _KERNEL_DS。

用户代码段 (user code segment) 由处于用户模式中的所有进程共享。存储在 GDT 中的对应段描述符的值如下:

Base = 0x00000000

Limit = 0xffffffff

G = 1

S = 1

Type = 0xa,表示可以读取和执行的代码段

DPL = 3,表示用户模式

在 Linux 中,我们可以通过 _USER_CS 宏来访问此段选择器。

在 用户数据段 (user data segment) 描述符中,惟一不同的字段就是 Type,它被设置为 2,表示将此数据段定义为可读取和写入。Linux 中用来访问此段选择器的宏是 _USER_DS。

除了这些段描述符之外,GDT 还包含了另外两个用于每个创建的进程的段描述符 ―― TSS 和 LDT 段。

每个 TSS 段 (TSS segment) 描述符都代表一个不同的进程。TSS 中保存了每个 CPU 的硬件上下文信息,它有助于有效地切换上下文。例如,在 U->K 模式的切换中,x86 CPU 就是从 TSS 中获取内核模式堆栈的地址。

每个进程都有自己在 GDT 中存储的对应进程的 TSS 描述符。这些描述符的值如下:

Base = &tss (对应进程描述符的 TSS 字段的地址;例如 &tss_struct)这是在 Linux 内核的 schedule.h 文件中定义的

Limit = 0xeb (TSS 段的大小是 236 字节)

Type = 9 或 11

DPL = 0。用户模式不能访问 TSS。G 标志被清除

所有进程共享默认 LDT 段。默认情况下,其中会包含一个空的段描述符。这个默认 LDT 段描述符存储在 GDT 中。Linux 所生成的 LDT 的大小是 24 个字节。默认有 3 个条目:

LDT[0] = 空

LDT[1] = 用户代码段

LDT[2] = 用户数据/堆栈段描述符


你可能感兴趣的:(linux,内存管理,分段机制)