x86 系列 CPU 内存寻址模式总结

by adie 
June 23, 2013, 6:12 p.m.

说明:  

   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. 内存的二级分页结构

x86 系列 CPU 内存寻址模式总结_第1张图片 


        2. 4KB 页面寻址过程

x86 系列 CPU 内存寻址模式总结_第2张图片

         3. 4MB 页面寻址过程

x86 系列 CPU 内存寻址模式总结_第3张图片



五. 虚拟 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 模式的切换

x86 系列 CPU 内存寻址模式总结_第4张图片

 

六. 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 页面的寻址

     x86 系列 CPU 内存寻址模式总结_第5张图片

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

x86 系列 CPU 内存寻址模式总结_第6张图片

 

八. 长模式 (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 页面的转换.

x86 系列 CPU 内存寻址模式总结_第7张图片

from: http://www.adintr.com/article/blog/298

你可能感兴趣的:(x86 系列 CPU 内存寻址模式总结)