须知:只要开启了分页机制,不管物理地址还是虚拟地址在CPU面前都按照分页处理,也就是即便给出物理地址CPU也按虚拟地址对待。 |
先访问到页表自己 + 再用页目录项pde(页目录表中页表的索引)做为pte的索引访问到页表 + 再用pte的索引做为页内偏移
代码
\boot\loader6_3.s
;第二步:
;将页目录表物理地址赋值给cr3寄存器,分页机制打开前要将页表地址加载到控制寄存器cr3中
mov eax, PAGE_DIR_TABLE_POS ; PAGE_DIR_TABLE_POS = 0x100000
mov cr3, eax
\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); // 真正页表项下标,在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
正常虚拟地址访问
- 第一步:
- 1:MMU从cr3中取出页目录表的物理地址。
- 2:MMU用cr3中的页目录表的物理地址+511*4定位到页目录项的物理地址。
- 第二步:
- 1:MMU从下标为511的页目录项中取出页表的物理地址。
- 2:MMU用页表的物理地址+986*4,定位到对应的页表项的物理地址。
- 第三步:
- 1:MMU从下标为986的页表项中取出普通页框的物理地址。
- 2:MMU用普通页框的物理地址+ 3199,从而定位到了真正的物理地址。
构造虚拟地址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位访问的才是别的页表的页表项。
- 第一步:
- 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时,页目录表访问两次,页表访问一次。