虚存设计关键问题
虚存设计
- 设计目标
- 保护
- 隔离进程之间的错误
- 虚拟化
- 用磁盘来扩展物理内存的容量
- 方便用户进行编程(进程地址空间从0-max)
- 保护
- 虚存设计时要考虑的问题
- 虚实映射
- 映射机制:分段/分页
- 映射加速:TLB/TLB Miss/TLB表项数目
- 映射开销:页大小和页表大小
- 按需加载和页替换
- 缺页处理
- 页替换:替换算法/颠簸/pin和unpin
- 后备存储:Swap
- 虚存使用
- 清零
- Copy on Write
- 共享内存
- 虚实映射
虚存设计问题 —— 虚实映射设计问题
页大小
- 大页
- 好处:页表小 且 磁盘I/O高效
- 不足:内部碎片
- 进程执行不需要的部分也放进了内存
- 页可能不满,存在浪费
- 小页
- 好处:减少内部碎片
- 不足:页表大
- 占用更多的内存
- 加载时间更长
- 查找虚页的时间更长
- 不足:磁盘I/O不够高效
- 进程加载、页替换
虚存设计问题 —— 页替换设计问题
颠簸
- 颠簸的概念
- 频繁发生缺页,运行速度很慢
- 进程被阻塞,等待页从磁盘取进内存
- 从磁盘中读入一页10ms,执行一条指令几ns
- 虚存访问时间
- 请求平均访问时间 = h*内存访问时间 + (1-h)*磁盘访问时间(h:命中率)
- 例如:假设内存访问时间 = 100ns,磁盘访问时间 = 10ms,h=0.9
- 请求平均访问时间 = 0.9*100ns + 0.1*10ms = 1.0009ms
- 颠簸的原因
- 进程的工作集 > 可用的物理内存
- 进程过多,即使单个进程都小于内存
- 内存没有被很好地回收利用
- 如何避免
- 多进程时,哪些工作集放进内存
- 多进程时,如何分配页
多进程时,哪些工作集放进内存
- 将进程分为两组
- 活跃组:工作集加载进内存
- 不活跃组:工作集不加载进内存
- 如果确定哪些进程是不活跃的
- 等待事件(键盘、鼠标等IO设备)
- 等待资源
- 典型方法是设定一个等待时间阈值,如果超过了这个阈值则被称为不活跃进程
- 两种调度器
- 长期调度器(准入调度器)决定
- 哪些进程可以同时运行(CPU vs I/O,工作集之和,...)
- 哪些进程是不活跃的进程,将它们换出到磁盘
- 哪些进程是活跃的进程,将它们换入到内存
- 短期调度器(CPU调度器)执行CPU调度算法,决定
- 把CPU分配给哪个活跃进程
- 长期调度器(准入调度器)决定
多进程时,如何分配页
- 多个进程同时运行的时候,替换哪个进程的页框?
- 全局分配
- 从所有进程的所有页框中选择
- 局部分配
- 只从本进程(发生缺页的进程)的页框中选择
- 全局分配
- 全局分配 vs 局部分配
- 全局分配
- 从所有进程的所有页框中选择
- 可替换其他进程的页框
- 每个进程运行期间,其内存大小是动态变化的
- 好处:全局资源调配
- 坏处:没有隔离,干扰其他进程/收到其他进程的页替换干扰
- 不能控制各个进程的内存使用量
- 局部分配
- 只能从本进程(发生缺页的进程)自己的页框中选择
- 一个进程运行期间,固定其内存大小不变
- 页框池:分配给进程的页框的集合,不同进程的页框池大小可不同
- 好处:隔离,不影响其他进程
- 坏处:不灵活
- 进程使用内存频繁会产生颠簸
- 难以充分利用内存:每个进程对内存的需求不一样
- 全局分配
平衡分配
- 局部分配 + 池大小动态调整
- 每个进程有自己的页框池(pool)
- 从自己的池中分配页,且从自己的工作集中替换页
- 用一种机制来运行时动态调整每个池的大小
- 初始大小 + 动态调整
- 进程加载方式:进程换入时
- 纯粹的按需加载页:加载较慢
- 预加载:先加载部分页 -> 初始池大小
- 初始池大小约等于工作集的大小
- 初始池大小
- 固定大小:所有进程都一样
- 平均分配
- 根据进程大小按照比例分配
- 动态调整池大小:进程大小变化
- PFF算法(Page Fault Frequency)
- 缺页率 PFR:进程每秒产生多少次缺页
- 对应大多数替换策略,PFR随分配给进程的内存增加而减少
- 根据进程的PFR来调整分配给它的内存量(pool size)
- 当PFR高于A,就增加其内存(pool 增大)
- 当PFR低于B,就缩小其内存(pool 减小)
- PFR的计算
- 方法1:只看当前1秒钟内的缺页次数Ri
- 方法2:求滑动均值
- 例如 \(PFR_i = (R_i + PFR_{i-1}) / 2\)
- PFF算法(Page Fault Frequency)
后备存储
- 交换区(Swap Area)
- 在磁盘上
- 专门用于存储进程换出页
- 交换分区(Swap Partition)
- 用专门的磁盘分区保存专门的换出数据
- 交换文件
- 用文件保存换出的数据
- 交换空间分配
- 静态分配
- 创建进程时分配,进程结束时回收
- 大小:进程映像
- 进程控制块记录交换空间的磁盘地址
- 绑定:一个虚存页 <-> 一个磁盘页
- 磁盘页称为shadow page
- 初始化:两种方法
- 页换出:进程映像拷贝到交换区
- 页换入:进程映像加载进内存
- 缺点:难以增长(栈段/堆)
- 动态分配
- 创建进程时不分配
- 页换出时分配,页换入时回收
- 虚页和磁盘页不绑定
- 多次换出,分配不同的磁盘页
- PTE中记录页的磁盘地址
- 一个优化:程序text段
- 直接用磁盘上的可执行文件作为交换区,减少交换区的大小
- 换出时直接丢弃(只读的),减少不必要的写回
- 静态分配
- 页地址转换
- PTE: 虚页 -> 页框和磁盘
- 如果valid bit=1,物理页号pp#
- 如果valid bit=0,磁盘页号dp#
- 换出
- 将PTE和TLB置为无效
- 把页拷贝到磁盘
- 将磁盘页号写回PTE
- 换入
- 找一个空闲页框(可能触发页替换)
- 将页从磁盘拷贝到这个页框中
- 将页框号填入PTE中,并将PTE置为有效
- PTE: 虚页 -> 页框和磁盘
钉住页(pin/unpin)
- 为什么需要?
- 有些页需要频繁访问,换出后又会被换入,影响系统性能
- 数据再内外存间进行传输时,需要传输的页框不能被换出,否则CPU会把新内容写入这些页框
- 如何设计?
- 用一个数据结构来记录所有钉住的页
- 换页算法在替换页时检查该数据结构
- 如果页被钉住,则不替换它,重新再选择一页
虚存设计问题 —— 页使用设计问题
清零页
- 将页清零
- 把页置成全0
- 堆和栈的数据都要初始化
- 如何实现
- 对于堆和栈段中的页,当它们第一次发生page fault时,将他们清零
- 可以通过一个专门的线程来实现清零
- 不做页清零可能会引起安全问题
写时复制(Copy-On-Write)
- 该技术用于创建子进程(fork系统调用)
- 原理
- 子进程的地址空间使用其父进程相同的映射
- 将所有的页置成 read-only
- 将子进程置成就绪
- 对于读,可以随意读
- 对于写,产生page fault
- 修改PTE,映射到一个新的物理页
- 将页内容全部拷贝到新物理页
- 重新运行发生缺页的指令
进程间共享内存
- 两个进程的页表共享一些物理页
- 进程间通信
- 进程间数据共享
- 涉及问题
- 共享页的换入换出
- 可能会影响到多个进程,需要使用pin/unpin钉住页
- 有共享页的进程的工作集
- 共享页优先进入工作集
- 有共享页的进程结束
- 根据情况确定是否释放共享页
- 共享页的换入换出
UNIX 和 Linux 的虚存机制
UNIX的地址空间
- Text段:只读 & 大小不变
- 数据段
- 初始化数据
- 未初始化数据:BSS段
- Heap区
- 栈段
- 内存映射文件(Memory Mapped File)
- 将一个文件映射进虚存,并像访问内存一样访问文件
- mmap 和 unmap
BSD4的虚存
- 物理内存划分
- Core map(pin): 所有页框的描述信息
- 内核(pin)
- 其它页框:给用户进程
- 页替换
- 运行换页守护进程(page daemon),直到有足够的空闲页
- 早期BSD使用原始的Clock替换算法(FIFO with Second Chance)
- 后来的BSD使用双表针的Clock算法
- 如果page daemon找不到足够多的空闲页的话,则运行swapper
- 寻找idle时间超过20秒及以上的进程
- 最大的4个进程
Linux的地址空间
- 32位机的地址空间
- 3GB 的用户空间
- 1GB 的内核空间
- 栈段从3GB位置向下增长
- 初始时保存进程的环境变量和命令行参数
- 数据段
- 大小可变
- BSS段:未初始化的全局变量
- 页加载时初始为0
- zero page: 避免分配大量全0的物理页
- 系统调用
- brk(addr) —— change data segment size
- mmap(addr,len,prot,flags,fd,offset) —— Map a file in
- unmap(addr,len) —— Unmap a file
- Linux的地址转换
- 2.6.11及之后的Linux都是使用4级页表
- 虚地址划分为5段: GDT UDT MDT PT 和 Offset
- 如何支持2级页表的MMU(例如Pentium)
- 通过将UDT和MDT都设置为只有一个表项
- 2.6.11及之后的Linux都是使用4级页表
- Linux的物理页分配
- 伙伴算法
- 初始时:只有一个段,含全部可用空间
- 分配空间,m页:找size>=m 且 size最小的段,假设其为n
- 如果n>=2m,则将该段对分为两个size=n/2的段
- 重复对分,直到n>=m>n/2
- 释放空间:m页,如果其伙伴也是空闲的,则不断合并它们,直到找不到空闲伙伴为止
- 数据结构
- 空闲链表数组:每条链上的页大小为2的幂
- 伙伴算法
- Linux的页替换
- 方法
- 保持一定数量的空闲页
- 文件缓存(page cache)使用Clock算法
- 未使用的共享页使用Clock算法
- 用户进程的内存使用改进的Clock算法
- 两条链
- Active List:所有进程的工作集
- Inactive List:回收的候选页
- Refill是将页从active list移动到inactive list
- 方法