如何通过一个虚拟地址获得页目录表的虚拟地址

须知:只要开启了分页机制,不管物理地址还是虚拟地址在CPU面前都按照分页处理,也就是即便给出物理地址CPU也按虚拟地址对待。

为什么没有出现页目录表结构体,也没有页目录项结构体。页目录表在某一块内存中,页表也在某一块内存中。只要用指针指向这些内存块中的某一个地址就能操控页目录表项和页表项,并不需要一个专门的的数据结构来定义页目录表和页表。

一个页目录项存储的是页表的物理地址,页表中有1024个页表项,每个页表项存储的是页框的物理地址。一个页4k大小(2^12=4k),因此,每个页目录项和页表项存储的物理地址的最后12位都是0。一个虚拟地址vaddr的高10位中间10位最后12位都是索引。

代码

\boot\loader6_3.s

;第二步:
;将页目录表物理地址赋值给cr3寄存器,分页机制打开前要将页表地址加载到控制寄存器cr3中
mov eax, PAGE_DIR_TABLE_POS              ; PAGE_DIR_TABLE_POS = 0x100000
mov cr3, eax

0x7ffdac7f
\kernel\memory.c

/** 
 * 功能:
 *      得到参数vaddr所在的pte指针,指针的值也就是虚拟地址。
 *      通过vaddr构造一个新的虚拟地址new_vaddr,该新地址能够访问到vaddr所在pte。
 * 参数:
 *      vaddr          虚拟地址,分为三个部分,0000_0000_00|0000_0000_00|0000_0000_0000
 *                                            pde_num      pte_num      p_add
 * 说明:
 *      这里面临一个问题:如何通过虚拟地址访问到页目录表本身?
 *      1. MMU会把寄存器cr3的地址+pde_num*4获得pde地址。
 *      2. 再从pde中取出页表地址+pte_num*4获得pte地址。
 *      3. 再从pde中取出普通页地址+p_add获得最终地址。
 * 
 *      (1)pte_ptr和pde_ptr这两个函数返回的是能够访问到vaddr所在pte及pde的新虚拟地址new_vaddr,
 *      new_vaddr经过处理器处理32位地址的三个步骤,最终指向vaddr的pte及pde所在的物理地址。
 *      因此,这两个函数的功能等同于:给我一个新的虚拟地址new_vaddr,让它指向vaddr所在的pde及pte,
 *      也就是让new_vaddr指向pde及pte所在的物理地址。
 *      (2)这两个函数中的参数vaddr,可以是已经分配、在页表中存在的,也可以是尚未分配,
 *      目前页表中不存在的虚拟地址,pte_ptr和pde_ptr这两个函数只是根据虚拟地址转换的规则计
 *      算出vaddr对应的pte及pde的虚拟地址,与vaddr所在的pte及pde是否存在无关。
 *      
 *      处理器第一次当成页目录表处理,第二次当成页表处理,第三次当成普通页处理
 * 
 *      分三步:
 *          第一步,CPU将页目录表当成页目录表
 *          第二步,CPU将页目录表当成页表
 *          第三步,CPU将页目录表当成页(也就是页帧)
 */
uint31_t *pte_ptr(uint32_t vaddr)
{
    // 0xffc00000 = 1111 1111 1100 0000 0000 0000 0000 0000 
    // 1111_1111_11b=0x3ff=1023 
    uint32_t *pte = (uint32_t *)(0xffc00000  // new_vaddr的页目录项下标,只为获得页目录表的物理地址
                    + ((vaddr & 0xffc00000) >> 10)  // 真正的页目录项下标,在cpu眼里是页表项下标
                    + PTE_IDX(vaddr) * 4);  // 表项下标,取出vaddr的中间10位,在cpu眼里是普通页中的地址
    
    // 得到一个新地址,新地址new_vaddr的高10位必定是0xffc;中间10位是vaddr的高10位;低12位中的高10位是vaddr的中间10位。
    return pte;
}

/** 
 * 功能:
 *      根据vaddr来构造一个新的32位地址new_vaddr。
 * 参数:
 *      vaddr                   虚拟地址                
 * 说明:
 *      new_vaddr为虚拟地址vaddr对应的pde的指针得到虚拟地址vaddr所在pde的指针,也就
 *      是返回能够访问该pde的虚拟地址。
 */
uint32_t *pde_ptr(uint32_t vaddr)
{
    /* 0xfffff是用来访问到页表本身所在的地址,如果new_vaddr的低12位为0,则访问到的是页表的起始虚拟地址 */
    uint32_t *pde = (uint32_t *)((0xfffff000) + PDE_IDX(vaddr) * 4);
    return pde;
}

MMU机制

页目录表物理地址已经存放在cr3寄存器,只要开启了分页机制,任何地址在CPU眼里都要cr3+pde_num*4找到页目录项,从页目录项中取出页表的地址;再用页表地址+pte_num*4找到页表项,再从页表项中找到页框地址;再用p_add在该页框中定位具体地址。

正常虚拟地址访问

假如vaddr = 0x7ffdac7f = (0111 1111 11)(11 1101 1010) 1100 0111 1111
             pde_num   pte_num   p_add

  • 第一步:
    • 1:MMU从cr3中取出页目录表的物理地址。
    • 2:MMU用cr3中的页目录表的物理地址+511*4定位到页目录项的物理地址。
  • 第二步:
    • 1:MMU从下标为511的页目录项中取出页表的物理地址。
    • 2:MMU用页表的物理地址+986*4,定位到对应的页表项的物理地址。
  • 第三步:
    • 1:MMU从下标为986的页表项中取出普通页框的物理地址。
    • 2:MMU用普通页框的物理地址+ 3199,从而定位到了真正的物理地址。

为什么要构造虚拟地址vaddr对应的pte指针

虚拟地址vaddr从虚拟地址池分配出来后,是无法访问的,因为没有建立物理地址映射。为此需要在页目录表中建立所在的页目录项和页表项。

虚拟地址范围与页目录项是一一对应的关系的,同样与页表项也一一对应,这种对应关系是固定的。4G范围的虚拟地址空间对应1024个页目录项,同样4M范围的虚拟地址对应1024个页表项。

虚拟地址空间与页表项、页目录项对于关系

虚拟地址vaddr = 0x7ffdac7 f对应页目录项下标为511,对应的页表项下标为986。也就是访问vaddr必须通过下标为511页目录项和下标为986页表项中转。因此,要将下标为986的页表项所在页表的物理地址存入下标为511页目录项中,要将普通页框的物理地址下标为986页表项中。

这就是函数pde_ptr()和pte_ptr()存在的意义。利用vaddr与页目录项和页表项一一对应的关系,计算出要操作哪个页目录项和页表项,从而改写目录项与页表项中的内容,建立物理地址与虚拟地址的映射关系。

构造虚拟地址vaddr对应的pte指针

要构造一个新的虚拟地址new_vaddr,该新地址要能够访问到vaddr所在pte。也就是得到虚拟地址 vaddr 对应的 pte 指针。

将vaddr作为参数,调用pte_ptr()得到:

*pte = (uint32_t *)(0xffc00000 + ((0x7ffdac7f & 0xffc00000) >> 10) 
                    + ((0x7ffdac7f & 0x003ff000) >> 12) * 4); 

0x7ffdac7f = 0111 1111 1111 1101 1010 1100 0111 1111
0xffc00000 = 1111 1111 1100 0000 0000 0000 0000 0000

(0x7ffdac7f & 0xffc00000) = 0x7fc00000 = 0111 1111 1100 0000 0000 0000 0000 0000
= (uint32_t *)(0xffc00000 + (0x7fc00000 >> 10) + ((0x7ffdac7f & 0x003ff000) >> 12) * 4)

0x7fc00000 >> 10 = 0111 1111 1100 0000 0000 00 = 0x1ff000
= (uint32_t *)(0xffc00000 + 0x1ff000 + ((0x7ffdac7f & 0x003ff000) >> 12) * 4)

(0x7ffdac7f & 0x003ff000) = 0x3DA000 = 0011 1101 1010 0000 0000 0000
= (uint32_t *)(0xffc00000 + 0x1ff000 + (0x3DA000 >> 12) * 4)

0x3DA000 >> 12 = 0011 1101 1010 00 = 0x3da
0x3da * 4 = 0xf68

= (uint32_t *)(0xffc00000 + 0x1ff000 + 0xf68)

0xffc00000 = 1111 1111 1100 0000 0000 0000 0000 0000
0x1ff000 = 0000 0000 0001 1111 1111 0000 0000 0000
0xf68 = 0000 0000 0000 0000 0000 1111 0110 1000

0xffc00000 + 0x1ff000 + 0xf68 = 0xFFDFFF68
= (1111 1111 11)(01 1111 1111) (1111 0110 1000)
pde_num    pte_num    p_add

= (uint32_t *)(0xFFDFFF68)

因此得到:

  • pde_num = 0x3ff
  • pte_num = 0x1ff
  • p_add = 0xf68

访问vaddr对应的pte

由于页目录表中的最后一个页目录项(也就是下标为1023的页目录项)中存储的是页目录表的物理地址,所以只要给出一个能访问pde或pte的虚拟地址,那么该虚拟地址new_vaddr的高十位必定是0x3ff,new_vaddr的中间10位访问的也是页目录项,也就是实际上访问了2遍页目录表,高10位访问一次中间10为访问一次。new_vaddr的低12位访问的才是别的页表的页表项。

也可以这么理解只要给出的虚拟地址new_vaddr高10位是0x3ff,那么这个虚拟地址肯定会访问2遍页目录表,因为0x3ff是最后一个页目录项下标,而最后一个页目录项中存储的是页目录表自身的物理地址。所以中间10位仍然会作为页目录项的下标访问页目录表,此次的页目录项中获得的物理地址才是页表的地址。

还可以构造一个虚拟地址new_addr高10位是0x3ff,中间10位也是0x3ff那么这个虚拟地址将会访问3遍页目录表。如果最后12位是4092,那仍然会访问页目录表中的最后一个页目录项,从中获取数据,这个数据就是页目录表的物理地址。

  • 第一步:
    • 1:MMU从cr3中取出页目录表的物理地址。
    • 2:MMU用cr3中的页目录表的物理地址+1023*4定位到页目录项的物理地址。
  • 第二步:
    • 1:MMU从下标为1023的页目录项中再次取出页目录表的物理地址。
    • 2:MMU用下标为1023的页目录项中存储的页目录表的物理地址+511*4,再次定位到对应页目录项的物理地址。
  • 第三步:
    • 1:MMU从下标为511的页目录项中取出页表的物理地址。
    • 2:MMU用下标为511的页目录项中存储的页表的物理地址+3944,从而定位到vaddr对应的pte的物理地址。

总结

当正常访问vaddr时,页目录表访问一次,页表访问一次,普通页框访问一次。
当访问vaddr对应的pte时,页目录表访问次,页表访问一次。

访问机制是一样的都是分三步走,通过构造不一样的数据分别能访问到vaddr和vaddr对应的pde和pte。

posted @ 2023-09-30 19:01  仰望星空_22  阅读(367)  评论(0)    收藏  举报