提高物理内存的使用效率->虚存。
- 动态内存分配
- 页表的虚实内存映射机制
- 简化编译器对地址空间的设置
- 加强应用之间和内核之间的内存隔离
- 空分复用
应用层面来看地址都是从0开始,也就是应用程序的linker.ld起始地址都是0x10000,而os通过页表加载到不同的物理地址中,程序员不用考虑访问地址。
动态分配的内存区域就是heap,由os管理。
- 初始化的时候提供一大块内存空间
- 提供分配和释放的接口
- 管理空闲块
静态分配
编译期间就知道变量的大小,分配了固定内存用来存储。
动态分配
应用有一个大小随着运行增减的heap空间
内存碎片
- 内碎片:已经分配出去,没用满
- 外碎片:没分,但是太小了分不出去
指针
- 裸指针 *const T/*mut T,和C的指针一样,本身的数值就是地址,不安全,编译器只能保证他不能修改只读的数据
- 引用&T/&mut T,是一个地址范围,但是检查比较严格,也就是所有权模型
- 智能指针,胖指针,除了地址范围,还有额外信息,能起到控制和管理作用
相关智能指针
- Box
<T>
创建时在堆上分配一个类型为T的变量,自身也保存在这个变量的位置,当他被回收的时候,变量的空间也就没了。 - Rc
<T>
是单线程用的引用技术类型,使多个指针指向同一变量,都可以拿到不可变引用,计数为0后都被回收。Arc是多线程用的。 - RefCell
<T>
,借用检查在运行时进行。实现内部可变性。 - Mutex
<T>
是一个互斥锁,在多线程中使用,内部可变性,同步互斥。
用alloc库定义的接口来实现动态内存分配器。alloc需要os提供一个全局的动态内存分配器,这个分配器要实现规定的GlobalAlloc Trait,包括分配和回收两个接口,它将利用这个分配器管理堆空间,使得容器可用。
这里直接使用已有的分配器,进行全局静态实例化分配器,然后分配一段heap空间,用分配器管理这段空间。
os只是代为初始化这个heap空间为0,实际上它属于没有被初始化的全局数据。所以分配在.bss段中。
硬件:内存保护/映射/地址转换
os:提供统一的虚存访问接口
CPU访问的是虚拟地址,通过MMU+页表进行地址转换,找到物理地址。
os中建立虚实地址空间映射机制。
地址空间设计目的
- 开发者不用关系
- 不要太大开销
- 安全
虚拟地址需要MMU内存管理单元支持。
内核要提供支持,使得MMU对于不同应用、相同的虚拟地址映射到正确的物理地址。
实现方法大全:
分段管理
- 一个app空间是bound,物理地址起始是base,内核负责切换base。内存块的占用状态用位表表示。
- 产生内碎片,给stack、heap预留了空间,可能无法充分利用。
- 为解决内碎片,就使用段来管理,段大小是不同的,则需要不同的base-bound配对每个段,一个任务就有多个base-bound。
- 解决了内碎片问题,还有外碎片问题
分页管理
页面大小固定,一个app一个页表,对应虚拟页号和物理页号。
虚拟地址 = 虚拟页号 + 偏移
物理地址 = get value(虚拟页号) + 偏移
权限:rwx 限制了app对得到的物理地址的应用方式 读/写/取指令
rv64提供了SV39的多级页表硬件机制,先了解硬件,再完成软件对应的实现。
虚拟地址和物理地址
MMU没启动,访问的地址都是物理地址。satp这个CSR可以启动分页模型,如何S和U的访问地址都作为虚拟地址,去MMU转成物理地址。M级是物理地址。
satp的字段:
- MODE 使用的页表实现方式
- ASID 地址空间标识符
- PPN 根页表所在的物理页号 (不同应用对应的页表的搜索入口,切换任务的时候也要切换这个,并且要刷新快表TLB)
当MODE 设置为 0 的时候,代表所有访存都被视为物理地址;而设置为 8 的时候,SV39 分页机制被启用,所有 S/U 特权级的访存被视为一个 39 位的虚拟地址,它们需要先经过 MMU 的地址转换流程,如果顺利的话,则会变成一个 56 位的物理地址来访问物理内存;否则则会触发异常,这体现了分页机制的内存保护能力
实际同时usize,通过封装。实现trait,进行相互生成和转换。
[derive(Copy, Clone)] //能让下面的结构体自动实现深拷贝
虚存地址:39 = 27 + 12
物理地址:56 = 44 +12
页表项: 64 = 10 + 44(ppn) + 2 + 8(pte标志)
线性表的开销太大,使用字典树的思想,构造多级页表。SV39是三级页表实现的。
物理内存范围 0x80000000 - 0x80800000