Lec 08 物理内存管理
Lec 08 物理内存管理
0 Contents
OS的职责:管理和分配物理内存资源
- 引入虚拟内存后,物理内存分配体现在以下四个方面:
1.用户态应用出发延迟映射,此时内核需要分配物理内存页,映射到对应的虚拟页。
2.内核自己申请内存并且使用。用于内核自身的数据结构,通过kmalloc()完成。
3.发生换页时,通过磁盘来扩展物理内存的容量。
4.内核申请用于设备的DMA内存,通常需要连续的物理页。
应用出发延迟映射
-
应用调用malloc后,与物理内存是否有关?
-- 调用malloc后,返回的虚拟地址属于某个虚拟地址结构体。
-- 但是虚拟地址对应的页表项的有效位可能是0
-- 第一次访问新分配的虚拟地址时,CPU可能触发page fault -
操作系统应对页错误:
-- 找到一块空闲的物理内存页
-- 修改页表并映射物理页到触发页错误的虚拟地址所在的虚拟页
-- 恢复并重新执行。
简单的内存管理方法导致很多外部碎片
会导致资源利用率下降,分配性能下降
物理内存分配器的指标
- 资源利用率
- 分配性能
- 外部碎片和内部碎片
- 外部碎片:单个空白部分都小于分配请求的内存大小,但加起来足够。
- 注:蓝色部分为一分配内存,空白部分为未分配内存
- 内部碎片:蓝色阴影部分是分配内存大于实际使用内存而导致的内部碎片。
- 注:蓝色粗线框是已分配内存,蓝色部分表示实际使用的内存,蓝色阴影表示已经分配但是没有被使用部分。
1. 伙伴系统(buddy system)
- 采取分裂与合并避免外部碎片。
举例分配15K内存
当一个请求需要分配 m 个物理页时,伙伴系统将寻找一个大小合适的块, 该块包含 2n个物理页,且满足
合并过程如何定位伙伴块
- 高效地找到伙伴块
-- 互为伙伴的两个块的物理地址仅有一位不同
-- 而且块的大小决定是哪一位 - 例如:
-- 块A(0-8K)和块B(8-16K)互为伙伴块
-- 块A和B的物理地址分别是 0x0 和 0x2000
仅有第13位不同,块大小是8K( )
伙伴系统:以页为粒度的物理内存管理
- 分配物理页/连续物理页(
)
-- 直接映射物理页的物理地址与虚拟地址 - 资源利用率
-- 外部碎片程度降低 - 分配性能
- 分配和合并的最差时间复杂度为
, 最好时间复杂度为 。
? 真的不出现外部碎片了吗?
不是。因为伙伴系统不能合并不同阶次的页块,并且最小只能分配4K的物理内存页,因此需要更细颗粒度的分配机制。
回顾:直接映射机制
-
OS一次性将所有物理内存映射到一段虚拟地址空间
-- 任一物理地址和虚拟地址仅相差一个偏移量- OS可迅速根据物理地址或虚拟地址互相计算
-- 看上去类似最传统的段机制,实际通过配置页表来实现
- OS可迅速根据物理地址或虚拟地址互相计算
-
直接映射机制的特点
-- 内核使用直接映射的虚拟地址,不会触发page fault
-- 更方便找到连续的物理页:只要寻找连续的虚拟页 -
对内核来说:
-- 已映射的地址不一定正在使用(需要通过kmalloc才能用)
-- 正在使用的地址通常已映射(例外:vmalloc) -
对应用来说:
-- 正在使用的地址不一定已映射(on-demand paging)
-- 已映射的地址一定正在使用(否则不会被映射) -
同一个物理地址可以有多个虚拟地址
-- 如应用任一已映射的虚拟地址,在内核的直接映射下也有一个虚拟地址
伙伴系统代码实现
描述物理页的代码实现
操作系统维护struct physical_page
数组
2. SLAB/SLOB/SLUB:细粒度内存管理
内核运行中需要进行动态内存分配
-
内核自身用到的数据结构
-- 为每个进程创建的process,VMA等数据结构
-- 动态性:用时分配,用完释放,类似用户态的malloc
-- 数据结构大小往往小于页粒度 -
为什么不使用与malloc类似的机制?
-- 即先分配虚拟地址,然后通过on-demand paging分配物理页
- 没必要(内核最高权限,访问所有物理地址
- 复杂度:内核为应用处理缺页,若内核自己缺页,则处理过程中不能再次触发缺页
- 若要不触发page fault,则必须已经映射完成
SLAB:建立在伙伴系统之上的分配器
-
目标:快速分配小内存对象
-- 内核中的数据结构大小远小于4K(例如VMA) -
SLAB分配器家族:SLAB、SLUB、SLOB
-- 上世纪 90 年代,Jeff Bonwick在Solaris 2.4中首创SLAB
-- 2007年左右,Christoph Lameter在Linux中提出SLUB
-- Linux-2.6.23之后SLUB成为默认分配器
-- 发展过程中,提出针对内存稀缺场景的SLOB -
后续以主流的SLUB为例讲解
-
观察
-- 操作系统频繁分配的对象大小相对比较固定 -
基本思想
-- 从伙伴系统获得大块内存(名为slab)
-- 对每份大块内存进一步细分成固定大小的小块内存进行管理
-- 块的大小通常是 2n 个字节(一般来说, )
-- 也可为特定数据结构增加特殊大小的块,从而减小内部碎片
SLUB设计
- 只分配固定大小块
- 对于每个固定块大小,SLUB 分配器都会使用独立的内存资源池进行分配
- 采用best
SLAB设计
- 初始化资源池,将连续的物理页划分成若干等份的slot
- 空闲链表用于区分是否空闲。分配时直接分配空闲slot
- 分配N字节:
- 1.定位到大小最合适的资源池(假设只有一个slab),
- 2.从slab中取走Next_Free指向的第一个slot
- 释放:将Next_Free指针指向待释放内存(slot)
-
释放时如何找到Next_Free?
思路:根据待释放内存地址计算slab起始地址
ADDR & ~(SLAB_SIZE-1)
-
上述方法在kfree(addr)接口下可行吗?
-
没有size信息,无法判断addr是被slab分配的,还是伙伴系统分配的
-
在物理页结构中记录所属于的slab信息
-
新增slab:当某个资源池slab分配完了, 再从伙伴系统分配一个slab
- 组织多个64字节slot的SLAB?
- 引入两个指针
current
和partial
。 - 分配:
- Current指向一个slab,并从其中分配;
- 当Current slab全满,则从Partial链表中取出一个放入Current
- 释放:
- 释放后,若某个slab不再全满,则加入partial
- 释放后,若某个slab全空则可还给伙伴系统
SLAB 小结
针对每种slot大小维护两个指针:
-
current仅指向一个 slab
- 分配时使用、按需更新
-
partial指向未满slab链表
- 释放时若全free,则还给伙伴系统
-
优势:
- 减少内部碎片(可根据开发需求)
- 分配效率高(常数时间)
从伙伴系统获得的物理内存块称为slab
slab内部组织为空闲链表
slot一般选择
2.突破物理内存容量限制
换页机制(Swapping)
- 换页的基本思想
-- 用磁盘作为物理内存的补充,且对上层应用透明
-- 应用对虚拟内存的使用,不受物理内存大小限制 - 如何实现
-- 磁盘上划分专门的Swap分区,或专门的Swap文件
-- 在处理缺页异常时,触发物理内存页的换入换出
? 如何判断缺页异常是由于换页引起的
- 导致缺页异常的三种可能
-- 访问非法虚拟地址
-- 按需分配(尚未分配真正的物理页)
-- 内存页数据被换出到磁盘上 - OS如何区分?
-- 利用VMA区分是否为合法虚拟地址(合法缺页异常)
-- 利用页表项内容区分是按需分配还是需要换入
应用进程地址空间中的虚拟页可能存在四种状态,分别是:
- 未分配;
- 已分配但尚未为其分配物理页;
- 已分配且映射到物理页;
- 已分配但对应物理页被换出。
请问当应用进程访问某虚拟页时,在上述四种状态下,操作系统会分别做什么?
- 检查是否虚拟页的大小符合虚拟地址空间大小,如果符合则为其分配虚拟地址空间中的虚拟页,否则抛出page fault。然后直接读取物理内存为其分配映射及物理空间。如果物理空间为满则也会出现page fault。分配后更新TLB,进行TLB读取。读取成功后将数据写入Cache。最后从Cache中读出物理页。(TLB缺失,Cache缺失)
- 检查物理内存空间,如果有则为其分配物理空间。然后刷新TLB和cache。(TLB缺失,Cache缺失)
- 直接读取TLB和Cache中的映射和数据即可。
- 发生TLB缺失后,进入缺页处理,从磁盘中换出页表,更新TLB,更新Cache,再次读取Cache即可。
(仅供参考)
换出操作时机?
- 策略A
-- 当用完所有物理页后,再按需换出
-- 回顾:alloc_page,通过伙伴系统进行内存分配
-- 问题:当内存资源紧张时,大部分物理页分配操作都需要触发换出,造成分配时延高 - 策略B
-- 设立阈值,在空闲的物理页数量低于阈值时,操作系统择机(如系统空闲时) 换出部分页,直到空闲页数量超过阈值
-- Linux Watermark:高水位线、低水位线、 最小水位线
换页机制代价
- 优势:突破物理内存容量限制
- 劣势:缺页异常+磁盘操作导致访问延迟增加
- 如何取得平衡?
- 预取机制 (Prefetching)
-- 预测接卸来进程要使用的页,提前换入
-- 在缺页异常处理函数中,根据应用程序访存具有的空间本地性进行预取
换页机制
- 页替换策略
-- 选择一些物理页换出到磁盘
-- 猜测哪些页面应该被换出(短期内大概率不会被访问)
-- 策略实现的开销
OPT:理想换页策略
假设物理内存中可以存放三个物理页,初始为空,某应用程序一共需要访问物理页面 1~5,OPT:优先换出未来最长时间内不会再访问的页面
FIFO
操作系统维护一个队列用于记录换入内存的物理页号,每换入一个物理页就把其页号加到队尾,因此最先换进的物理页号总是处于队头位置
Belady's Anomaly
second Chance策略
FIFO 策略的一种改进版本:为每一个物理页号维护一个访问标志位。
如果访问的页面号已经处在队列中,则置上其访问标志位。
换页时查看队头:1)无标志则换出;2)有标志则去除并放入队尾,继续寻找
LRU策略
OS维护一个链表,在每次内存访问后,OS把刚刚访问的内存页调整到链表尾端;每次都选择换出位于链表头部的页面
缺点-1:对于特定的序列,效果可能非常差,如循环访问内存
缺点-2:需要排序的内存页可能非常多,导致很高的额外负载
时钟算法策略
- 精准的LRU策略难以实现
- 物理页环形排列(类似时钟)
-- 为每个物理页维护一个访问位
-- 当物理页被访问时, 把访问位设成T
-- OS依次(如顺时针)查看每个页的“访问位”- 如果是T,则置成F
- 如果是F,则驱逐该页
针臂:
- 指向换入位置
- 需要换出时:从下一个位置开始转
实现时钟算法
- 每个物理页需要有一个“访问位”
-- MMU在页表项里面为虚拟页打上“访问位”
-- 回顾:页表项中的Access Flag - 如何实现
-- OS在描述物理页的结构体里面记录页表项位置- 当物理页被填写到某张页表中时,把页表项的位置记录在元数据中(在Linux中称为“反向映射”:reverse mapping)
- 根据物理页对应的页表项中的“访问位”判断是否驱逐
- 驱逐某页时应该清空其对应的所有页表项(例如共享内存)
页替换策略小结
- 常见的替换策略
-- FIFO、LRU/MRU、时钟算法、随机替换 … - 替换策略评价标准
-- 缺页发生的概率 (参照理想但不能实现的OPT策略)
-- 策略本身的性能开销- 如何高效地记录物理页的使用情况?
-- 页表项中Access/Dirty Bits
- 如何高效地记录物理页的使用情况?
The Thrashing Problem(颠簸现象)
- 直接原因
-- 过于频繁的缺页异常(物理内存总需求过大) - 大部分 CPU 时间都被用来处理缺页异常
-- 等待缓慢的磁盘 I/O 操作
-- 仅剩小部分的时间用于执行真正有意义的工作 - 调度器造成问题加剧
-- 等待磁盘 I/O导致CPU利用率下降
-- 调度器载入更多的进程以期提高CPU利用率
-- 触发更多的缺页异常、进一步降低CPU利用率、导致连锁反应
工作集模型(有效避免thrashing)
-
一个进程在时间t的工作集W(t, x):
-- 其在时间段(t - x, t)内使用的内存页集合
-- 也被视为其在未来(下一个x时间内)会访问的页集合
-- 如果希望进程能够顺利进展,则需要将该集合保持在内存中 -
工作集模型:All-or-nothing
-- 进程工作集要么都在内存中,要么全都换出
-- 避免thrashing,提高系统整体性能表现
跟踪工作集w(t,x)
- 工作集时钟中断固定间隔发生,处理函数扫描内存页
-- 若访问位为1,则表示此次tick中被访问- 记录上次使用时间为当前时间
-- 若访问位为0,则表示此次tick中未访问 - Age = 当前时间 – 上次使用时间
- 若Age大于设置的x,则不在工作集
-- 最后,OS将所有访问位清0 - 注意:访问位由CPU在访问时设为1
- 记录上次使用时间为当前时间
换页
- 换出操作:物理内存不够时
-- OS选择不常用的物理内存(不同的选择策略)
-- OS将内存中的数据写入磁盘块,并记录磁盘块与内存的关联
-- OS更新页表,将对应页表项的valid bit设置为0 - 换入操作:当换出的页被访问时,触发page fault
-- OS判断该地址所在页被换出,找到对应的磁盘块
-- OS分配空闲的物理内存页;若没有空闲页,则再次进行换出操作
-- OS将磁盘块中的数据读入前一步找到的内存页
-- OS更新页表,将对应页表项的valid bit设置为1
DMA内存(Direct Memory Access)
- DMA:设备绕过CPU直接访存
-- 由于绕过CPU的MMU,因此直接访问物理地址
-- 通常需要大段连续的物理内存 - 操作系统必须有能力分配连续的物理页
-- 需要用一种高效的方式来组织和管理物理页
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了