释放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
相关产品推荐
相关产品推荐

