从零写 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 核心操作

  1. 查找 VMA(find_vma)

  2. 插入 VMA(insert_vma)

  3. 合并相邻 VMA(可选优化)


📞 四、系统调用集成

  1. mmap:创建新 VMA

  2. munmap:释放 VMA


☠️ 五、进程退出:资源回收全景

当进程调用 exit() 或被杀死,内核必须回收:

  1. 虚拟内存资源

  2. 文件描述符

  3. 内核对象

  4. 进程状态转换


🧪 六、测试:内存泄漏检测

用户程序:

内核监控:


do_exit
中打印释放的 VMA 数量、物理页数
确保每次进程退出,资源计数归零

✅ 无泄漏:进程退出后,所有物理页、VMA、文件描述符均被回收!


⚠️ 七、边界情况处理

  1. 多线程进程

    当前设计为单线程,多线程需额外管理线程栈

  2. 共享内存

    MAP_SHARED

    映射的区域,释放时需检查是否还有其他进程引用

  3. 信号处理中的退出

    确保在 Page Fault、系统调用中也能安全退出

  4. 父进程先退出

子进程成为孤儿,应被 init 进程收养

💡 完善的进程退出机制,是系统长期稳定运行的关键!


💬 写在最后

VMA 和进程退出看似是“收尾工作”, 但它们决定了操作系统的 健壮性与可靠性 。 一个会泄漏内存的 OS,终将在长时间运行后崩溃。

今天你实现的 do_exit , 正是 Linux 中 do_exit 和 release_task 的简化版。

🌟 真正的工程之美,在于优雅地处理每一个结束。


📬 动手挑战 : 实现 munmap 系统调用,并验证释放后该区域触发 Page Fault。 欢迎在评论区分享你的 VMA 链表调试技巧!

👇 下一篇你想看: 写时复制(CoW)实现 ,还是 进程间通信(IPC) ?


#操作系统 #内核开发 #VMA #进程退出 #内存管理 #资源回收 #从零开始

最后修改: 2026年7月1日

作者

评论

发表评论

您的邮箱地址不会被公开。