跑了一段时间之后,内核会越来越慢,最终卡死——不断创建进程、运行、退出,但内存一直在增长。原因是 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 不再互相踩踏,内核栈切换正确。