内存管理学习之内存寻址

内存寻址

本部分主要记录内存寻址这部分内容:

  1. CPU与Mem之间是如何连接的?
  2. 程序使用内存(virtual mem)与物理内存之间经过怎样的转换?
    1. 逻辑地址到线性地址的转换;
    2. 线性地址到物理地址的转换;
  3. Cache和MMU的作用
  4. user空间和kernel空间的内存分配;

1. 框图结构

  1. SOC与DDR之间连接
    内存管理学习之内存寻址

    CPU取对应地址中的数据,地址经过MMU的解析:

    1. 判断在cache中,如果在则直接取回去;(PS:此图中Cache存储为物理地址,也存在cache存储逻辑地址,则不需要先经过MMU解析)
    2. 判断不再Cache中,则继续从物理内存中取数据,并将其存储一份到cache中;
  2. CPU MMU cache之间具体的逻辑
    内存管理学习之内存寻址

2. 概念理解

地址相关概念:

  1. 虚拟地址:程序使用的地址virtual address,对于user层的程序而言,都认为自己占据了完整的4G空间,所以需要做对应转换;
  2. 线性地址:经过段转换后的连续地址,与物理地址连续对应,linux没有用到这部分物理结构,将所有段的基地址都设置为0,这样virtual addr实质上就与线性地址相等了;
  3. 物理地址:在DDR中存储的位置;

计算机系统相关概念:

  1. CPU设计根据指令和data的区分与否划分为两个结构:冯诺依曼结构和哈佛结构,目前使用大多是哈佛结构+冯诺依曼的改进体;
  2. 计算机中的指令集结构:区分为CISI和RISI,基于这两种结构各厂商设计出自己的指令集和HW设计
    1. CISI有Intel 、AMD-X86,复杂指令集,性能更快,但是很多使用比较少的指令也使用电炉实现,则造价以及功耗方面受影响;
    2. RISI有 IBM(PowerPC)、ARM(ARM架构),精简指令集,使用频次比较少的指令通过其他指令组合形成,则更加
  3. MMU:Memory Manager Unit,用于将待访问的逻辑地址找到对应的物理地址
    1. CPU通过分段电路,一种硬件电路将逻辑地址转换为线性地址,再通过分页单元的硬件单元把线性地址转换为物理地址;
    2. 用于权限控制,对于不具有权限访问的进程如果访问关键数据,则会返回exception;
  4. Cache:高速缓存区
    1. 局部性原理:
      1. 一般来讲地址访问具有连续性,即相邻的数据在接下来要用到的可能性比较大;
      2. 在一段时间内,该块内存被访问的概率hi比较大;
    2. 一般采用SRAM实现,读写速度相对于DRAM更快一些;
      1. CPU的clk基本都以G为单位,而dram数据频率基本以M为单位,不过其实相差不太大,但是由于涉及多个HW模块,所以这部分还是比较耗时的;

3. 逻辑地址–>线性地址

核心处理就是这张图:
内存管理学习之内存寻址

进行简要说明:每个逻辑地址由16位的段选择符+32位的偏移量组成;

  1. 从段寄存器中读出段选择符,段选择符包含三个内容:
    1. 索引
    2. 索引表是GDT还是LDT
    3. 特权等级
  2. 在全局描述符表中根据index找到所述段;
  3. 根据所属段,在段描述符表中找到对应的段起始线性地址;
    则经过上述过程,可以将逻辑地址 + base 转换为线性地址,不过由于linux中没有实质分段,所以基地址都是相同的值,则其实逻辑地址与线性地址只是经过了一个简单的偏移;

段描述符表:

  1. GDT(Global Descriptor Table):全局描述表;
  2. LDT(Local Descriptor Table):局部描述表;

总结:

  1. 分段过程将逻辑地址转换为线性地址;
  2. 分段过程中设置了特权等级,使没有权限访问的地址范围,进入exception mode;
  3. linux实质没有划分不同的段base地址,所以只使用了分段机制的权限管控功能;

各个段的定义:/kernel-4.9/arch/m32r/include/asm/segment.h

4. 线性地址–>物理地址

分页部分的核心功能是将线性地址转换为物理地址,抽象出来一段HW无关的线性地址给到Task使用;
将固定大小的地址范围,称之为一页,每次映射一页到物理地址,这样更好管理,效率也更高;(类似于小组划分这种)
常规分页方式,或者叫做线性地址:dicrectory(10位)、Table(10位)、offset(12位),刚好4G大小;
如下图:
内存管理学习之内存寻址

建立了分页的机制后,我们每个Task都分配4G空间,但是这些空间一定是没有都映射到物理地址的,要不然怎么够用:

  1. 在Task实际运行的时候发现该地址还没映射(即页目录表设置为0,就代表还没有映射)则触发缺页异常,申请页面;
  2. 怎样防止进程访问不属于自己的线性地址(如内核空间)或无效的地址呢?内核里记录着每个进程能访问的线性地址范围(进程的vm_area_struct 线性区链表和红黑树里存放着),在引发缺页异常的时候,如果内核检查到引发缺页的线性地址不在进程的线性地址范围内,就发出SIGSEGV信号,进程结束,我们将看到程序员最讨厌看到的Segmentation fault。
  3. 分页机制(线性地址)让物理地址的复用,更加系统化的管理;

linux中的普通分页模型,目前了解到的嵌入式相关的操作都是基于linux系统的,所以这部分也需要着重了解下:
采用4级页表,目前的通用模型
* 页全局目录:PGD
* 页上级目录:PUD
* 页中间目录:PMD
* 页表:PT
对于32bit系统,PUD和PMD中全是0,也就是说只使用两级页表就已经足够(有一个标志位);

5. layout情况

32bit和64bit是指CPU寄存器的位宽,而非data pin或者addr pin的位宽

  1. 当物理内存不足4GB的时候(比如1GB):
    1. kernel 映射 0~896MB,则kernel可以正常访问DMA区域和Normal区域
    2. 剩余128M作为High Mem,给到user space申请使用,这部分需要在使用完成后立即释放其映射关系给到其他使用;
      物理内存在4GB以内的情况下,均采用上述处理方式;
  2. 物理内存超过4GB,则32根地址线无法访问完全;

5.1 ZONE定义

字段名 说明
ZONE_DMA 低于16MB的内存页框
ZONE_NORMAL 高于16MB但低于896MB的内存页框
ZONE_HIGHMEM 高于896MB的内存页框

5.2 定义:

  1. 查看官方文档:Documentation/arm64/memory.txt
  2. memory.h kernel和userspace的区分:PAGE_OFFSET 目前定义为:0x80000000 = 2G
  3. page.h这里有很多定义可以使用:
    1. PAGE_SHIFT 根据我们定义的PAGE_SIZE来决策的:CONFIG_ARM64_4K_PAGES=y,一般来说还都是默认4K

5.3 virtual地址

文档中定义4K页表(3 level)的虚拟地址范围:
AArch64 Linux memory layout with 4KB pages + 3 levels:

Start End Size Use
0000000000000000 0000007fffffffff 512GB user
ffffff8000000000 ffffffffffffffff 512GB kernel

在mem_init中打印:

[ 0.000000] [N]Virtual kernel memory layout:
[ 0.000000] [N] modules : 0xffffff8000000000 - 0xffffff8008000000 ( 128 MB)
[ 0.000000] [N] vmalloc : 0xffffff8008000000 - 0xffffffbebfff0000 ( 250 GB)
[ 0.000000] [N] .text : 0xffffff8008080000 - 0xffffff8008b00000 ( 10752 KB)
[ 0.000000] [N] .rodata : 0xffffff8008b00000 - 0xffffff8008f60000 ( 4480 KB)
[ 0.000000] [N] .init : 0xffffff8008f60000 - 0xffffff8009210000 ( 2752 KB)
[ 0.000000] [N] .data : 0xffffff8009210000 - 0xffffff8009331808 ( 1159 KB)
[ 0.000000] [N] .bss : 0xffffff8009331808 - 0xffffff800a063130 ( 13511 KB)
[ 0.000000] [N] fixed : 0xffffffbefe7fb000 - 0xffffffbefec00000 ( 4116 KB)
[ 0.000000] [N] PCI I/O : 0xffffffbefee00000 - 0xffffffbeffe00000 ( 16 MB)
[ 0.000000] [N] vmemmap : 0xffffffbf00000000 - 0xffffffc000000000 ( 4 GB maximum)
[ 0.000000] [N] 0xffffffbf00000000 - 0xffffffbf02000000 ( 32 MB actual)
[ 0.000000] [N] memory : 0xffffffc000000000 - 0xffffffc080000000 ( 2048 MB)
[ 0.000000] [I]SLUB: HWalign=64, Order=0-3, MinObjects=0, CPUs=4, Nodes=1

5.4 physical地址

在lk platform rules.mk
KERNEL_BASE := fffffff800000000 kernel物理地址起始
MEMBASE := 0x800000000 物理地址起始,如果2G内存,则0x880000000为最终地址
KERNEL_LOAD_OFFSET := 0xF100000

在upgrade.h中定义KERNEL_LOAD_ADDR_PHYS:0x800080000,我们实际load img 的起始位置,即kernel image存放位置

6. 附录

  1. CPU内部取指令过程(这东西计算机组成原理讲的比较细):
    内存管理学习之内存寻址
    内存管理学习之内存寻址

    截取自:https://wenku.baidu.com/view/46350e504b35eefdc9d3335c.html