从零写 OS 内核-第十六篇:VMA 与进程退出 —— 安全回收每一片内存
“分配内存只是开始,安全释放才是终点。 今天,我们实现虚拟内存区域(VMA)管理,并确保进程退出时滴水不漏地回收所有资源!”
在前几篇中,我们实现了 Buddy 系统、Slab 分配器、用户态 malloc, 但进程的虚拟地址空间仍是一团乱麻:
无法跟踪哪些区域已分配
进程退出时,物理页、文件描述符、内核对象可能泄漏
真正的操作系统,必须能 精确管理每个进程的虚拟内存布局 , 并在进程结束时 彻底回收所有资源 ,避免内存泄漏和资源耗尽。
今天,我们就来实现: ✅ 虚拟内存区域(VMA)链表 ✅ 进程退出时的完整资源回收 ✅ 僵尸进程与 wait 机制完善
让你的 OS 拥有 健壮的生命周期管理 !
🗺️ 一、为什么需要 VMA(Virtual Memory Area)?
进程的虚拟地址空间不是一整块,而是由多个 独立区域 组成:
| 区域 | 说明 |
|---|---|
| 代码段 | ELF 的 .text 段(只读、可执行) |
| 数据段 | .data + .bss (可读写) |
| 堆(Heap) | brk 扩展的区域 |
| 内存映射区 | mmap 创建的区域(文件/匿名) |
| 栈(Stack) | 用户栈(通常在高地址) |
没有 VMA 的问题:
无法知道某虚拟地址属于哪个区域
(Page Fault 处理困难)
无法正确释放
mmap
区域
无法实现
munmap
系统调用
💡 VMA 是进程虚拟地址空间的“地图” !
📦 二、VMA 数据结构设计
每个 VMA 描述一个连续的虚拟内存区域:
进程 PCB 增加 VMA 链表:
🔑 VMA 链表按 vm_start 升序排列 ,便于查找和合并。
⚙️ 三、VMA 核心操作
- 查找 VMA(find_vma)
-
插入 VMA(insert_vma)
-
合并相邻 VMA(可选优化)
📞 四、系统调用集成
-
mmap:创建新 VMA
-
munmap:释放 VMA
☠️ 五、进程退出:资源回收全景
当进程调用 exit() 或被杀死,内核必须回收:
- 虚拟内存资源
-
文件描述符
-
内核对象
-
进程状态转换
🧪 六、测试:内存泄漏检测
用户程序:
内核监控:
在
do_exit
中打印释放的 VMA 数量、物理页数
确保每次进程退出,资源计数归零
✅ 无泄漏:进程退出后,所有物理页、VMA、文件描述符均被回收!
⚠️ 七、边界情况处理
- 多线程进程
当前设计为单线程,多线程需额外管理线程栈
-
共享内存
MAP_SHARED
映射的区域,释放时需检查是否还有其他进程引用
-
信号处理中的退出
确保在 Page Fault、系统调用中也能安全退出
-
父进程先退出
子进程成为孤儿,应被 init 进程收养
💡 完善的进程退出机制,是系统长期稳定运行的关键!
💬 写在最后
VMA 和进程退出看似是“收尾工作”, 但它们决定了操作系统的 健壮性与可靠性 。 一个会泄漏内存的 OS,终将在长时间运行后崩溃。
今天你实现的 do_exit , 正是 Linux 中 do_exit 和 release_task 的简化版。
🌟 真正的工程之美,在于优雅地处理每一个结束。
📬 动手挑战 : 实现 munmap 系统调用,并验证释放后该区域触发 Page Fault。 欢迎在评论区分享你的 VMA 链表调试技巧!
👇 下一篇你想看: 写时复制(CoW)实现 ,还是 进程间通信(IPC) ?
#操作系统 #内核开发 #VMA #进程退出 #内存管理 #资源回收 #从零开始
评论