再接着往下看,如果是第一次访问巨页空间,那么走的是hugetlb_no_page,这是个相对较大的函数。
[cpp]
static int hugetlb_no_page(struct mm_struct *mm, struct vm_area_struct *vma,
unsigned long address, pte_t *ptep, unsigned int flags)
{
idx = vma_hugecache_offset(h, vma, address);
page = find_lock_page(mapping, idx);
if (!page) {
size = i_size_read(mapping->host) >> huge_page_shift(h);
page = alloc_huge_page(vma, address, 0);
err = add_to_page_cache(page, mapping, idx, GFP_KERNEL);
}
new_pte = make_huge_pte(vma, page, ((vma->vm_flags & VM_WRITE)
&& (vma->vm_flags & VM_SHARED)));
set_huge_pte_at(mm, address, ptep, new_pte);
}
hugetlb_no_page负责分配一块连续的页框,根据一个巨页所包含的虚拟空间,将所有涉及到的页表项pte entry,都指向这块连续页框的起始地址。这样,以后无论进程发生在这块巨页空间里任何一处的tlb异常,都会将tlb的pfn指向这块物理空间,保证完成最终的tlb映射。
vma_hugecache_offset根据传入的缺页地址address,计算出此address相对于整个地址空间的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。
接着,对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);