深入理解 Linux 内核 --- 内存寻址

版权声明:本文为博主原创文章,转载请注明出处。 https://blog.csdn.net/u012319493/article/details/82954677

内存地址
在这里插入图片描述

  • 逻辑地址,段标识符(段选择符,16 位) + 段内偏移(32 位)。
  • 线性地址,也称虚拟地址,32 位无符号整数。
  • 物理地址,用于内存芯片级内存单元寻址,32 位或 36 位无符号整数。

内存仲裁器。多处理器系统中,所有 CPU 共享同一内存,内存仲裁器可保证 RAM 芯片上的读写操作必须串行执行。即便单处理系统也需要,因为 DMA 控制器可以与 CPU 并发操作。

段选择符

在这里插入图片描述
段选择符中的字段:

  • index,段描述符入口。
  • TI,Table Indicator,TI = 0,GDT 中;TI = 1,LDT 中。
  • RPL,请求者特权级。

段选择符存放在段寄存器中。段寄存器包括:

  • cs,代码段寄存器,指向包含程序指令的段。包含一个两位的字段,指明 CPU 当前特权级(Current Privilege Level,CPL)。0,内核态;3,用户态。
  • ss,栈段寄存器,指向包含当前程序栈的段。
  • ds,数据段寄存器,指向包含静态数据或全局数据的段
  • es,fs,gs,可指向任意数据的段。

段描述符

段描述符,8 字节,放在全局描述符表(Global Descriptor Table,GDT)或局部描述符表(Local Descriptor Table,LDT)。

通常只定义一个 GDT,GDT 存放在 gdtr 控制寄存器中。LDT 保存每个进程附加的段,LDT 存放在 ldtr 控制寄存器中。

段描述符中的字段:

  • Base,段首字节的线性地址。
  • G,粒度标志。
  • Limit,最后一个内存单元的偏移量。
  • S,系统标志。0,系统段。
  • Type,段的类型特征及存取权限。代码段描述符,S = 1;数据段描述符,S = 1;任务状态段描述符,进程是否正在 CPU 上运行,S = 0;局部描述符表描述符(LDTD)。
  • DPL,描述符特权级字段(Descriptor Privilege Level)。0,只有 CPL 为 0 时才能访问;3,对任何 CPL 值都可访问。
  • P,Segment-Present 标志,0 表示不在主存中。
  • D 或 B。
  • AVL。
    在这里插入图片描述

与分段相比,Linux 更喜欢分页:

  • 所有进程使用相同的段寄存器值,内存管理简单。
  • 更方便跨平台,因为 RISC 对分段支持有限。

4 个主要的 Linux 段的段描述符:用户代码段、用户数据段、内核代码段、内核数据段。

其他:任务状态段描述符(TSSD),保存处理寄存器的内容。局部描述符表描述符(LDTD),包含一个 LDT 段。

快速访问段描述符

当一个段选择符被装入段寄存器,相应的段描述符就从内存装入到附加的非编程 CPU 寄存器,段的逻辑地址转换不必访问 GDT 或 LDT。

在这里插入图片描述
分段单元:逻辑地址—>线性地址
在这里插入图片描述
分段单元执行以下操作:

  • 根据段选择符的 TI 字段,决定从 GDT(gdtr 寄存器) 还是 LDT(ldtr 寄存器) 中取段描述符。
  • 将段选择符的 index 字段乘以 8(段描述符大小),再与 gdtr 或 ldtr 寄存器中的内容相加,得到段描述符地址。
  • 逻辑地址的偏移量与段描述符的 Base 字段值相加。

有了附加的非编程 CPU 寄存器,可以省略前两步。

分页:线性地址—>物理地址

页:线性地址被分成以固定长度为单位的组。
页框:分页单元把 RAM 分成固定长度的页框,与页的长度一致。页可以存放在任何页框中。
页表:把线性地址映射到物理地址的数据结构,存放在主存中,在启用分页单元前由内核对页表初始化。

1、常规分页

32 位线性地址被分成 3 个域:

  • Directory,目录,高 10 位。
  • Table,页表,中间 10 位。
  • Offset,偏移量,最低 12 位。

线性地址的转换分两步:

  • 基于页目录表(page directory)
  • 基于页表(page table)

举例:读线性地址 0x20021406 中的字节,分页单元进行如下处理:

  • Directory 字段,即高 10 位,为 0x80,选择页目录的第 0x80 目录项,指向与该进程相关的页表。
  • Table 字段 0x21,选择页表的第 0x21 项。
  • Offset 字段 0x406,在目标页框中读偏移量为 0x406 中的字节。
  • 如果 Present 标志为 0,产生缺页中断。

在这里插入图片描述
使用二级页表可减少每个进程页表所需 RAM 的数量。

每个活动进程都有一个页目录。正在使用的页目录的物理地址存放在控制寄存器 cr3 中。

页目录项目和页表项结构相同,包含的字段有:

  • Present 标志,1,页/页表在主存中;0,不在主存中。
  • 包含页框物理地址最高 20 位的字段,页框大小为 4K,所以物理地址的最低 12 为总是 0。
  • Accessed 标志,操作系统操作。
  • Dirty 标志,只应用于页表项,对一个页框写操作时设置。
  • Read/Write 标志,存取权限,0,只读;1,可读写。
  • User/Supervisor 标志,0,只有 CPL 小于 3(内核态)时才能对页寻址;1,总能对页寻址。
  • PCD 和 PWT 标志,控制硬件高速缓存处理页或页表的方式。PCD,Page Cache Disable,访问此页框中的数据时,高速缓存功能是启用还是禁用。PWT,Page Write-Through,数据写到页框时使用回写策略还是通写策略。
  • Page Size 标志,只应用于页目录项,1,则页目录项指的是 2MB 或 4MB 的页框。
  • Global 标志,只应用于页表项,防止页从 TLB 高速缓存刷新出去。需设置 cr4 寄存器中的页全局启用(PGE)标志。

2、扩展分页

允许页框大小为 4 MB。

启用页目录项的 Page Size 标志。分页单元把 32 位线性地址分成两个字段:

  • Directory,最高 10 位。
  • Offset,其余 22 位。

设置 cr4 处理寄存器的 PSE 标志,可使扩展分页和常规分页共存。
在这里插入图片描述

3、物理地址扩展(PAE)分页机制

Inter 通过在处理器上把管脚数从 32 增加到 36,使得寻址能力达到 64 GB,需要引入新的分页机制将 32 位线性地址转换为 36 位物理地址—物理地址扩展(Physical Address Extension, PAE)机制。

设置 cr4 控制寄存器中的物理地址扩展(PAE)标志可激活 PAE。

新的分页机制:

  • 64GB RAM 被分成 2 24 ^{24} 个表项,页表项物理地址字段由 20 扩展到了 24 位。页表项大小由 32 位变为 64 位增加了一倍,使得 4KB 的页表包含的表项由 1024 将为 512。
  • 引入页表新级别:页目录指针表(Page Directory Pointer Table,PDPT),由 4 个 64 位表项组成。
  • cr3 控制寄存器包含一个 27 位的页目录指针表(PDPT)基址字段。
  • 线性地址映射到 4 KB 的页时,32 位线性地址解释方式:cr3,指向一个 PDPT;位 31-30,指向 PDPT 4个项中的一个;位 29-21,指向页目录中 512 个项中的一个;位 20-12,指向页表中 512 项中的一个;位 11-0,4KB 页中的偏移量。
  • 线性地址映射到 2 M 的页时(页目录项中的 PS 为 1):同上;位 20-0,指向 2MB 页中的偏移量。

相当于页目录项和页表项均由 10 位降为 9 位,高 2 位指向 PDPT。

4、64 位系统中的分页

采用 3/4 级分页。

硬件高速缓存

为了缩小 CPU 与 RAM 之间的速度不匹配,引入硬件高速缓存内存(hardware cache memory)。硬件高速缓存基于局部性原理。

80x86 引入行(line)的新单位,由几十个连续字节组成。高速缓存被细分为行的子集。大多数高速缓存是 N-路组关联的,主存中的任意一个行可以存放在高速缓存 N 行中的任意一行。

高速缓存单元插在分页单元和主存之间,包括一个硬件高速缓存内存和一个高速缓存控制器。高速缓存内存中存放内存中的行。高速缓存控制器存放一个表项数组,每个表项对应一个行。每个表项有一个标签,用于辨别行所映射的内存单元。内存物理地址通常分 3 组,高几位对应标签,中间几位对应高速缓存控制器的子集索引,最低几位对应行内的偏移量。

访问一个 RAM 存储单元时,CPU 取出物理地址中的子集索引号,并把子集中所有行的标签与物理地址高几位比较,如果某行的标签与物理地址的高几位相同,则 CPU 命中一个高速缓存。

高速缓存侦听:只要一个 CPU 修改了它的硬件高速缓存,它就必须检查其他硬件高速缓存是否包含相同的数据;如果是,通知其他 CPU 进行更新。由硬件处理,内核无需关心。

Linux 清除了所有页目录项和页表项中的 PCD 和 PWT 标志,即对于所有的页框都启用高速缓存,对于写操作总是采用回写策略。
在这里插入图片描述

快表(Translation Lookaside Buffer,TLB)

除了硬件高速缓存,TLB 也可以加速线性地址向物理地址转换。

一个线性地址被第一次使用时,需通过慢速访问 RAM 中的页表计算出物理地址。同时,物理地址被存放在一个 TLB 表项中,下次使用同一线性地址时就可以直接从 TLB 中取。

cr3 控制寄存器被修改时,硬件使本地 TLB 中所有项无效,因为新的一组页表被启用。

我理解的硬件高速缓存与 TLB 的区别:硬件高速缓存根据局部性原理将可能会用到的物理地址也加载进来,TLB 仅仅存放曾经用过的物理地址。

Linux 中的分页

兼容 32 、64 位系统。

从 2.6.11 版本开始,采用 4 级分页模型:

  • 页全局目录(Page Global Directory)
  • 页上级目录(Page Upper Directory)
  • 页中间目录(Page Middle Directory)
  • 页表(Page Table)

在这里插入图片描述

对于 32 位系统,如果没有启用物理地址扩展,2 级页表即可,页上级目录和页中间目录全为 0;否则,使用 3 级页表,页全局目录 <—> 80x86 的页目录指针表(PDPT),取消页上级目录,页中间目录 <—> 80x86 的页目录,页表 <—> 80x86 的页表。

对于 64 位系统,使用 3 还是 4 级分页取决于硬件对线性地址的划分。

1、物理内存布局

  • 页框 0 由 BIOS 使用,存放加电自检期间检查到的系统硬件配置。

  • 物理地址从 0x000a0000 到 0x000fffff 留给 BIOS 例程,并映射 ISA 图像卡内部内存。

  • 第一个 MB 内的其他页框标记为保留,可能由特定计算机模型保留。

  • Linux 内核从物理地址 0x00100000 开始,即第二个 MB 开始。典型的配置所得到的内核可被安装在小于 3MB 的 RAM 中。
    在这里插入图片描述

_text 对应于物理地址 0x00100000,为内核代码第一个字节的地址,_etext 为内核代码结束的位置。

内核分两组:初始化过的数据,_etext ~ edata;未初始化过的数据,e_data ~ _end

2、进程页表

进程的线性地址空间分成两部分:

  • 0x00000000 到 0xbfffffff,用户态或内核态的进程都可寻址。
  • 0xc0000000 到 0xffffffff,只有内核态的进程可以寻址。

页全局目录的第一部分表项映射的线性地址小于 0xc0000000,具体大小依赖于特定进程。剩余表项对所有进程相同。

3、内核页表

内核初始化自己的页表过程分两个阶段:

  • 内核创建一个大小为 128KB 的空间,包括内核的代码段和数据段、初始页表和用于存放动态数据的结构。
  • 对剩余 RAM 建立分页表。

RAM 可用空间 3 种情况下的内核页表:

  • < 896 MB,物理地址与虚拟地址一一对应。
  • 896 MB ~ 4 GB,动态重映射。
  • > 4 GB,非连续内存管理。

猜你喜欢

转载自blog.csdn.net/u012319493/article/details/82954677