long mode 分页_x86 系列 CPU 内存寻址模式总结

说明:

S16 表示 16 位段寄存器

P16 表示 16 位的普通寄存器, 立即数, 结果为 16 位的表达式等等.

P32 同上, 只是扩展到 32 位.

一. CPU 概况

1. 8086: 8 位数据线, 16 位地址线. 8 位数据线和前8位地址线合用.

2. 8088: 16位数据线, 16位地址线. 数据线和地址线完全分时合用.

3. 80186: 16位数据线, 16位地址线. 数据线和地址线完全分时合用.

4: 80188: 16位数据线, 16位地址线. 数据线和地址线完全分时合用.

5. 80286: 16位数据线, 24位地址线. 数据线和地址线是完全分开的. 转到保护模式的过渡 CPU.

6. 80386: 32位数据线, 32位地址线. 数据线和地址线是完全分开的(其中80386SX像80286).

7. 80486: 32位数据线, 32位地址线.

8. Pentium: 64位数据线, 32位地址线.

9. Pentium Pro: 64位数据线, 36位地址线.

二.  实模式: 分段内存

1. 支持的 CPU:  8086 以上

2. 启用方式: 启动后自动进入

3. 地址长度: 20

4. 寻址能力: 1M

5. ALU宽度: 16

6. 寻址过程:

引入了 CS, DS, SS, ES 这 4 个 16 位的段寄存器. 寻址时将段寄存器左移 4 位后再加上 16 位的偏移, 既:  (S16 << 4) + P16. 得到 20 位的地址.

省略段寄存器时, 会使用默认的段寄存器:

偏移

用途

CS

IP

指令地址

DS

AX, BX, SI, DI, Disp8/16

数据地址

SS

SP, BP

堆栈地址

ES

DI

串操作目的地址

三. 保护模式: 分段内存

1. 支持的 CPU: 80286 以上

2. 启用方式:  将 CR0 寄存器的 PE 位置 1.

3. 地址长度: 32

4. 寻址能力: 4GB

5. ALU宽度: 32

6. 寻址过程:

从 32 位的数据宽度, 寻 32 位的地址, 看起来似乎是非常简单的一件事情. 不过由于对内存保护的加入, 这个过程其实更为复杂. 而 Intel 又选择了兼容之前的分段内存, 且分段机制在进入保护模式后是必须的, 不能关闭. 其实大多数的操作系统实现的时候都选择绕过分段机制, 只使用分页机制来进行内存管理.

保护模式的分段机制保留了以前的段寄存器, 并且增加了两个 FS, GS. 这些段寄存器仍然为 16 位, 但是里面保存的不再是段的基地址了, 而是一个段选择子, 段选择子是一个如下的结构:

struct SegSelector {

unsigned int RPL : 2;

unsigned int PI  : 1;

unsigned int Selector : 13;

};

既, 0-1 位表示请求的权限, 共有 0 - 3 四种. 但几乎所有的操作系统都只使用 2 种. 0 表示内核的, 3 表示用户空间的.  第 2 位决定是使用 GDT 还是 LDT, 为 0 使用 GDT, 为 1 使用 LDT. 选择绕过分段机制的操作系统是不会创建 LDT 的, 永远只使用 GDT. 最后高 13 位表示在 GDT/LDT 中的索引号.

GDT/LDT 是段描述符的数组, 段描述符是描述段的基址, 边界, 属性的一个结构, 共 64 bit, 8 个字节. GDT 是全局的, 只有一个. LDT 可以有多个. Intel 设想的是, GDT 供操作系统内核使用, LDT 每个进程一个, 但是基本上没有人按这种方式来使用. 段描述符的结构如下:

struct SegDesc {

unsigned short limitLow;

unsigned int baseLow : 24;

unsigned int type : 4;  // 根据 S 字段的不用, 有不同的含义. 包含读写权限等等.

unsigned int S : 1;  // 系统段标志, 为 0 表示系统段

unsigned int DPL : 2;  // 段的权限等级.  0 为内核  3 位用户

unsigned int P : 1;  // 段是否在内存中, 为 1 表示在内存. 当 P 为 0 时, base 和 limit 都是无效的, 操作系统可以用来保存自己的数据.

unsigned int limitHigh : 4;

unsigned int AVL : 1; // CPU 不使用, 软件自己决定表示什么.

unsigned int zero : 1; // 为 0

unsigned int DB : 1;   // 根据段的使用方式, 有不同的含义.

unsigned int G : 1;  // 粒度, 决定 Limit 的单位.  为 0, 单位为字节. 为 1, 单位是 4KB

unsigned char baseHigh;

};

之所以这么复杂, 是因为在 80286 中, 高 16 位是没有使用的. 新的 CPU 为了兼容这个过渡产品, base 和 limit 只能扩展到高 16 位去.

GDT/LDT 这个数组是放在内存里面的, 所以还需要记录它们在内存中的起始位置, 为此, 又增加了 2 个寄存器: GDTR 和 LDTR.  GDTR 是一个 48 位的寄存器, 高 32 位是一个线性地址, 低 16 位是边界. LDTR 是一个 16 位寄存器, 里面保存的是在 GDT 中的索引号.

struct {

unsigned short limit;

unsigned int base;

} GDTR;

这样, 段寄存器通过查询 GDT/LDT 表获得段的基址后, 再加上偏移得到一个 32 位的地址. 这个地址被称为线性地址. 如果没有启用下面说的分页机制, 那么线性地址就是物理地址. 如果启用了分页机制, 线性地址还需要经过一次映射才能得到物理地址. 综上, 虚拟地址到线性地址的映射关系可用如下伪代码来描述:

if(S16 & 0x04 == 0)  // 取段寄存器的第 3 位, 判断是使用 GDT 还是 LDT

{  // 使用 GDT

gdt =  (GDTR >> 16) & 0xFFFFFFFF;  // 取 GDTR 寄存器的高 32 位, 既 GDT 表的起始地址.

sd = gdt + S16 & 0xFFF8;  // 根据段寄存器中的索引号找到段描述符

sbase_l = (sd >> 12) & 0x00FFFFFF;   // 取基址的低 24 位

sbase_h = (sd >> 32) & 0xFF000000; // 取基地址高 8 位

sbase = sbase_h | sbase_l;

return sbase + P32  // 基址加上偏移得到最终的线性地址

}

else

{  // 使用 LDT

gdt = (GDTR >> 16) & 0xFFFFFFFF;

sd_ldt = gdt + LDTR & 0xFFF8;

ldt =(sd_ldt >> 12) & 0x00FFFFFF +(sd_ldt >> 32) & 0xFF000000;

sd = ldt + S16 & 0xFFF8;

sbase =(sd >> 12) & 0x00FFFFFF +(sd >> 32) & 0xFF000000;

return sbase + P32;

}

或者, 我们使用上面定义的结构来表述:

if(SegSelector(S16)->PI)

{  // PI 为 1 使用 LDT

SegDesc* gdt = GDTR->base;

SegDesc* ldt= &gdt[LDTR >> 3];

SegDesc* sd = &ldt[SegSelector(S16)->Selector];

void* base = sd->baseHigh << 24 + sd->baseLow;

return base + P32

}

else

{  // PI 为 0 使用 GDT

SegDesc* gdt = GDTR->base;

SegDesc* sd = &gdt[SegSelector(S16)->Selector];

void* base = sd->baseHigh << 24 + sd->baseLow;

return base + P32

}

同样, 在没有指明段寄存器的情况下, 会使用一个默认的段寄存器:

偏移

用途

CS

EIP

指令地址

DS

EAX, EBX, ECX, EDX, ESI, EDI,  Disp8/16/32

数据地址

SS

ESP, EBP

堆栈地址

ES

EDI

串操作目的地址

GS

一般地址

FS

一般地址

以下是网上搜集的描述分段内存的图表:

四. 保护模式: 分页内存

1. 支持的 CPU: 80386 以上, PSE 需要 Pentium 以上

2. 启用方式: 将 CR0 寄存器的 PG 位置 1

3. 地址长度: 32

4. 寻址能力: 4GB

5. ALU宽度: 32

6. 寻址过程:

分页机制是现代操作系统实现内存管理的主要方式. 它将线性地址空间划分为固定大小的页面, 每个页面可以被映射到物理内存或外部存储器的虚拟内存文件中, 并且进行权限检查. 在没有启用 PAE 时, 内存页面大小可以是 4KB 或 4MB. 如果 CR4 中的 PSE 位是 0, 则只支持 4KB 的内存页. 如果 PSE 位是 1, 则根据 PDE 中的 PS 位来决定内存页的大小.

1#  4KB 页面寻址

对于 4KB 的页面, 32 位的线性地址不再表示物理地址, 而是变成如下的含义了:

struct LineAddress {

unsigned int offset : 12;

unsigned int table  : 10;

unsigned int directory : 10;

};

其中高 10 位表示页目录的下标, 中间 10 位表示页表中的下标.  在系统中, 每一个进程有一个页目录, 其基址为 4KB 对齐, 低 12 位为 0, 高 20 位存放在 CR3 寄存器中的高 20 位里. 因此, CR3 寄存器又被称为页目录基址寄存器(PDBR). CR3 的结构为:

struct CR3 {

unsigned int nouse1 : 3;

unsigned int PWT : 1;   // Page Write Through 标志, CR0.CD 为 1 时忽略, CR0.CD 为 0 时: PWT = 1 使用 Write-Through 的缓存类型, PWT = 0 使用 Write-Back 的缓存类型.

unsigned int PCD : 1;    // Page Cache Disable 标志, CR0.CD 为 1 时忽略, CR0.CD 为 0 时: PCD = 1 表示该物理页不能被缓存

unsigned int nouse2 : 7;

unsigned int pdt_base : 20;

};

页目录(Page Directory)是一个 4KB 大小的数组, 里面包含 1024 个 4 字节的页目录表项(PDE, Page Directory Entry). 对于 4KB 的页面, PDE 的结构如下:

struct PDE {

unsigned int P : 1;  // Present, 是否在物理内存中, 1 在, 0 不在

unsigned int R/W: 1;  // Read/Write, 为 1 表示可读写, 为 0 表示只读

unsigned int U/S : 1;  // User/Supervisor 为 0 表示管理权限, 为 1 表示用户权限

unsigned int PWT: 1; // Write-through

unsigned int PCD : 1; // Cashe-Disabled

unsigned int A : 1;    // Accessed 是否被访问过, 1 表示访问过

unsinged int ZERO: 1; // 固定为 0

unsigned int PS : 1;    //  PageSize, 页大小, 0 表示 4KB, 1 表示 4MB. 当 CR4 的 PSE 为 0 时, 忽略该项, 页大小始终为 4KB.

unsigned int G : 1;     // Global Page, 全局页.

unsigned int nouse : 3; // CPU 未使用, 供系统程序员使用.

unsigned int base : 20; // 页表基址的高 20 位, 低 12 位固定为 0. 所以, 页表基址一定是按 4KB 对齐的.

};

PDE 中最重要的, 是高 20 位表示的页表基址, 它指向的是一个 4KB 的页表(Page Table), 页表包含了 1024 个 4 字节的页表表项(Page Table Entry, PTE). PTE 的结构如下:

struct PTE {

unsigned int P : 1;

unsinged int R/W : 1;

unsigned int U/S : 1;

unsigned int PWT: 1;

unsigned int PCD : 1;

unsigned int A : 1;

unsigned int D : 1;  // Dirty 表示内存页是否被修改过, 1 修改过 0 未修改过

unsigned int PAT : 1;  // Page Attribute Table, 在全局属性表中的索引

unsigned int G : 1;  // 全局页

unsigned int nouse : 3; // 供系统程序员使用

unsigned int base : 20; // 内存页起始物理地址的高 20 位, 低 12 位固定为 0, 所以必须按 4KB 对齐.

};

根据上面的结构, 线性地址到物理地址的映射过程, 可以用如下伪代码来表示:

LineAddress laddr = P32;

PDE* pde = CR3.pdt_base << 12;

PTE* pte = pde[laddr.direcotry].base << 12;

char* pageStart = pte[laddr.table].base << 12;

return pageStart + laddr.offset;

2#  4MB 页面寻址 (PSE 模式)

对于 4MB 的页面, 不需要使用页表, 只需要页目录的一层映射. 要配置 4MB 的页面, 需要设置 CR4 中的 PSE 位. 并在 PDE 中设置 PS 位. 使用 4MB 页面时, 线性地址的结构如下:

struct LineAddress {

unsigned int offset : 22;

unsigned int directory : 10;

};

此时的 PDE 结构如下:

struct PDE {

/* 0      */unsigned int P : 1;

/* 1      */unsinged int R/W : 1;

/* 2      */unsigned int U/S : 1;

/* 3      */unsigned int PWT: 1;

/* 4      */unsigned int PCD : 1;

/* 5      */unsigned int A : 1;

/* 6      */unsigned int D : 1;

/* 7      */unsigned int PS : 1; // 4MB 页面该位为 1

/* 8      */unsigned int G : 1;

/*  9-11 */unsigned int nouse : 3; // 供系统程序员使用

/*  12    */unsigned int PAT : 1;

/* 13-21 */unsigned int reserved : 9; // 保留未使用, 必须为 0

/* 22-31 */unsigned int base : 10;   // 内存页起始物理地址的高 10 位, 低 22 位固定为 0, 所以必须按 4MB 对齐.

};

此时的寻址过程伪代码如下:

LineAddress laddr = P32;

PDE* pde = CR3.pdt_base << 12;

char* pageStart =pde[laddr.direcotry].base << 22;

return pageStart + laddr.offset;

以下是网上搜集的描述分页内存的图表:

1. 内存的二级分页结构

2. 4KB 页面寻址过程

3. 4MB 页面寻址过程

五. 虚拟 86 模式 (V8086, V86)

1. 支持的 CPU:  80386 以上

2. 启用方式: 在保护模式下, 将标志寄存器中的 VM 位置 1

3. 地址长度: 20

4. 寻址能力: 1MB

5. ALU 宽度: 16

6. 寻址过程:

虚拟 86 模式是保护模式下, 某些任务的一种工作模式. 此模式是为了能够在保护模式下运行实模式软件. 在虚拟 86 模式下, 软件的工作环境和实模式类似, 使用 (S16 << 4) + P16 的方式访问 1M 的内存, 但是得到的地址不再是物理地址了, 而是由系统的虚拟 86 管理程序分配的内存. 虚拟 86 模式下也可以使用内存分页(实模式下不行), 让没有使用的内存空间不占用物理内存.  虚拟 86 模式下的中断和特殊指令的访问也由系统软件进行模拟, 不能直接访问硬件.

在虚拟 86 模式下, 是不能直接更改标志寄存器的 VM 位的, 所以进入和退出虚拟 86 模式是通过任务切换或中断来完成的.

以下是网上搜集的描述 V86 模式的相关图表:

1. 保护模式和 V86 模式的切换

六. PSE-36: Page Size Extension 36

1. 支持的 CPU: Pentium III 以上 (另说为 Pentium II)

2. 启用方式: 开启 PSE 的情况下, 如果 CPU 支持即可使用

3. 地址长度: 36

4. 寻址能力: 64GB

5. ALU 宽度: 32

6. 寻址过程:

在前面的分页内存中, 处于 PSE 模式时,  PDE 结构只使用了高 10 位作为基址. 在 PSE-36 模式里, 将使用其中的 14 位来作为基址, 这样, 最后的地址位数将达到 36 位, 寻址能力提高到 64GB. 在 PSE 36 模式下, 4MB 页面的 PDE 结构变为:

struct PDE {

/* 0      */unsigned int P : 1;

/* 1      */unsinged int R/W : 1;

/* 2      */unsigned int U/S : 1;

/* 3      */unsigned int PWT: 1;

/* 4      */unsigned int PCD : 1;

/* 5      */unsigned int A : 1;

/* 6      */unsigned int D : 1;

/* 7      */unsigned int PS : 1; // 4MB 页面该位为 1

/* 8      */unsigned int G : 1;

/*  9-11 */unsigned int nouse : 3; // 供系统程序员使用

/*  12    */unsigned int PAT : 1;

/* 13-16 */unsigned int baseHigh : 4;  // 内存起始地址的 32-35 位

/* 17-21 */unsigned int reserved : 5;  // 保留未使用, 必须为 0

/* 22-31 */unsigned int baseLow : 10; // 内存页起始物理地址的 22-31 位.

};

此时寻址过程伪代码为:

LineAddress laddr = P32;

PDE* pde = CR3.pdt_base << 12;

INT64 pageStart =(INT6)pde[laddr.direcotry].baseLow << 22 + (INT64)pde[laddr.directory].baseHigh << 32;

return pageStart + laddr.offset;

对于 4KB 的页面, 仍然和普通的分页内存相同, 它可以表示的内存仍然只要 4GB. 所以, 在 PSE 36 模式下, 4KB 的页面只能位于前 4GB 物理内存中. 4GB 以上的内存只能通过 PSE 方式访问, 页面大小只能为 4MB.

七. PAE: Physical Address Extension 物理地址扩展

1. 支持的 CPU:  Pentium Pro 以上

2. 启用方式: 设置 CR4 中的 PAE 位

3. 地址长度: 36

4. 寻址能力: 64GB

5. ALU 宽度: 32

6. 寻址过程:

在 PAE 模式中, 应用程序仍然为 32 位, 只能使用 4GB 的内存空间. 但是系统可以把不同的进程映射到 64GB 的物理内存中. 应用程序如果需要使用大于 4GB 的内存, 则需要操作系统的特殊支持(Windows 为 AWE, Address Windowing Extensions;  Unix 存在多种, 比如 mmap ).

启用 PAE 后, CR3 寄存器不再指向页目录基址, 而是指向一个页目录指针表 PDPT (Page Directory Pointer Table), 即包含 4 个页目录指针的表.  页目录项和页表项从原来的 4 字节变为 8 字节, 占用的内存大小仍然为 4KB, 所以其中的表项从 1024 个变为 512 个.  因此, 现在一共有 4 个页目录表, 页目录表和页表的下标也只需要 9 位了, 于是线性地址的划分也有了变化.

1#  4KB 页面寻址

在 4KB 页面下, 线性地址被描述为:

struct LineAddress {

unsigned int offset : 12;

unsigned int table  : 9;

unsigned int directory : 9;

unsigned int ptrindex  : 2;

};

其中的高 2 位表示在页目录指针表 PDPT 内的索引, CR3 寄存器里保存了 PDPT 的地址, CR3 结构为:

struct CR3 {

unsigned int nouse : 5;

unsigned int pdpt_base : 27;

};

因此, PDPT 的地址是 32 字节对齐的, 因为 PDPT 的大小是 32 字节. PDPT 的表项 PDPTE 是 64 位的, 下面是 PDPTE 的结构:

struct PDPTE {

unsigned int P : 1;

unsigned int reseved1 : 2; // 保留, 必须为 0

unsigned int PWT : 1;

unsigned int PCD : 1;

unsigned int reserved2 : 4;  // 保留, 必须为 0

unsigned int nouse : 3;

unsigned int pdt_base : 52;

};

PDPTE 从第 12 位开始的高位是 PDT 的基地址的高位, 随着物理地址位数的不同, 使用的位也不同, 未使用的位需保持为 0.  PDT 的低 12 位固定为 0, 按 4KB 对齐. 比如果物理地址是 52 位, 则 PDT 的高 40 位由 PDPTE[51:12] 提供, PDPTE[63:52] 必须为 0. 当物理地址是 40 位时, PDT[39:12] = PDPTE[39:12], 物理地址 36 位时 PDT[35:12] = PDPTE[35:12].

PDE 在 PAE 模式下是 64 位了, 结构中的 PS = 0 时表示 4 KB 的页面, 此时的 PDE 结构如下:

struct PDE_4k {

unsigned int P : 1;

unsigned int R/W : 1;

unsigned int U/S : 1;

unsigned int PWD : 1;

unsigned int PCD : 1;

unsigned int A : 1;

unsigned int nouse1 : 1; // 忽略

unsigned int PS : 1; // 4KB 页面这里是 0

unsigned int nouse2 : 4;

unsigned int pt_base : 51; // PDT 的基址, 和 PDPTE 类似, 随着物理地址位数不同, 该结构中有效的位数也不同, 无效的位需要为 0

unsigned int XD : 1;  // Execution Disable: 当寄存器 IA32_EFER.NXE 置位后有效, 否则为保留必须为 0 的位. 开启 XD 功能后, PDE.XD = 1 或 PTE.XD = 1 则该页面是数据页, 不可执行.

};

PDE 中从第 12 位开始的高位表示 PT 的基地址, 随着物理地址的位数不同, 使用的 PDE 结构中的位数也不同. PT 基地址的低 12 位固定为 0, 以 4 KB 对齐. PT 中存放的 PTE 结构也是 64 位的了:

struct PTE {

unsigned int P : 1;

unsigned int R/W : 1;

unsigned int U/S : 1;

unsigned int PWD : 1;

unsigned int PCD : 1;

unsigned int A : 1;

unsigned int D : 1;

unsigned int PAT : 1;

unsigned int G : 1;

unsigned int nouse : 3;

unsigned int page_frame : 51; // 12~62 位是 4KB 页面的基地址了, 和 PDPTE 一样, 随着物理地址位数的不同, 有效位数不同.

unsigned int XD : 1;

};

在上述扩展下, 寻址过程和 Non-PAE 模式下是类似的, 只是多了 PDPT 一个层次, 线性地址到物理地址转换的伪代码表示如下:

LineAddress laddr = P32;

PDPTE* pdpt = CR3.pdpt_base << 5;

PDE_4k* pde = pdpt[laddr.ptrindex].pdt_base << 12;

PTE* pt   = pde[laddr.direcotry].pt_base << 12;

char* pageStart =pt[laddr.table].page_frame << 12;

return pageStart + laddr.offset;

其中的指针类型的位数不再是 32 位, 可以认为是 64 位或物理地址的位数.

2#  2MB 页面寻址

在 PDE 中, PS 位是 1 的话, 将会使用 2MB 的页面, 由于不再使用 PTE 结构, 线性地址的含义如下:

struct LineAddress {

unsigned int offset : 21;

unsigned int directory : 9;

unsigned int ptrindex  : 2;

};

CR3 以及 PDPTE 的结构都和 4KB 模式下相同, PDE 的结构有些区别:

struct PDE_2M {

unsigned int P : 1;

unsigned int R/W : 1;

unsigned int U/S  : 1;

unsigned int PWT : 1;

unsigned int PCD : 1;

unsigned int A : 1;

unsigned int D : 1;

unsigned int PS : 1 // 2M 页面该位是 1

unsigned int G : 1;

unsigned int nouse1 : 3;

unsigned int PAT : 1;

unsigned int reserved : 8; // 保留必须为 0

unsigned int frame_base : 43; // 21~63, 随物理地址位数不同有效位不同, 无效的位必须为 0

unsigned int XD : 1;

};

其中页面地址的低 21 位固定为 0, 基址按 2MB 对齐, 高位由 PDE_2M 的高位提供. 此时寻址过程如下:

LineAddress laddr = P32;

PDPTE* pdpt = CR3.pdpt_base << 5;

PDE_2M* pde = pdpt[laddr.ptrindex].pdt_base << 12;

char* pageStart =pde[laddr.directory].frame_base << 21;

return pageStart + laddr.offset;

附图:

1. PAE 模式下 4KB 页面的寻址

2. PAE 模式下 2MB 页面的寻址

八. 长模式 (long-mode, IA-32e 模式)

1. 支持的 CPU:  x86-64 的 CPU

2. 启用方式:

同时满足以下条件:

(1). 开启保护模式  CR0.PE = 1

(2). 开启分页机制  CR0.PG = 1

(3). 开启 PAE      CR4.PAE = 1

(4). IA32_EFER.LME = 1  (Long Mode Enable)

(5). IA32_EFER.LMA = 1  (Long Mode Active)

3. 地址长度: 48

4. 寻址能力: 256 TB

5. ALU 宽度: 64

6. 寻址过程:

x86-64 体系, 也被称为 x64 体系, 还被叫做 AMD 64 和 Intel 64 体系. 他们是 x86 体系向 64 位的扩展, 有别于纯 64 位架构的 IA64 体系. x64 体系兼容 x86 的运行模式, 并增加一种新的长模式. x64 的运行模式如下:

x64 体系

子模式

资源

long-mode (IA-32e)

64-bit mode

64 位执行环境

compatibility mode

内核为 64 位, 应用为 32 位的 legacy mode

legacy mode

protected mode

32 位

real mode

16 位

在长模式下, 内核只能为 64 位, 应用可以为 64 位或 32 位. 兼容模式(compatibility mode)和保护模式基本相同.

在 64 位模式下, 寄存器被扩展为 64 位, 默认的地址大小也是 64 位(可以使用 67H 前缀来使用 32 位地址,  但是不能使用 16 位地址), 并增加了 RIP 相对寻址方式. 兼容模式下代码段描述符中的 D 标志位决定了默认的地址大小: D = 0 默认为16位地址, D = 1 默认为32位地址, 可通过 67H 前缀来改变默认值.

1. 分段机制

长模式下的分段机制被进一步的弱化, 但是仍然被保留下来.

在 64 位模式下, 六个段寄存器仍然为 16 位,  其含义和保护模式下相同, 包含 Index, TI, RPL. 除 CS 寄存器外,其余寄存器允许加载 Null selector(SS 只能在 Ring 0/1/2 下加载 Null selector).

GDTR/LDTR 的 base 扩展为 64 位, 所以 GDTR/LDTR 是 80 位的寄存器了:

struct {

unsigned short limit;

unsigned int64 base;

} GDTR;

段描述符仍然为 8 字节 64 位, 但是里面的大多数字段都已经无效了. 因为在 64 位模式下,  只有 FS 和 GS 可以使用非 0 的段基址, 其余的段的基址都被固定为 0, 长度被固定为 0XFFFFFFFF. 对于 FS 和 GS 的段 base 值, 新增了两个 MSR 寄存器来表示, 分别是 IA32_FS_BASE, IA32_GS_BASE . 代码段的描述符格式如下:

struct CodeSegDesc {

0 - 15: unsigned short limitLow;         // 无效

16-39: unsigned int baseLow : 24;     // 无效

40: unsigned int A : 1;                 // 无效

41: unsigned int R : 1;                 // 无效

42: unsigned int C : 1;

43: unsigned int C/D: 1;              // 固定为 1

44: unsigned int S : 1;                // 固定为 1

45-46: unsigned int DPL : 2;

47: unsigned int P : 1;

48-51: unsigned int limitHigh : 4;      // 无效

52: unsigned int AVL : 1;            // 无效

53: unsigned int L : 1;

54: unsigned int D : 1;

55: unsigned int G : 1;               // 无效

56-63: unsigned char baseHigh;       // 无效

};

从上面可见, 64位模式下段描述符只有 C, DPL, P, L, D 五个可用的标志:

C 代码一致性, 影响权限的检查

DPL 标识段的权限

P 段是否在内存中

L 用于长模式下的子模式选择, L=1 表示 64 位模式, L=0 表示兼容模式.

D 用于标识默认操作数的大小

数据段的描述符格式如下:

struct DataSegDesc {

0 - 15: unsigned short limitLow;         // 无效

16-39: unsigned int baseLow : 24;     // 无效

40: unsigned int A : 1;                 // 无效

41: unsigned int W : 1;

42: unsigned int E : 1;                // 无效

43: unsigned int C/D: 1;              // 固定为 0

44: unsigned int S : 1;                // 固定为 1

45-46: unsigned int DPL : 2;

47: unsigned int P : 1;

48-51: unsigned int limitHigh : 4;      // 无效

52: unsigned int AVL : 1;            // 无效

53: unsigned int L : 1;                // 无效

54: unsigned int D : 1;               // 无效

55: unsigned int G : 1;               // 无效

56-63: unsigned char baseHigh;       // 无效

};

其中, 只有 P 和 DPL 标志有效. W 标志只有作为堆栈段时要求必须为 1.

由于段的基址被强制为 0, 所以虚拟地址和线性地址是等价的, 这个地址进行分页转换后成为物理地址.

2. 分页

Long Mode 下, 分页是必须的. 而且只有一种模式, 使用 4 层映射, 在 PAE 的上面又增加了一层. 页面的大小可以是 4K, 2M, 1G. 线性地址长度为 64 位, 目前使用的最高地址为 48 位. 对 4KB 的页面, 线性地址结构如下:

struct LineAddress {

0-11: unsigned int offset : 12;      // 偏移

12-20: unsigned int pte_index : 9;

21-29: unsigned int pde_index : 9;

30-38: unsigned int pdpte_index : 9;

39-47: unsigned int pml4te_index : 9;

48-63: unsigned int sign : 16;        // 符号扩展位

};

其中的 48-63 位是符号扩展位, 要么全 0, 要么全 1, 必须与第 47 位相同. 这种地址被称为规范化地址(canonical address), 是为了方便以后将 48 位的地址扩展到更高位数时无需进行修改. 4 个 9 位的 index 分别是 4 种表结构的下标, 这些表结构的元素都是 8 字节 64 位的, 由于索引为 9 位, 所以这些表结构的大小都是 4KB.  第一个索引 pml4te_index 用于索引 PML4T(Page Map Level-4 Table, 表元素称为 PML4E),  PML4T 的基址由 CR3 寄存器提供. CR3 被扩展为 64 位, 在不支持 PCIDE 功能时, CR3 的结构为:

struct NormalCR3 {

0- 2: unsigned int no_use1 : 3;

3: unsigned int PWT : 1;      // Page-level Write-Through

4: unsigned int PCD  : 1;      // Page-level Cache Disable

5-11:  unsigned int no_use2: 7;

12-47:  unsigned int pml4t_base :  36;

48-63:  unsigned int receved : 16; // 保留为 0

};

PML4T 的基地址低 12 设置为 0, 按 4 KB 对齐. 高位从 CR3 的 12 位开始. 随着物理地址长度不同, 使用的位数也不同, 没有使用的位数则保留为 0.

启用 PCID (CR4.PCIDE = 1, PCID = Process Context ID, 仅 Intel64 支持, AMD64 不支持) 后, CR3 的低 12 位表示 PCID 值, 第 63 位控制 CR3 切换时缓存的处理. 详情从略.

根据 CR3 和 pml4t_index 可以寻到 PML4E 结构,  该结构描述如下:

struct PML4E {

0: unsigned int P : 1;

1: unsigned int R/W : 1;

2: unsigned int U/S  : 1;

3: unsigned int PWT : 1;

4: unsigned int PCD : 1;

5: unsigned int A : 1;

6: unsigned int no_use1 : 1;

7: unsigned int receved : 1; //  PS 位 必须为 0

8-11:  unsigned int no_use2 : 4;

12-47:  unsigned int pdpt_base : 36;

48-62:  unsigned int receved : 15; // 保留为 0, 随物理地址大小扩展

63:  unsigned int XD : 1;  // Execution Disable

};

其中的 pdpt_base 和 pml4t_base 一样, 第 12 位为 0, 高位可向保留位扩展, 以后的结构也都类似. XD 位和 PAE 模式中的 XD 位含义相同. 通过 PML4E 中的 pdpt_base 以及线性地址中的 pdpte_index 可以寻址到 PDPTE 结构, PDPTE 结构将控制 1G 页面的转换, 所以第 7 位 PS 位有效, 当 PS = 1 时直接通过 PDPTE 结构转换 1G 的页面. 此时 PDPTE 结构如下:

struct PDPTE_1G {

0: unsigned int P : 1;

1: unsigned int R/W : 1;

2: unsigned int U/S  : 1;

3: unsigned int PWT : 1;

4: unsigned int PCD : 1;

5: unsigned int A : 1;

6: unsigned int D : 1;

7: unsigned int PS : 1; //  PS 位, 为 1

8: unsigned int G : 1;

9-11:  unsigned int no_use1 : 3;

12: unsigned int PAT : 1;

13-29:  unsigned int receved : 17; // 保留, 必须为 0

30-47:  unsigned int page_base : 18;

48-62:  unsigned int receved : 15;

63:  unsigned int XD : 1;  // Execution Disable

};

其中的 page_base 的低 30 位为 0, 按 1GB 对齐, 高位可向保留位扩展. 线性地址中的 pde_index 和 pte_index 也用于表示偏移, 一个是 30 位的偏移. 由基址 + 偏移得到物理地址.

当 PS = 0 是, 页面是 4K 或 2M 的页面, PDPTE 需要提供 PDT 的基址, 此时的 PDPTE 结构如下:

struct PDPTE {

0: unsigned int P : 1;

1: unsigned int R/W : 1;

2: unsigned int U/S  : 1;

3: unsigned int PWT : 1;

4: unsigned int PCD : 1;

5: unsigned int A : 1;

6: unsigned int no_use1 : 1;

7: unsigned int PS : 1; //  PS 位, 为 0

8-11:  unsigned int no_use2 : 4;

12-47:  unsigned int pdt_base : 36;

48-62:  unsigned int receved : 15; // 保留为 0, 随物理地址大小扩展

63:  unsigned int XD : 1;  // Execution Disable

};

由其中的 pdt_base 和线性地址中的 pde_index 可寻找到 PDE 结构, PDE 中的 PS 位决定是 4KB 页面还是 2MB 页面, 当 PS = 1 时页面为 2M, 此时 PDE 结构如下:

struct PDE_2M {

0: unsigned int P : 1;

1: unsigned int R/W : 1;

2: unsigned int U/S  : 1;

3: unsigned int PWT : 1;

4: unsigned int PCD : 1;

5: unsigned int A : 1;

6: unsigned int D : 1;

7: unsigned int PS : 1; //  PS 位, 为 1

8: unsigned int G : 1;

9-11:  unsigned int no_use1 : 3;

12: unsigned int PAT : 1;

13-20:  unsigned int receved : 8; // 保留, 必须为 0

21-47:  unsigned int page_base : 27;

48-62:  unsigned int receved : 15;

63:  unsigned int XD : 1;  // Execution Disable

};

page_base 的低 21 位为 0, 按 2M 对齐, 线性地址 pte_index 用于表示偏移, 一共 21 位偏移, 由基址 + 偏移得到物理地址.

PDE.PS = 0 时, 页面为 4K, 继续寻找 PTE 结构, 此时的 PDE 为:

struct PDE {

0: unsigned int P : 1;

1: unsigned int R/W : 1;

2: unsigned int U/S  : 1;

3: unsigned int PWT : 1;

4: unsigned int PCD : 1;

5: unsigned int A : 1;

6: unsigned int no_use1 : 1;

7: unsigned int PS : 1; //  PS 位, 为 0

8-11:  unsigned int no_use2 : 4;

12-47:  unsigned int pt_base : 36;

48-62:  unsigned int receved : 15; // 保留为 0, 随物理地址大小扩展

63:  unsigned int XD : 1;  // Execution Disable

};

由 pt_base + pte_index 最终得到 PTE 结构, 此时的 PTE 结构与 PAE 模式下是完全一致的:

struct PTE {

0: unsigned int P : 1;

1: unsigned int R/W : 1;

2: unsigned int U/S  : 1;

3: unsigned int PWT : 1;

4: unsigned int PCD : 1;

5: unsigned int A : 1;

6: unsigned int D : 1;

7: unsigned int PAT : 1; //  PS 位变为 PAT 标志

8: unsigned int G : 1;

9-11:  unsigned int no_use1 : 3;

12-47:  unsigned int page_base : 36;

48-62:  unsigned int receved : 15;

63:  unsigned int XD : 1;  // Execution Disable

};

最终由 PTE 的 page_base 加上线性地址的 offset 得到物理地址, 漫长的寻址过程终于画上了句号.

以下是网上搜集的描述长模式下寻址相关的图表:

1. 线性地址到物理地址转换, 灰色路线是 2M 页面, 深灰色路线是 1G 页面, 黑色路线是 4K 页面的转换.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/504095.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

典型相关分析_2020-2025年中国海水淡化行业发展前景与投资预测分析报告

《2020-2025年中国海水淡化行业发展前景与投资预测分析报告》利用中经未来长期对海水淡化市场跟踪搜集的一手数据&#xff0c;从行业的高度全面而准确的构建分析体系。报告主要分析了海水淡化行业的发展概况、海水淡化行业的发展环境、国外海水淡化行业发展经验借鉴、中国海水淡…

语言还是你不得不服的大哥级别编程语言!

直到今天&#xff0c;有人在喊C语言过时的语言&#xff0c;还有什么值得学习的&#xff0c;现在看Python&#xff0c;PHP等语言现在都很容易用&#xff0c;谁还在学习老C语言&#xff0c;其实这是真的吗&#xff1f;作者下载了两种语言的源代码作为下载器。由于空间的限制&…

字节跳动测试开发4轮面试_字节跳动测试开发工程师一面总结

公司简介:字节跳动是全球发展速度最快的科技公司之一&#xff0c;公司旗下拥有今日头条、抖音、西瓜视频、懂车帝、Faceu激萌、轻颜相机、飞书、皮皮虾、TikTok等多款海内外产品&#xff0c;全系产品月活用户超过15亿。目前还在积极探索游戏、教育、金融领域。字节跳动办公室遍…

python爬虫好学不_python爬虫好学吗

python爬虫难学吗 简单来说互联网是由一个个站点和网络设备组成的大网&#xff0c;我们通过浏览器访问站点&#xff0c;站点把HTML、JS、CSS代码返回给浏览器&#xff0c;这些代码经过浏览器解析、渲染&#xff0c;将丰富多彩的网页呈现我们眼前。爬虫是什么&#xff1f; 如果我…

力压Java、C语言!Python 获2018年度编程语言

TIOBE 近日宣布&#xff1a;Python 成为 2018 年度编程语言&#xff0c;Python之所以获得这个称号&#xff0c;是因为它在2018年的排名中比其他所有语言都高。Python语言赢得了3.62%的选票&#xff0c;紧随其后的是Visual Basic . net&#xff08;3.2%&#xff09;和Java&#…

.idea文件夹是做什么的_33 个 IDEA 最牛配置,写代码太爽了!

作者&#xff1a;琦彦blog.csdn.net/fly910905/article/details/778683001.设置maven1.在File->settings->搜索maven2.Mavan home directory--设置maven安装包的bin文件夹所在的位置3.User settings file--设置setting文件所在的位置4.Local repository--设置本地仓库2.I…

耳挂式蓝牙耳机原理_一种耳挂式蓝牙耳机的制作方法

本实用新型涉及蓝牙耳机技术领域&#xff0c;特别涉及一种耳挂式蓝牙耳机。背景技术&#xff1a;随着蓝牙耳机技术的使用普及&#xff0c;蓝牙耳机在智能设备以及智能通信中得到了广泛地应用。自蓝牙耳机出现以来&#xff0c;蓝牙耳机适用于各种场合&#xff0c;蓝牙耳机在佩戴…

学习C语言什么都做不了,为什么你还学?是这样吗?

对于大部分初学者&#xff0c;学习C语言的目的是希望做一名合格的程序员&#xff0c;开发出靠谱的软件来。但是学了C语言的基本语法后&#xff0c;发现只能开发“黑底白字”的DOS程序&#xff0c;完全没有漂亮的界面和生动的交互。于是学数据结构&#xff0c;学算法&#xff0c…

手动卸载_一种手动液压一体式卸载扳手

摘要一种手动液压一体式卸载扳手&#xff0c;属于煤矿井下施工设备领域&#xff0c;可解决井下回收单体柱时存在的安全隐患的问题&#xff0c;包括扳手腔体、柱塞、堵头和加长把手&#xff0c;扳手腔体包括半圆柱体Ⅰ以及与半圆柱体Ⅰ连接的半圆柱体Ⅱ&#xff0c;所述半圆柱体…

docker运行jenkins挂掉_【图文】Jenkins教程集成SonarQube

什么是SonarQube?看看维基百科的说明&#xff1a;SonarQube与CI/CD架构图SonarQube与CI/CD架构图Docker运行SonarQube简单了解之后&#xff0c;开始安装SonarQube.这里用Docker安装注&#xff1a;这里用mysql来存储SonarQube的数据&#xff0c;SonarQube7.9起已经不在支持mysq…

干货丨总结5类面试官特点和应对方法

秋招已经结束&#xff0c;年后春招即将拉开帷幕&#xff0c;想必大家都已经参加了不少面试&#xff0c;也见了不少面试官&#xff0c;俗话说知己知彼&#xff0c;百战不殆&#xff0c;推荐下面这篇文章&#xff0c;一起看看面试官可以分成哪几类以及应对的方法~ 一。虚张声势型…

去除面部黑色素小妙招_面部黑色素沉着怎么去除 推荐几个去黑色素的方法

1面部黑色素沉着怎么去除 推荐几个去黑色素的方法面部黑色素沉着怎么去除 推荐几个去黑色素的方法1、使用无刺激的美白护肤品定期做保养。2、对于色素较深且面积较小的色斑&#xff0c;可先用黑疗法(即药物点疗或激光点疗然后再配合一些能控制黑色素形成且可加速细胞新陈代谢的…

python写选择排序_如何快速掌握python选择排序算法?

对于算法&#xff0c;我们不少讲述&#xff0c;但是大部分小伙伴都希望&#xff0c;将单个算法拆分讲解&#xff0c;这样可以更加深对算法的印象&#xff0c;好了&#xff0c;本期&#xff0c;就针对选择排序算法&#xff0c;给大家讲解说明哦~感兴趣的小伙伴一起来看下吧~ 在列…

实现if_数组实现固定栈和队列+栈与队列相互实现

文章目录一、数组实现固定栈和队列1.数组实现固定栈2.数组实现固定队列二、栈与队列相互实现1.两个队列实现栈2.两个栈实现队列一、数组实现固定栈和队列1.数组实现固定栈代码如下&#xff1a;class ArrayStack { private:int *arr;int index; public:ArrayStack(int initSize)…

友情提示,你该找一份假期实习啦!

假期已至&#xff0c; 这么漫长的寒假&#xff0c; 你是否有找一份实习的打算呢&#xff1f; 是否毫无头绪和思路&#xff1f; 是找一份毫无意义的推销工作&#xff0c;传单&#xff0c;快餐店&#xff0c;电话推销等&#xff0c;还是真正找到兴趣所在&#xff0c;专业相关&…

python下拉菜单_自定义Django Form中choicefield下拉菜单选取数据库内容实例

工作中遇到的问题,自定义了一个forms.form表单,某项需要作出下拉菜单,下拉菜单中的选项需要从数据库(objectForm models)中提取. form.py为: class objectForm(forms.Form): pre choicefield(lable "工作") 最后的解决办法&#xff1a; 1.定义一个函数 def get_obj…

什么意思_invalid是什么意思

invalid是什么意思in&#xff0c;常见的表示反义的前缀&#xff0c;更多例子比如&#xff1a;indifferent, infinite, incapable, etc. valid&#xff0c;形容词&#xff0c;有根据的、让人信服的、有效的、有法律效力的。相关的单词我们还背过validity.validinvalid&#xff0…

互联网技术+非技术书单资源分享,都给泥萌!

为了更好的帮助到大家&#xff0c;从讨论区去找到对应的分享给大家 欢迎留言你想要的书资源&#xff0c;并说明理由&#xff08;比如为什么想看这本书之类的&#xff09;&#xff0c;这边有的话都会汇总~~ 深入理解java虚拟机 链接: https://pan.baidu.com/s/1wXGZnMiDKd6AKS…

createprocess失败代码2_pytest文档57单元测试代码覆盖率(pytestcov)

前言我们在做测试的时候&#xff0c;经常遇到领导的灵魂拷问&#xff1a;你的测试用例覆盖率是多少&#xff0c;达到100%了么&#xff1f;你如何保证你的测试质量&#xff1f;测试用例的覆盖率如何统计呢&#xff0c;如何知道开发的代码&#xff0c;我们都测到了&#xff0c;不…

java包名和类名可以一样吗_Java入门第三课:Java基本语法

Java基础编写Java程序时&#xff0c;应注意以下几点&#xff1a;1.大小写敏感&#xff1a;Java是大小写敏感的&#xff0c;这就意味着标识符Hello与hello是不同的。有些操作系统不区分大小写&#xff0c;不区分的话&#xff0c;文件名是不完全正确的。 然而&#xff0c;如果你的…