内存寻址
本部分主要记录内存寻址这部分内容:
- CPU与Mem之间是如何连接的?
- 程序使用内存(virtual mem)与物理内存之间经过怎样的转换?
- 逻辑地址到线性地址的转换;
- 线性地址到物理地址的转换;
- Cache和MMU的作用
- user空间和kernel空间的内存分配;
文章目录
1. 框图结构
-
SOC与DDR之间连接
CPU取对应地址中的数据,地址经过MMU的解析:
- 判断在cache中,如果在则直接取回去;(PS:此图中Cache存储为物理地址,也存在cache存储逻辑地址,则不需要先经过MMU解析)
- 判断不再Cache中,则继续从物理内存中取数据,并将其存储一份到cache中;
-
CPU MMU cache之间具体的逻辑
2. 概念理解
地址相关概念:
- 虚拟地址:程序使用的地址virtual address,对于user层的程序而言,都认为自己占据了完整的4G空间,所以需要做对应转换;
- 线性地址:经过段转换后的连续地址,与物理地址连续对应,linux没有用到这部分物理结构,将所有段的基地址都设置为0,这样virtual addr实质上就与线性地址相等了;
- 物理地址:在DDR中存储的位置;
计算机系统相关概念:
- CPU设计根据指令和data的区分与否划分为两个结构:冯诺依曼结构和哈佛结构,目前使用大多是哈佛结构+冯诺依曼的改进体;
- 计算机中的指令集结构:区分为CISI和RISI,基于这两种结构各厂商设计出自己的指令集和HW设计
- CISI有Intel 、AMD-X86,复杂指令集,性能更快,但是很多使用比较少的指令也使用电炉实现,则造价以及功耗方面受影响;
- RISI有 IBM(PowerPC)、ARM(ARM架构),精简指令集,使用频次比较少的指令通过其他指令组合形成,则更加
- MMU:Memory Manager Unit,用于将待访问的逻辑地址找到对应的物理地址
- CPU通过分段电路,一种硬件电路将逻辑地址转换为线性地址,再通过分页单元的硬件单元把线性地址转换为物理地址;
- 用于权限控制,对于不具有权限访问的进程如果访问关键数据,则会返回exception;
- Cache:高速缓存区
- 局部性原理:
- 一般来讲地址访问具有连续性,即相邻的数据在接下来要用到的可能性比较大;
- 在一段时间内,该块内存被访问的概率hi比较大;
- 一般采用SRAM实现,读写速度相对于DRAM更快一些;
- CPU的clk基本都以G为单位,而dram数据频率基本以M为单位,不过其实相差不太大,但是由于涉及多个HW模块,所以这部分还是比较耗时的;
- 局部性原理:
3. 逻辑地址–>线性地址
核心处理就是这张图:
进行简要说明:每个逻辑地址由16位的段选择符+32位的偏移量组成;
- 从段寄存器中读出段选择符,段选择符包含三个内容:
- 索引
- 索引表是GDT还是LDT
- 特权等级
- 在全局描述符表中根据index找到所述段;
- 根据所属段,在段描述符表中找到对应的段起始线性地址;
则经过上述过程,可以将逻辑地址 + base 转换为线性地址,不过由于linux中没有实质分段,所以基地址都是相同的值,则其实逻辑地址与线性地址只是经过了一个简单的偏移;
段描述符表:
- GDT(Global Descriptor Table):全局描述表;
- LDT(Local Descriptor Table):局部描述表;
总结:
- 分段过程将逻辑地址转换为线性地址;
- 分段过程中设置了特权等级,使没有权限访问的地址范围,进入exception mode;
- linux实质没有划分不同的段base地址,所以只使用了分段机制的权限管控功能;
各个段的定义:/kernel-4.9/arch/m32r/include/asm/segment.h
4. 线性地址–>物理地址
分页部分的核心功能是将线性地址转换为物理地址,抽象出来一段HW无关的线性地址给到Task使用;
将固定大小的地址范围,称之为一页,每次映射一页到物理地址,这样更好管理,效率也更高;(类似于小组划分这种)
常规分页方式,或者叫做线性地址:dicrectory(10位)、Table(10位)、offset(12位),刚好4G大小;
如下图:
建立了分页的机制后,我们每个Task都分配4G空间,但是这些空间一定是没有都映射到物理地址的,要不然怎么够用:
- 在Task实际运行的时候发现该地址还没映射(即页目录表设置为0,就代表还没有映射)则触发缺页异常,申请页面;
- 怎样防止进程访问不属于自己的线性地址(如内核空间)或无效的地址呢?内核里记录着每个进程能访问的线性地址范围(进程的vm_area_struct 线性区链表和红黑树里存放着),在引发缺页异常的时候,如果内核检查到引发缺页的线性地址不在进程的线性地址范围内,就发出SIGSEGV信号,进程结束,我们将看到程序员最讨厌看到的Segmentation fault。
- 分页机制(线性地址)让物理地址的复用,更加系统化的管理;
linux中的普通分页模型,目前了解到的嵌入式相关的操作都是基于linux系统的,所以这部分也需要着重了解下:
采用4级页表,目前的通用模型
* 页全局目录:PGD
* 页上级目录:PUD
* 页中间目录:PMD
* 页表:PT
对于32bit系统,PUD和PMD中全是0,也就是说只使用两级页表就已经足够(有一个标志位);
5. layout情况
32bit和64bit是指CPU寄存器的位宽,而非data pin或者addr pin的位宽
- 当物理内存不足4GB的时候(比如1GB):
- kernel 映射 0~896MB,则kernel可以正常访问DMA区域和Normal区域
- 剩余128M作为High Mem,给到user space申请使用,这部分需要在使用完成后立即释放其映射关系给到其他使用;
物理内存在4GB以内的情况下,均采用上述处理方式;
- 物理内存超过4GB,则32根地址线无法访问完全;
5.1 ZONE定义
字段名 | 说明 |
---|---|
ZONE_DMA | 低于16MB的内存页框 |
ZONE_NORMAL | 高于16MB但低于896MB的内存页框 |
ZONE_HIGHMEM | 高于896MB的内存页框 |
5.2 定义:
- 查看官方文档:Documentation/arm64/memory.txt
- memory.h kernel和userspace的区分:PAGE_OFFSET 目前定义为:0x80000000 = 2G
- page.h这里有很多定义可以使用:
- 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. 附录
-
CPU内部取指令过程(这东西计算机组成原理讲的比较细):
截取自:https://wenku.baidu.com/view/46350e504b35eefdc9d3335c.html