【发布时间】:2019-02-28 10:20:15
【问题描述】:
我正在尝试将虚拟内存地址转换为物理地址,但无法正常工作。我目前正在做一个操作系统作为作业,现在我必须为用户模式实现一个 printf 函数,所以当你调用 write 系统调用时,系统应该将用户模式下的数组内容打印到串口(现在),为此我必须将地址从虚拟地址转换为物理地址。
这是我来自系统调用处理程序的代码:
Pcb* pcb = getCR3(); // contains the page directory for usermode
setCR3(kernelPageDir); // set the CR3 register to the kernel page directory
uint32_t tableNum = (vAddr >> 22) & 0x3ffUL; // get the upper 10 bits
uint32_t pageIndex = (vAddr >> 12) & 0x3ffUL // get the middle 10 bits
uint32_t offset = vAddr & 0xfffUL; // get the 12 lower bits
uint32_t* topTable = pcb->pageDirectory[tableNum]; // Access the top level table
uint32_t lowTable = topTable[pageIndex]; // Entry to the 2nd table
uint32_t* addr = lowTable + offset; // Should be the physical address
serialPrintf("Structure: tableNum=%08x pageIndex=%08x offset=%08x\n", tableNum, pageIndex, offset);
serialPrintf("Address: topTable=%08x lowTable=%08x addr=%08x\n",topTable, lowTable, addr);
serialPrintf("Char:%c", (char*)addr[0]);
当我运行代码时,它在尝试访问它的值时给我一个页面错误:
Structure: tableNum=00000020 pageIndex=00000048 offset=00000378
Address: topTable=00000000 lowTable=0015d000 addr=0015d378
Page fault! errcode=00000000 addr=0015d378
这是书中解释页面结构的部分:
【问题讨论】:
-
(假设您的其他代码是正确的...)您不能在访问内存时直接使用(即取消引用)物理地址。物理地址仅供硬件本身使用。所有代码(甚至内核模式代码)都使用虚拟地址来访问内存。没有办法绕过它。现在,假设数据页存在,访问实际数据将使用
*((char *)vAddr)完成(尽管实际上内核代码仅通过特定函数访问用户虚拟地址,以确保遵守用户模式寻址限制,并正确处理页面错误等等)。 -
1.你如何获得内核 pgd 地址?它存储在 init_mm 结构中。我希望你能照顾好这个。 2.既然是系统调用处理程序,我希望这段代码运行在内核空间。 3. 不能直接在内核中取消引用物理地址。相反,您必须在每次表遍历时执行 kmap() 并使用该 VA 来遍历下一个表条目。最重要的是,对于您的用例,我没有看到任何强制页表遍历。您可以直接使用 *((char *)vAddr) (根据传递给自定义 printf 的参数对其进行类型转换)来打印值。
标签: c linux memory-management linux-kernel virtual-address-space