逻辑地址会被MMU(内存管理单元)转换为物理地址。因为MMU会读取内存中的page table,根据映射关系进行翻译。
虚拟地址:64个位,分为index和offse,index对应页,也就是4096个字节;offset对应具体字节。64位的前25位用不上,27位表示页号,12位表页内偏移地址(2^12=4096)。根据页表只要通过index找到对应的页就行了,offset不需要翻译。
多级结构:但27位表示页号说明这个进程拥有的page table会拥有2^27条映射关系。这过于消耗内存,实际上使用多级结构,将27分为9+9+9。3层page directory,他们的大小也是4096位,每个条目PTE(Page Table Entry)是8字节,所以一个page directory有4096/8=512项。就相当于3层512叉树。
SATP寄存器会指向最高一级的page directory的物理内存地址。
index前9位地址在最高层page directory找到通过索引获得一个PNN(44位),就是物理页号,再拼上12位的0形成物理地址找到一个页,这个页是第二层page directory
以此类推,然后再第三层page directory获得一个PNN(44位),拼上12位offset,就变成了真实物理地址
好处:这样如果地址空间只使用了1页,就只需要3个page directory,也就是3*512项,每一层的page directory只有一条PTE起作用。
而原来不管怎么样都只有一页,即使你只需要1页,整个page table也要占据2^27
PTE:低10位是标志位,vaild表示是否合法,Readable和Writable可读和可写
Executable表明你可以从这个page执行指令
User表明这个page可以被运行在用户空间的进程访问。
1.vmprint
启动xv6时,刚执行完exec(),打印第一个进程的页表
首先在kernel/defs.h中增加vmprint()的声明 void vmprint(pagetable_t );
然后在exec()结束的时候调用写的函数vmprint()
最后,在kernel/vm.c实现函数vmprint()
void _vmprint(pagetable_t pagetable, int depth){
if(depth == 1) //第一次调用的时候打印那个头部
printf("page table %p\n", pagetable);
if(depth == 4) //一共只有3层页表,第四次跳出循环递归
return;
for(int i = 0; i < 512; i++){ //一页最多512个条目,遍历他们并打印
pte_t pte = pagetable[i]; //第i个条目
if(pte && PTE_V){ //如果该条目合法
printf("..");
for(int j = 1; j < depth; j++)
printf(" ..");
printf("%d: pte %p pa %p\n", i, pte, PTE2PA(pte));
_vmprint((pagetable_t) PTE2PA(pte), depth + 1); //递归调用,打印此条目对应页的所有条目
}
}
}
void vmprint(pagetable_t pagetable){
_vmprint(pagetable, 1);
}