hugetlb mips 分析(一)

内核版本2.6.32
mips64 xlp832架构CPU
在阅读此文前,可先阅读ibm文库中关于x86巨页的使用,优点以及原理的文章:
http://www.ibm.com/developerworks/cn/linux/l-cn-hugetlb/index.html
巨页的原理,概括起来,就是在内核页面大小一定的情况下,分配物理地址连续的多个页框,模拟出一个大页面供用户态程序访问,从而减少用户程序缺页次数,提高性能。
为了让内核将这连续的多个页框视为一个整体,各个CPU架构分别做了手脚。
首先,hugetlbfs并不支持read/write。
实际上,巨页的使用,是通过用户态mmap一个hugetlbfs文件,然后第一次访问时缺页来完成。
例如,对于x86来说,有一个CR3页表基址寄存器。

当发生普通页面缺页时(其实准确的讲应该叫tlb miss),CPU根据CR3的内容,找到本进程的页表基址,再依次遍历页表,最终定位到具体的页表项(即pte entry)。
如果是hugetlb所在的虚拟空间缺页,则do_page_fault会给倒数第二级的pmd entry加一个标志,
相当于告诉CPU说,pmd entry就是最后一级页表。那么当page fault返回,CPU再次访问缺页地址时,CPU遍历页表,当找到pmd entry就不会往下寻址了,
相当于提高了寻址范围(此原理在上述ibm文档中有详细说明)

对于mips来说,由于页表的遍历和TLB的重填是软件来完成的(x86通过硬件完成),因此这里需要对页表遍历和TLB重填流程做一定修改。
所以,考察hugetlbfs的mmap驱动,以及hugetlbfs的page fault 。

mmap: hugetlbfs_file_mmap
hugetlbfs_file_mmap主要是对映射到的vma区间设置VM_HUGETLB和VM_RESERVED属性,前者用来在pagefault流程里,识别巨页
区间引发的缺页,后者用来防止巨页vma区间所包含的页被换出。
page fault: hugetlb_fault
hugetlb_fault函数的作用,主要是分配出连续物理页框,并把页框地址按一定方式写到页表里。这样,再次发生tlb重填时,就可以根据页表里的
映射写到tlb里。
但是hugetlb的缺页,和普通缺页的区别又是在哪里?根据上面的描述,我们提出两个问题:
1)页框地址按什么方式写到页表里
2)tlb重填时,如何根据页表内容写入tlb

我们继续回到hugetlb_fault流程。
[cpp] view plaincopy
int hugetlb_fault(struct mm_struct *mm, struct vm_area_struct *vma,
unsigned long address, unsigned int flags)
{ pte_t *ptep;
ptep = huge_pte_alloc(mm, address, huge_page_size(h));
entry = huge_ptep_get(ptep);
if (huge_pte_none(entry)) {
ret = hugetlb_no_page(mm, vma, address, ptep, flags);
goto out_mutex;
}
}

我们知道,对于普通的缺页,需生成从pgd到pte的映射树,
即,以发生缺页的虚拟地址为key,依次搜索进程页表,如果没有对应的pte表,
就分配一个新的pte表,将pte表地址填入上一级的pmd entry,最后返回pte表里,addr对应的pte entry地址(见pte_alloc_map函数)

然而对于巨页来说,假设一个巨页由n个连续页框组成,则需要把这n个页框可能的页表路径都先分配好(pgd->pud->pmd->pte),
也就是比普通缺页,需要多“确认” (n-1)次页表路径的“畅通”。
所以,需要调用huge_pte_alloc函数,给addr --> (addr+n*PAGE_SIZE)的虚拟空间,按照PGAE_SIZE为步长,依次分配pgd到pte的路径
当然,这里最后一步,分配具体页框还没有执行。
[cpp] view plaincopy
pte_t *huge_pte_alloc(struct mm_struct *mm, unsigned long addr,
unsigned long sz)
{
pte_t *pte = NULL;
unsigned long i = 0;
unsigned long htlb_entries = 1 << HUGETLB_PAGE_ORDER;

addr &= HPAGE_MASK;
for (i = 0; i < htlb_entries; i++) {
pte = huge_pte_alloc_single(mm, addr);
if (!pte)
return NULL;
addr += PAGE_SIZE;
}
return pte;
}

其中,htlb_entries是一个巨页包含的页框个数。
再接着往下看,如果是第一次访问巨页空间,那么走的是hugetlb_no_page,这是个相对较大的函数。
[cpp] view plaincopy
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);
}

--电子创新网--
粤ICP备12070055号