hugetlb mips 分析(二)
ress相对于整个地址空间的index(以巨页大小为单位)
接着,根据idx在mapping空间里找出对应的page, 即巨页的第一个页。如果找到不对应的页,则说明还没有分配巨页,于是调用
alloc_huge_page,要么从hstates高速缓冲,要么从伙伴
系统获取物理连续的页框,之后通过
add_to_page_cache加到page cache里,可以看出,巨页文件对应的mapping,都是由巨页的index构成的radix tree。
紧接着,根据找到的page,生成一个pte entry,并给pte entry设置上_PAGE_HUGE标记,以便在缺页返回后,再次访址引起的tlb load异常时,可以
辨识出这是一个巨页pte entry。
最后,set_huge_pte_at(mm,address,ptep,new_pte)是比较关键的地方。
[cpp]
void set_huge_pte_at(struct mm_struct *mm, unsigned long addr, pte_t *ptep,
pte_t entry)
{
unsigned long i;
unsigned long htlb_entries = 1 << HUGETLB_PAGE_ORDER;
pte_t entry2;
entry2 = __pte(pte_val(entry) + (HPAGE_SIZE >> 1));
addr &= HPAGE_MASK;
for (i = 0; i < htlb_entries; i += 2) {
ptep = huge_pte_offset(mm, addr);
set_pte_at(mm, addr, ptep, entry);
addr += PAGE_SIZE;
ptep = huge_pte_offset(mm, addr);
set_pte_at(mm, addr, ptep, entry2);
addr += PAGE_SIZE;
}
}
这个函数,首先计算出一个巨页需要htlb_entries个连续页框,接着,根据hugetlb奇数页,加上一个巨页一半大小,算出偶数页所在的tlb entry2。 www.2cto.com
接着,对addr到addr+HPAGE_SIZE的空间内,凡是奇数页地址,都设置为entry,而偶数页则都设置为entry2,示意图如下:
在 page fault返回后,再次访址将产生tlb load/store异常。在build_r4000_tlbchange_handler_head函数里,
经过一系列页表寻址,找到pte entry条目,由于这个条目之前在hugetlb_no_page已经被设置了_PAGE_HUGE,
因此会走tlb_huge_update-->build_huge_handler_tail-->build_huge_update_entries将获取到的奇偶页表项,
写入entrylo1 和 entrylo 2。
[cpp]
static __cpuinit void build_huge_update_entries(u32 **p,
unsigned int pte,
unsigned int tmp)
{
build_convert_pte_to_entrylo(p, pte);
UASM_i_MTC0(p, pte, C0_ENTRYLO0); /* load it */
uasm_i_ld(p, pte, sizeof(pte_t), tmp);
build_convert_pte_to_entrylo(p, pte);
UASM_i_MTC0(p, pte, C0_ENTRYLO1); /* load it */
uasm_i_ehb(p);
}
其中,uasm_i_ld(p,pte,sizeof(pte_t),tmp) 将传入的奇数页pte entry指针,加上一个pte_t的长度,取地址内容,得到偶数页pte entry的值
接着通过build_huge_tlb_write_entry(p, l, r, pte, tlb_random);将奇偶数页都写入TLB条目。(因为此时系统中
没有匹配虚拟地址的tlb条目,所以probe失败,index小于0,会跳过build_huge_tlb_write_entry(p, l, r, pte, tlb_indexed);
直接走r4000_write_huge_probe_fail分支。
[cpp]
static __cpuinit void build_huge_tlb_write_entry(u32 **p,
struct uasm_label **l,
struct uasm_reloc **r,
unsigned int tmp,
enum tlb_write_entry wmode)
{
/* Set huge page tlb entry size */
uasm_i_lui(p, tmp, PM_HUGE_MASK >> 16);
uasm_i_ori(p, tmp, tmp, PM_HUGE_MASK & 0xffff);
uasm_i_mtc0(p, tmp, C0_PAGEMASK);
build_tlb_write_entry(p, l, r, wmode);
build_restore_pagemask(p, r, tmp, label_leave);
}
首先是根据巨页大小设置pagemask,接着通过tlbw将pte entry写入tlb
最后恢复pagemask跳转至label_leave,tlb load退出。此时系统已经有了可用的巨页tlb entry,以后访问此巨页空间不会再出现tlb miss了。
总结一下,tlb巨页缺页流程如下:
tlb miss-->tlb refill填入invalid pte entry-->返回用户态再次访址发生tlb load异常-->tlb load异常发现页表项不在内存中,引发page fault
-->调用hugetlb_fault填充巨页页表项-->返回用户态再次访址发生tlb load异常(因为tlb里仍然是invalid pte entry)-->
根据巨页页表项填充TLB条目-->返回用户态再次访址,不会出现tlb miss,程序正常运行。