跑了一段时间之后,内核会越来越慢,最终卡死——不断创建进程、运行、退出,但内存一直在增长。原因是 proc_exit 什么都没回收。
proc_exit 原来做了什么
void proc_exit(int code) {
current->exit_code = code;
current->state = PROC_ZOMBIE;
schedule();
// 完事了
}
进程用到的所有资源:fd、内核栈、用户页表、用户物理页——全部泄漏。
补全资源回收
void proc_exit(int code) {
// 1. 关闭所有 fd
for (int i = 0; i < PROC_MAX_FD; i++) {
if (current->fd_table[i] >= 0) {
vfs_close(current->fd_table[i]);
current->fd_table[i] = -1;
}
}
// 2. 释放内核栈
kfree(current->stack);
current->stack = NULL;
// 3. 释放用户页表和所有用户物理页
if (current->pml4 != kernel_pml4) {
vmm_switch(kernel_pml4); // 先切回内核页表
vmm_free_user_pages(current->pml4); // 再释放
current->pml4 = NULL;
}
current->exit_code = code;
current->state = PROC_ZOMBIE;
schedule();
}
vmm_free_user_pages:遍历四级页表释放
void vmm_free_user_pages(uint64_t *pml4) {
for (int i4 = 0; i4 < 256; i4++) { // 只看低 256 项(用户空间)
if (!(pml4[i4] & PAGE_PRESENT)) continue;
uint64_t *pdpt = ENTRY_ADDR(pml4[i4]);
for (int i3 = 0; i3 < 512; i3++) {
// 跳过大页(内核 1GB 映射)
uint64_t *pd = ENTRY_ADDR(pdpt[i3]);
for (int i2 = 0; i2 < 512; i2++) {
uint64_t *pt = ENTRY_ADDR(pd[i2]);
for (int i1 = 0; i1 < 512; i1++) {
if (pt[i1] & PAGE_USER)
pmm_free(ENTRY_ADDR(pt[i1])); // 释放用户物理页
}
pmm_free(pt); // 释放 PT 页
}
pmm_free(pd);
}
pmm_free(pdpt);
pml4[i4] = 0;
}
pmm_free(pml4);
}
只遍历低 256 项对应的用户地址空间,高 256 项是内核映射(共享 kernel_pml4),不能释放。
修复大页拆分的 bug
在修 vmm_map_page 时发现另一个 bug:遇到 2MB 大页时,原来把整个 PT 清零:
// 修改前:大页拆分,新 PT 全清零 → 原来的物理映射消失!
for (int i = 0; i < 512; i++) new_table[i] = 0;
内核低 2GB 是 BIOS 建立的 2MB 大页映射。当用户进程的某个 mmap 地址触发大页拆分时,内核的物理映射就消失了,下一次访问内核代码就 Page Fault。
修复:拆分时保留原物理映射:
uint64_t large_phys = table[idx] & 0x000FFFFFFFE00000ULL; // 2MB 对齐基址
for (int i = 0; i < 512; i++)
new_table[i] = (large_phys + i * 0x1000) | page_flags; // 每个 4KB 指向原物理页
per-CPU TSS
SMP 下每颗核心需要独立的 TSS,原来只有一个:
tss_t tss; // 全局唯一 → 两核共享 rsp0,互相覆盖内核栈指针
改为数组,每核独立:
tss_t tss[8];
void tss_init(int cpu_id, uint64_t kernel_stack_top) {
tss[cpu_id].rsp0 = kernel_stack_top;
// GDT 里每核有独立的 TSS 描述符
uint16_t sel = 0x28 + cpu_id * 0x10;
write_tss_descriptor(gdtr.base, sel, &tss[cpu_id], ...);
ltr(sel);
}
验收
持续创建和退出进程,内存使用趋于稳定,不再增长。多核下 TSS 不再互相踩踏,内核栈切换正确。