内存管理之内存寻址

内存寻址

  • 操作系统本身不必完全理解物理内存;
  • 理解分页单元的一般原理, 又要更好地研究内存寻址技术在其他平台是如何实现的;
  • 内存管理其实可以分成三个部分: 内存寻址, 内核给自己分配主存, 内核怎样给进程分配线性地址;

三种内存地址

  1. 逻辑地址(logical address): 包含机器语言指令中用来指定一个操作数或一条指令的地址;
    • 在分段结构中表现出来: 由一个段(segment)和偏移量(offset或displacement)组成;
  2. 线性地址(linear address): 线性地址也称为虚拟地址(virtual address)
    • 线性地址通常用十六进制数据表示, 值的方位从0x0000 0000 到 0xffff ffff
  3. 物理地址(physical address): 用于内存芯片级内存单元寻址, 他们与从微处理器的地址引脚发送到内存总线上的电信号相对应, 物理地址由32位或64位无符号整数表示;
  4. 内存控制单元: 内存控制单元(MMU)通过一种称为分段单元(segmentation unit)的硬件电路把一个逻辑地址转换成线性地址;
    • 接着, 第二个称为分页单元(paging unit)的硬件电路把线性地址转换为一个物理地址;
    • 逻辑地址 -----> 分段单元 -----> 线性地址 -----> 分页单元 -----> 物理地址;
  5. 内存仲裁器(memory arbiter)的硬件电路插在总线和每个RAM芯片之间;
  6. DMA控制器与CPU并发操作;

硬件中的分段

  • Intel微处理器以两种不同的方式执行地址转换, 这两种方式分别为: 实模式(real mode)和保护模式(protected mode), 现在主要是使用保护模式, 实模式只是为了兼容性;
段选择符和段寄存器
  • 一个逻辑地址由两部分组成: 一个段标示符(segment selector)和一个指定段内相对地址的偏移量(32位字长);
  • 段寄存器的唯一目的是存放段选择符: cs, ss, ds, es, fs和gs, 六个段寄存器用于不同的目的;
    • cs 代码段寄存器, 指向包含程序指令的段;
    • ss 栈段寄存器, 指向包含当前程序栈的段;
    • ds 数据段寄存器, 指向包含静态或全局数据段;
    • es, fs 和 gs是作一般用途的寄存器, 可指向任意的数据段;
    • cs寄存器包含一个两位的字段: 用以指明CPU的当前特权即(current privilege level, CPL)
      • 0 表示最高优先级;
      • 3 表示最低优先级;
      • Linux只用0级和3级, 分别称为内核态和用户太;
  • 段描述符: 每个段由一个8字节的段描述符(segment DEscriptor)表示, 段描述符顾名思义, 它描述了段的特征;
    • 段描述符存放在全局描述符表(global descriptor table, GDT) 或局部描述符表(Local Descriptor Table, LDT)中;
    • 每个进程除了存放在GDT中的段之外, 如果还创建了附加的段, 可以存放在自己的LDT中;
    • GDT在主存中的地址和大小存放在gdtr控制寄存器中, 当前正在被使用的LDT地址和大小放在ldtr控制寄存器中;
  • 快速访问段描述符: 逻辑地址由16位段选择符和32位偏移地址组成, 段寄存器仅仅存放段选择符;
    • 段的逻辑地址转换就可以不访问主存中的GDT或LDT, 处理器主需要直接引用存放段描述符的CPU寄存器即可, 仅当段寄存器的内容改变时, 才有必要访问GDT或LDT;
  • 分段单元: 分段单元讲一个逻辑地址转换为相对应的线性地址;
    • 先检查段选择符的TI字段, 决定段描述符保存到拿一个描述符表中;
    • 从段选择符的index字段计算段描述符的地址, index字段的值乘以8, 再与gdtr或ldtr寄存器中的内容相加;
    • 把逻辑电子的偏移量与段描述符base字段的值相加就得到线性地址;
Linux中的分段
  • 分段可以给每一个进程分配不同的线性地址空间;
  • 分页可以把同一线性地址空间映射到不同的物理空间;
  • 与分段相比, Linux更喜欢分页;
    • 当所有进程使用相同的段寄存器值时, 内存管理变得更简单, 也就是说他们能共享同样的一组线性地址;
    • RISC对段的支持很有限;
    • 用户代码段和用户数据段, 内核代码段和内核数据段;
  • Linux GDT: 单处理器只有一个GDT表, 而多处理器系统中每个CPU对应一个GDT, 放在cpu_gdt_table数组中;
  • Linux LDT: 大多数用户态下的Linux程序不使用局部描述符表, 这样内核就定义了一个缺省的LDT供大多数进程共享, 缺省的局部描述符表挡在default_ldt数组中;
硬件中的分页
  • 分页单元把线性地址(虚拟地址)转换成物理地址;
    • 其中的一个关键人物就是把所请求的访问类型与线性地址的访问权限相比较, 如果这次内存访问是无效的, 就会产生一个缺页异常;
  • 为了效率起见, 线性地址呗分成以固定长度为单位的组, 称为页(page);
    • 页内部的连续线性地址被映射成连续的物理地址;
    • 内核可以指定一个页的物理地址和其存取权限, 而不用指定页所包含的全部线性地址的存取权限;
    • 一般情况下, 页即指一组线性地址, 又包含在这组地址中的数据;
  • 分页单元把所有的RAM分成固定长度的页框(page frame, 也叫作物理页);
    • 每个页框包含一个页, 也就是说一个页框的长度与一个页的长度一致;
    • 页框是主存的一部分, 也就是一个真实的存储区域;
    • 区分一页和一个页框是很重要得事情: 页只是一个数据块(是针对虚拟地址或者是线性地址来讲的), 页框可以理解为固定大小的物理内存地址, 用于存放页中的数据块;
  • 把线性地址映射到物理地址的数据结构称为页表(page table);
    • 页表存放在主存中, 并在启用分页单元之前必须有内核对页表进行适当的初始化;
    • cr0寄存器的PG标志位启动;
常规分页
  • 32位的线性地址被分为3个域: directory(目录), table(页表), offset(偏移量);
    • 每一步都基于一种转换表, 页目录表(page directory), 页表(page table)
  • 物理地址扩展(PAE)分页机制: 页的大小可以改变;
64位系统中的分页
  • 64位系统现在一般使用四级页表结构进行分页;
  • 引入硬件高速缓存(hardware cache memory): 硬件高速缓存是基于注明的局部性原理(locality principle), 既适用于程序结构, 又适用于数据结构;
  • 高速缓存内存插在分页单元和主内存之间, 它包含一个依那件高速缓存(hardware cache memory) 和 一个高速缓存控制器(cache controller);
  • 除了通过硬件高速缓存之外, 0xx86处理器还包括了另一个称为转换后援缓冲器或TLB(translation lookaside buffer)的高速缓存用于加快线性地址的转换;
    • 每个CPU都拥有一个自己的TLB, 这叫做该CPU的本地TLB
    • CPU的cr3寄存器被修改时, 硬件自动使本地TLB中的所有项都无效;
  • 4级分页模式中的4种页表:
    • 页全局目录(page global directory, PGD);
    • 页上级目录(page upper directory, PUD);
    • 页中间目录(page middle directory, PMD);
    • 页表(page table);
  • 每个进程都有自己的页全局目录和自己的页表集;
    • 进程切换时, Linux把cr3控制寄存器的内存保存在前一个执行进程的描述符中, 然后把下一个要执行的进程描述符的值转入到cr3寄存器中, 使分页单元指向正确的表项;
  • 从0x0000 0000 到0xbfff ffff的线性地址, 无论是进程运行在用户态还是内核态, 都可以寻址; 从0xc000 0000到0xffff ffff的线性地址, 只有在内核态的进程才能寻址;
posted @ 2018-09-11 10:35  coding-for-self  阅读(650)  评论(0编辑  收藏  举报