Overview
- 回过头来看这篇。来个总结:
- linux在内存管理上主要是两个概念(抽象):
- 分页paging:最主要的贡献是,运行物理内存不连续分配,但是在用户层面不可感知(仍然认为自己拥有独立且连续的可用内存空间) --> 通过分页+页表来实现
- 虚拟内存:允许可使用的内存比实际物理内存大。 --> 通过磁盘 + 页面置换来实现。
- 优点:
- 内存映射:paging之后,可以直接映射磁盘上的文件(可执行文件or动态库)到虚拟地址空间,从而做到物理内存的延时分配,只有在需要读相应文件时,才真正从磁盘加载到内存,而在内存紧张时,又清空。(这一切对应用程序都是透明的。)
- 共享内存:比如动态库,只需要在内存中存储一部分即可,然后将其映射到不同进程的虚拟地址空间中。[进程间的内存共享也可以这样实现。]
- linux在内存管理上主要是两个概念(抽象):
- 内存管理basic
- 内存管理
- 基本概念
- 交换
- 连续内存(分配)
- 分页
- 虚拟内存
- 按需调页
- 写时复制
- 页面置换
- 内存管理
- 优化
Memory Management Basic
内存管理
Background
- 基本硬件
- CPU内置寄存器:访问快,CPU可以在一个时钟周期内解析并执行多个指令
- 内存:CPU通常需要暂停,访问内存慢
- 高速缓存cache:缓解寄存器与内存之间的速度差异。
- 进程访问:需要确保每个进程都有独立的内存空间。
- 内存空间的保护:
- 通过基地址寄存器和界限地址寄存器来保护。
- 特权指令只可在内核模式下执行,所以用户程序不能更改上述寄存器值。
- 内存空间的保护:
- 地址绑定
- 源程序中地址是符号地址表示的(如count)
- 编译器通常将这些符号地址绑定到可重定位的地址(如“本模块开始的第14字节”)
- 链接程序或加载程序再将这些可重定位地址绑定成绝对地址(如74014)。
- 动态加载dynamic loading
- 一个子程序都以可重定位的形式保存在磁盘上,只有在调用时才被加载。
- 为了获得更好的内存空间使用率(不用的子程序决不会被加载)
- 动态链接dynamic linking
- 二进制镜像中对每个库程序的引用都有一个存根(sub),存根是一小段代码,用来指出如何定位适当的内存驻留库程序,或如果该程序不在内存时应如何装入库。
- 可用于库更新,一个库可以被新的版本所替代,而不需要重新链接。
交换
- 进程可以暂时从内存中交换(swap)到备份存储(通常是快速磁盘)上。
- 如果一个程序的绑定是在汇编or加载时就确定的,那么该进程需要交换回它原来所占有的内存空间。
内存分配
- 内存通常分为两个区域:
- 一个用于驻留操作系统
- 一个用于用户进程
- 为进程分配内存
- 多分区方法:最简单的方法是将内存分为多个固定大小的分区,每个分区只能容纳一个进程。
- 可变分区(variable partition):
- os有一个表记录哪些内存可用,哪些内存已被占用。
- 任何时候,有一组可用孔(hole)大小列表和输入队列(进程)。
- 如何选择一个孔来分配:
- 首次适应(first-fit): 分配第一个足够大的孔
- 最佳适应(best-fit): 分配最小的孔
- 最差适应(worst-fit): 分配最大的孔
- 外部碎片问题(external fragmentation):
- 外部碎片 vs 内部碎片:
- 如果孔太小,那么维护一个小孔的开销比孔本身大得多
- --> 因此通常将内存以固定大小的块为单元(而不是字节)来分配
- --> 给进程分配的内存 减去 进程申请的 = 内部碎片
- 而外部碎片显然就是进程之间的小碎片
- solution:
- 紧缩(compaction):
- 解决外部碎片的方法
- 移动内存内容,以便所有空闲空间合并成一整块。
- 但紧缩并非总是可能的:仅在重定位是动态并在运行时可采用
- 允许物理地址空间非连续:
- 分页
- 分段
- 紧缩(compaction):
- 外部碎片 vs 内部碎片:
分页
- 分页paging内存管理方案
- 目的:允许进程的物理地址空间是可以非连续的
- 实现方法:
- 基本方法是涉及将物理内存分为固定大小的块,称为帧frame。而将逻辑内存也分为同样大小的块,称为页page。
- 当需要执行进程时,其页从备份存储中调入到可用内存帧中。
- 页表包含每页所在的物理内存的基地址。(由基地址与页偏移组合形成物理地址)。
- --> 用户视角的内存和实际的物理内存的分离。
- 页大小
- 由硬件决定
- 通常为2的幂 --> 这样可以方便地将逻辑地址转换为页号和页偏移。如果逻辑地址空间为2^m,页大小为2^n单元(字节or字),那么逻辑地址的高m-n未表示页号,低n位表示页偏移。
- 从512B~16MB不等
- 碎片问题:采用分页技术不会产生外部碎片,但会产生内部碎片。
- 硬件支持:
- 通常会为每个进程分配一个页表,页表的指针与其他寄存器的值(例如PC)一起存入进程控制块。
- 页表的硬件实现:
- 将页表作为一组专用寄存器来实现。 (maybe不适用于页表很大的场景)
- --> 因为对内存的每次访问都需要经过分页表,因此效率很重要。
- 大多数当代计算机都允许页表非常大,因此需要将页表放在内存中,并将页表基寄存器(page-table base register, PTBR)指向页表。
- --> 在这种场景下,访问用户内存位置需要多一些时间:访问一个字节需要两次内存访问,从而速度减半。
- --> 改进是采用小但专用且快速的硬件缓存:转换缓冲区(translation look-aside buffer,TLB)。[本质上就是缓存,所以可能出现TLB失效。]
虚拟内存
- 虚拟内存技术允许执行进程不必完全在内存中
- --> 优点:程序可以比物理内存大。
背景
- 程序有一些几乎不执行的代码:比如处理异常错误的代码
- 数组、链表和表通常分配了比实际所需更多的内存
- 程序的某些功能很少使用
实现:按需调页
- 按需调页demand paging:在需要时才调入相应的页。
- “定义”需要的时候:
- 懒惰交换lazy swapper
- pager调页程序:
- 当换入进程时,pager推测在该进程再次换出之前会用到哪些页
写时复制
- 通过采用类似页面共享的技术,采用系统调用fork创建进程的开始阶段可能不需要按需调页。
- 提供了快速进程创建,且最小化新建进程必须分配的新页面的数量
- 采用写时复制copy-on-write技术:运行父子进程开始时共享同一页面。
- 这些页面标记为写时复制页。
页面置换
- 页面置换page replacement
- 基本页置换
- 基本方法:如果没有空闲帧,那么就查找当前没有使用的帧,并将其释放。
- 如何释放:将其内容写到交换空间,并改变页表,以表示该页不在内存中。
- FIFO页置换
- 为每个页记录着该页调入内存的时间
- 最优置换
- 置换最长时间不会使用的页
- 难以实现,因为需要用到引用串的未来之食
- LRU置换
- least recently used
- 近似LRU置换
优化
- 有了分页 + 物理内存,就满足了正常工作的基本条件。但还有一些使内存访问更高效的optimization。
虚拟地址: 物理地址
MMU
- MMU(Memory Management Unit)是CPU的一个用来将进程的虚拟地址转换成物理地址的模块。
- 将虚拟地址转换成物理地址的速度直接影响着系统的速度,所以CPU包含了这个模块用来加速。
TLB
- TLB(Translation Lookaside Buffer)
- 上述MMU的输入是page table,而page table又存储在内存中,跟CPU的cache相比,内存的速度很慢。
- 所以TLB是用来加速虚拟地址到物理地址的转换的。
- TLB存在于CPU的L1 cache内,用来缓存已经找到的映射。
huge pages
- 由于CPU的cache有限,所以TLB内缓存的数据也有限,而采用了huge pages后,由于每页的内存变大,TLB记录数不变却能覆盖更大的地址空间。
- 同时也能减少调用MMU的次数,加快了地址转换。
内存大小
- 由于实际情况下,物理内存要比虚拟内存小很多,所以os必须很小心的分配物理内存,以最大化内存利用率。
- 按需分配物理页
- 交换空间
- 发生场景:实际物理内存不够时。
- 使用场景:
- 被修改过的磁盘文件。[如果物理内存里面的数据来自于磁盘上的文件,那么内核将直接将该部分数据从内存中移除掉来释放出更多的内存,当下次有进程需要访问这部分数据时,再将它从磁盘上加载到内存中来。]
- --> 但是,如果这部分数据被修改过且没被写入文件,那这部分数据就变成了脏数据,脏数据不能被直接删掉,只能被移动到交换空间上去。
- 对于anonymous的内存数据,在磁盘上没有对应的文件,这部分数据不能直接被删除,而是被系统移到交换空间上去。
- --> 注意,可执行文件和动态库文件不会被修改,但通过mmap+private的方式映射到内存的磁盘文件有可能被修改,这种方式映射的内存比较特殊,没修改之前是file backed,修改后但没有写回磁盘之前就变成了anonymous的。
- 总结:交换空间就是磁盘上预留的一块特殊空间,被系统用来临时存放内存中不常被访问的数据,当下次有进程需要访问交换空间上的数据时,系统再将数据加载到内存中。由于交换空间在磁盘上,所以访问速度要比内存慢很多,频繁的读写交换空间会带来性能问题。
Summary
- 内存的使用过程:
- 进程发出内存申请请求
- 系统会检查进程的虚拟地址空间是否还有剩余,若有,给进程分配虚拟地址
- 系统为这块虚拟地址创建相应的memory mapping(可能多个),并将它放进该进程的page table。
- 系统返回虚拟地址给进程,进程开始访问该虚拟地址
- CPU根据虚拟地址在该进程的page table中找到相应的memory mapping,若该mapping没和物理内存关联,则会产生缺页中断。 [按需分配]
- os收到缺页中断后,分配真正的物理内存并将它关联到相应的memory mapping。
- 中断完成后,CPU就可以访问该内存了。
- --> 缺页中断不是每次都会发生,只有系统觉得有必要延迟分配内存的时候才发生。否则在创建memory mapping的时候会直接分配物理内存并关联。
满地都是六便士,她却抬头看见了月亮。
浙公网安备 33010602011771号