Appearance
虚拟内存与页面置换
什么是虚拟内存
每个进程看到的是独立、连续的虚拟地址空间。CPU 的 MMU(内存管理单元)依据页表把虚拟页映射到物理页框;映射不存在或权限不符时触发异常,由内核处理。
进程内存隔离如何实现
每个进程有自己的页表。即使进程 A 与进程 B 使用相同虚拟地址,它们也会经各自页表映射到不同的物理页;若页表中没有映射或权限不允许,CPU 会触发异常,内核通常终止违规进程(如段错误)。进程切换时,CPU 同时切换当前使用的页表,因此用户态进程不能直接访问其他进程的私有内存。
页表还包含读、写、执行以及用户态/内核态等权限位。内核以更高特权级管理页表和物理内存;共享内存、mmap 和共享库则是内核显式让多个进程映射同一物理页的例外,此时需要额外同步来保护共享数据。
虚拟内存带来:
- 隔离与保护:不同进程默认无法任意读写彼此内存;页面可标记为可读、可写、可执行;
- 地址空间抽象:程序不必知道物理内存位置,方便装载、共享库与地址随机化;
- 按需调页:只在真正访问时把页面装入物理内存;
- 文件映射与共享:文件可映射到内存,多个进程也可映射同一只读代码页或共享内存页;
- 超额使用的有限支持:不活跃匿名页可换出到 swap,或可丢弃后从文件重新读取;但频繁换页会造成严重抖动,不能把磁盘当作等价内存。
缺页与交换
访问未驻留页面会产生缺页异常(page fault):内核检查地址是否合法,若合法则从文件或交换区载入页,更新页表后恢复指令;若非法则向进程报告错误(例如段错误)。
当物理内存紧张,系统会回收页面:干净的文件页可直接丢弃后按需重读;脏页需回写;匿名页可能写入交换空间。持续的高缺页率导致系统大部分时间在调页,称为抖动(thrashing)。
常见页面置换算法
| 算法 | 核心思想 | 特点 |
|---|---|---|
| OPT | 淘汰未来最长时间不会被访问的页 | 理论最优,需要预知未来,不能实际实现,常作基准 |
| FIFO | 淘汰最早进入内存的页 | 简单,但可能出现 Belady 异常 |
| LRU | 淘汰最近最久未使用的页 | 利用时间局部性;严格实现成本高 |
| LFU | 淘汰访问次数最少的页 | 利用频率局部性;需处理“历史热点”和计数老化 |
| Clock / Second Chance | 环形扫描访问位,访问位为 1 则清零并跳过,为 0 则淘汰 | 近似 LRU、实现成本低,操作系统常见 |
现代系统通常采用 LRU 的近似与多队列/工作集等策略,而非直接实现教材中的单一算法。
C/C++ 常见内存区域(概念模型)
不同 ABI、编译器和操作系统的具体布局不同,以下是理解生命周期的常见划分:
| 区域 | 内容 | 生命周期/管理 |
|---|---|---|
| 代码段(text) | 可执行指令,常为只读可共享 | 程序映像存在期间 |
| 只读数据 | 字符串字面量、只读常量等 | 程序映像存在期间 |
| 数据段(data) | 已初始化的全局/静态对象 | 静态存储期 |
| BSS | 零初始化的全局/静态对象 | 静态存储期 |
| 堆(heap) | 动态分配对象及分配器管理区域 | 显式释放或 RAII 管理 |
| 栈(stack) | 调用帧、自动局部对象、部分参数 | 作用域/函数返回时自动销毁 |
“栈上/堆上”描述的是常见实现与分配方式,不应代替 C++ 的正式概念:自动、动态、静态和线程存储期。
堆与栈
- 栈通常按调用帧后进先出管理,分配/回收快但容量有限;深递归或大局部数组可能栈溢出。
- 堆由分配器管理,适合运行期大小或跨作用域生命周期的对象;分配成本和碎片问题更复杂。
- C++ 中应优先让对象由值、容器和智能指针按 RAII 管理,而不是把“手动
new/delete”当作默认方式。