Quantcast
Channel: CSDN博客推荐文章
Viewing all articles
Browse latest Browse all 35570

hugetlb mips 分析

$
0
0

内核版本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流程。

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的路径

当然,这里最后一步,分配具体页框还没有执行。

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,这是个相对较大的函数。

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)是比较关键的地方。

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。

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分支。

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,程序正常运行。


作者:chenyu105 发表于2013-2-16 23:04:39 原文链接
阅读:105 评论:0 查看评论

Viewing all articles
Browse latest Browse all 35570

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>