须知: 只要开启了分页机制,不管物理地址还是虚拟地址在CPU面前都按照分页处理,也就是即便给出物理地址CPU也按虚拟地址对待。 为什么没有出现页目录表结构体,也没有页目录项结构体。页目录表在某一块内存中,页表也在某一块内存中。只要用指针指向这些内存块中的某一个地址就能操控页目录表项和页表项,并不需要一个专门的的数据结构来定义页目录表和页表。 |
\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
* 说明:
* 处理器第一次当成页目录表处理,第二次当成页表处理,第三次当成普通页处理
*
* 分三步:
* 第一步,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); // 真正页表项下标,在cpu眼里是普通页中的地址
// 得到一个新的地址,新地址高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
和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的页目录项)中存储的是页目录表的物理地址,所以只要给出的虚拟地址new_vaddr的高十位是0x3ff,那么,原本new_vaddr的中间10位访问的是别的页表中的页表项就不会发生,相反,new_vaddr的中间10位(原本作为页表项的下标)仍然访问的页目录项的物理地址。new_vaddr的低12位访问的才是别的页表的页表项。文章来源:https://uudwc.com/A/Xkggz
- 第一步:
- 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时,页目录表访问两次,页表访问一次。文章来源地址https://uudwc.com/A/Xkggz