You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

释放PCB后访问pid未触发页错误及invlpg指令使用疑问

销毁PCB后访问pid未触发页错误的问题分析

问题描述

在BOCHS模拟Intel-80386开发操作系统时,遇到以下问题:

  • 销毁PCB并调用invlpg指令刷新TLB后,仍然能访问该PCB的pid成员且未触发页错误
  • 对inline assembly中传递指针作为内存操作数时,invlpg指令无法正常工作的原因存在疑问

相关代码

进程退出函数

//释放进程
void thread_exit(struct task_struct* thread_over, bool need_schedule)
{
    intr_disable();
    thread_over->status = TASK_DIED;

    if (elem_find(&thread_ready_list, &thread_over->general_tag)) {
        list_remove(&thread_over->general_tag);
    }

    if (thread_over->pgdir) {
        mfree_page(PF_KERNEL, thread_over->pgdir, 1);
    }

    list_remove(&thread_over->all_list_tag);

    //此处释放PCB的物理页,此后不应再访问整个PCB
    if (thread_over != main_thread) {
        mfree_page(PF_KERNEL, thread_over, 1);
    }

    //但仍可访问PCB的pid且未触发页错误
    release_pid(thread_over->pid);

    if (need_schedule) {
        schedule();
        PANIC("thread_exit: should not be here\n");
    }
}

物理页释放函数

//释放物理页
void mfree_page(enum pool_flags pf, void* _vaddr, uint32_t pg_cnt)
{
    uint32_t pg_phy_addr;
    uint32_t vaddr = (int32_t)_vaddr, page_cnt = 0;
    ASSERT(pg_cnt >=1 && vaddr % PG_SIZE == 0);

    pg_phy_addr = addr_v2p(vaddr);

    ASSERT((pg_phy_addr % PG_SIZE) == 0 && pg_phy_addr >= 0x102000);
    if (pg_phy_addr >= user_pool.phy_addr_start) {
        vaddr -= PG_SIZE;
        while (page_cnt < pg_cnt) {
            vaddr += PG_SIZE;
            pg_phy_addr = addr_v2p(vaddr);
            ASSERT((pg_phy_addr % PG_SIZE) == 0 && pg_phy_addr >= user_pool.phy_addr_start);
            pfree(pg_phy_addr);
            page_table_pte_remove(vaddr);
            page_cnt++;
        }
        vaddr_remove(pf, _vaddr, pg_cnt);
    } else {
        vaddr -= PG_SIZE;
        while (page_cnt < pg_cnt) {
            vaddr += PG_SIZE;
            pg_phy_addr = addr_v2p(vaddr);
            ASSERT((pg_phy_addr % PG_SIZE) == 0 && pg_phy_addr < user_pool.phy_addr_start \
                                                && pg_phy_addr >= kernel_pool.phy_addr_start);
            pfree(pg_phy_addr);
            page_table_pte_remove(vaddr);
            page_cnt++;
        }
        vaddr_remove(pf, _vaddr, pg_cnt);       
    }
}

页表项移除函数

//从页表中移除pte
static void page_table_pte_remove(uint32_t vaddr)
{
    uint32_t* pte = pte_ptr(vaddr);
    *pte &= ~PG_P_1;
    //使TLB失效
    asm volatile("invlpg %0" : : "m"(vaddr) : "memory");
}

问题原因与解决方案

1. invlpg指令使用错误导致TLB未正确刷新

invlpg指令的作用是刷新指定虚拟地址对应的TLB条目,其语法要求操作数为该虚拟地址的内存引用(即invlpg [虚拟地址])。但当前代码中的inline assembly写法错误:

asm volatile("invlpg %0" : : "m"(vaddr) : "memory");

这里的"m"(vaddr)告诉编译器将变量vaddr的内存地址作为操作数,因此实际执行的是invlpg [&vaddr],刷新的是存储vaddr的那个内存地址的TLB条目,而非vaddr值指向的PCB虚拟地址的TLB条目。

正确写法:
应该将vaddr的值作为虚拟地址传入,使用寄存器操作数构造内存引用:

asm volatile("invlpg (%0)" : : "r"(vaddr) : "memory");

或者直接引用vaddr值对应的内存区域:

asm volatile("invlpg %0" : : "m"(*(uint8_t*)vaddr) : "memory");

2. 未触发页错误的根本原因

因为invlpg调用错误,TLB中PCB虚拟地址的旧条目未被清除,CPU仍然使用该条目访问对应的物理页。即使页表项的Present位已被清零,CPU直接从TLB获取地址转换信息,不会重新查询页表,因此不会触发页错误。另外,被释放的物理页内容尚未被覆盖,所以仍然能读取到pid的值。

3. 额外注意点

  • 修复invlpg的使用后,还需确保在访问thread_over->pid前,PCB的虚拟地址对应的页表项确实已被正确标记为非Present状态
  • 内核空间的页表操作需确保页目录基址(CR3寄存器)是当前内核的页目录,避免页表操作失效

内容的提问来源于stack exchange,提问作者Ares

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 18:15:16