rmap反向映射


前言

rmap笔记。


一、kernel 2.6.0版本rmap(第一版rmap)

2.6.0版本的rmap的整体结构图如下所示。
在这里插入图片描述
2.6.0的rmap的主要函数是:page_add_rmap和try_to_unmap_one
page_add_rmap:其主要功能是将存储pte表项的虚拟地址放在struct pte_chain的ptes数组中,存放ptep时按照n-1、n-2、…、1、0这种次序存放,next_and_idx则存放下一个struct pte_chain的首地址,并记录其可用的数组下标(NRPTE-1)。

struct pte_chain {
	unsigned long next_and_idx;
	pte_addr_t ptes[NRPTE];
} ____cacheline_aligned;

该函数在什么时候会调用page_add_rmap:
例如在COW时:do_page_fault->__do_page_fault->handle_mm_fault->handle_pte_fault->do_wp_page->page_add_rmap函数,首先需要alloc_page申请一个new page给write者使用。page_table = pte_offset_map(pmd, address)返回的是一个虚拟地址,表示存储pte表项的page frame的虚拟地址。再调page_add_rmap将new_page和page_table关联起来存储在pte_chain数组上,这个关系在try_to_unmap_one里面需要使用。

static inline pte_t *pmd_page_kernel(pmd_t pmd)
{
	unsigned long ptr;
    把页目录项中存的页表地址先去掉低PTRS_PER_PTE * PAGE_SIZE位属性,然后通过__va转换成线性地址
	ptr = pmd_val(pmd) & ~(PTRS_PER_PTE * sizeof(void *) - 1);
	ptr += PTRS_PER_PTE * sizeof(void *);

	return __va(ptr);页表的虚拟地址,因为内核的物理地址转虚拟地址只需要加上PAGE_OFFSET即可
}

#define pte_offset_map(dir,addr)	(pmd_page_kernel(*(dir)) + __pte_index(addr))
static int do_wp_page(struct mm_struct *mm, struct vm_area_struct * vma,
	unsigned long address, pte_t *page_table, pmd_t *pmd, pte_t pte)
{
   ……
	/*
	 * Re-check the pte - we dropped the lock
	 */
	spin_lock(&mm->page_table_lock);
	page_table = pte_offset_map(pmd, address);#这个page_table是一个虚拟地址
	if (pte_same(*page_table, pte)) {
		if (PageReserved(old_page))
			++mm->rss;
		page_remove_rmap(old_page, page_table);
		break_cow(vma, new_page, address, page_table);
		pte_chain = page_add_rmap(new_page, page_table, pte_chain);##page_table是一个虚拟地址
		lru_cache_add_active(new_page);

		/* Free the old page.. */
		new_page = old_page;
	}
	……
}

pgtable_add_rmap:主要功能是将用户态虚拟address和mm存存储在pte页表的struct page结构体里面。
其中进程的mm_struct直接记录在page->mapping,而address的高bit则记录在page->index(通过ptep_to_address反向计算出address)。

pgtable_add_rmap:存储pte表项的struct page的page->index = address & ~((PTRS_PER_PTE * PAGE_SIZE) - 1);
ptep_to_address:根据pte表项的struct page存储address的高bits,以及ptep(存储pte表项的地址)可以计算出address & (~4095)
首先根据ptep则((unsigned long)ptep & ~PAGE_MASK) =ptep & 0xFFFFF000就得到了pte表项在pmd的偏移,再乘以PTRS_PER_PTE就是low bits也就得到了address & ( ~4095 )。通过address就能知道其属于哪个vma了进而能接触pte映射。

#define PTRS_PER_PTE		512
#define PTRS_PER_PMD		1
#define PTRS_PER_PGD		2048
#存储page->index
#参数struct page* page指的是pte的page frame
#参数struct mm_struct* mm是进程的mm
#参数unsigned long address是进程需要do_page_fault的向下PAGE_SIZE对齐的虚拟地址
static inline void pgtable_add_rmap(struct page * page, struct mm_struct * mm, unsigned long address)
{
#ifdef BROKEN_PPC_PTE_ALLOC_ONE
	/* OK, so PPC calls pte_alloc() before mem_map[] is setup ... ;( */
	extern int mem_init_done;

	if (!mem_init_done)
		return;
#endif
	page->mapping = (void *)mm;
	page->index = address & ~((PTRS_PER_PTE * PAGE_SIZE) - 1);虚拟地址address的高bit位存在对应pte页表的struct

 page
	inc_page_state(nr_page_table_pages);
}

#define PAGE_SHIFT		12
#define PAGE_SIZE		(1UL << PAGE_SHIFT)
#define PAGE_MASK		(~(PAGE_SIZE-1))
#获取page->index
#参数pte_t* ptep在arm32上是物理地址
static inline unsigned long ptep_to_address(pte_t * ptep)
{
	struct page * page = kmap_atomic_to_page(ptep);
	unsigned long low_bits;
	low_bits = ((unsigned long)ptep & ~PAGE_MASK) * PTRS_PER_PTE;#将ptep的低12bit清除,这是因为此时得到的pfn<<12, 再乘以PTRS_PER_PTE
	return page->index + low_bits;
}

try_to_unmap_one:解除page和所有pte表项的映射关系。
在try_to_unmap_one时,由于已经知道ptep,那么怎么获取其映射的address呢?
由于ptep是指向pte表项的,那么pte表项也是由一个page里面分出来,那么pte也有起始位置。

static int try_to_unmap_one(struct page * page, pte_addr_t paddr)
{
	pte_t *ptep = rmap_ptep_map(paddr);/*在page_add_rmap时会将ptep转为paddr,故而这里转回来,对arm实际就是空的*/
	unsigned long address = ptep_to_address(ptep);/*根据ptep可得到其映射的虚拟地址*/
	struct mm_struct * mm = ptep_to_mm(ptep);/*page->mapping 表示就是struct mm_struct*/
	struct vm_area_struct * vma;
	pte_t pte;
	int ret;

	if (!mm)
		BUG();

	/*
	 * We need the page_table_lock to protect us from page faults,
	 * munmap, fork, etc...
	 */
	if (!spin_trylock(&mm->page_table_lock)) {锁住这个page_table,担心其他路径会修改ptep表项
		rmap_ptep_unmap(ptep);
		return SWAP_AGAIN;
	}


	/* During mremap, it's possible pages are not in a VMA. */
	vma = find_vma(mm, address);
	if (!vma) {
		ret = SWAP_FAIL;
		goto out_unlock;
	}

	/* The page is mlock()d, we cannot swap it out. */
	if (vma->vm_flags & VM_LOCKED) {
		ret = SWAP_FAIL;
		goto out_unlock;
	}

	/* Nuke the page table entry. */
	flush_cache_page(vma, address);
	pte = ptep_get_and_clear(ptep);/*清除pte表项*/
	flush_tlb_page(vma, address);/*刷新tlb表项*/

	if (PageSwapCache(page)) {
		/*
		 * Store the swap location in the pte.
		 * See handle_pte_fault() ...
		 */
		swp_entry_t entry = { .val = page->index };
		swap_duplicate(entry);
		set_pte(ptep, swp_entry_to_pte(entry));
		BUG_ON(pte_file(*ptep));
	} else {
		unsigned long pgidx;
		/*If a nonlinear mapping then store the file page offset in the pte.*/
		pgidx = (address - vma->vm_start) >> PAGE_SHIFT;
		pgidx += vma->vm_pgoff;
		pgidx >>= PAGE_CACHE_SHIFT - PAGE_SHIFT;
		if (page->index != pgidx) {
			set_pte(ptep, pgoff_to_pte(page->index));
			BUG_ON(!pte_file(*ptep));
		}
	}

	/* Move the dirty bit to the physical page now the pte is gone. */
	if (pte_dirty(pte))
		set_page_dirty(page);
		
	mm->rss--;
	page_cache_release(page);
	ret = SWAP_SUCCESS;

out_unlock:
	rmap_ptep_unmap(ptep);
	spin_unlock(&mm->page_table_lock);
	return ret;
}

二、kernel 2.6.33 版本rmap(第二版rmap)

由于kernel 2.6.0通过在struct page增加一个union的方式来实现,这样导致所有struct page都增加了一个union的消耗,对于内存消耗过多。另外由于每个pte_chain都是一个数组链表,也有不小内存消耗,在fork的时候需要copy这么一份数据,这些动作导致fork出现了性能问题。于是乎有新的一版rmap,消除掉pte_chain和增加struct page成员的方式,新版本在2.6.7就有了,本笔记采用2.6.33版本来学习。

kernel 2.6.33
do_anonymous_page
->anon_vma_prepare 申请一个anon_vma结构体,将vma挂入anon_vma的双向链表
->page_add_new_anon_rmap
1,SetPageSwapBacked,设置page可以swap交换出去。
2,增加_mapcount引用计数。
3,__page_set_anon_rmap让anon_vma指针加1,表示是匿名映射,设置page->mapping为anon_vma,
page->index = vma->vm_pgoff + (address - vma->vm_start)>> PAGE_SHIFT;

page->mapping:
1,等于NULL:表示该page frame不再内存中,而是被swap out到磁盘去了。
2,不等于NULL:
当bit[0]值为1时,表示page->mapping是指向struct anon_vma,含义是匿名映射
当bit[0]值为0时,表示page->mapping是指向struct address_space,含义是文件映射

page->index:表示address在vma的第几个page(linear_page_index函数计算)
其中vma->vm_pgoff有几种含义,
1,对于文件映射表示该vma相对于文件起始地址的偏移量(单位page)。
2,对于匿名MAP_SHARED映射表示0。
3,对于匿名MAP_PRIVATE映射表示vm_addr/PAGE_SIZE(vm_pgoff只有在匿名页面的生命周期中只有RMAP会使用)。

/**
 * page_add_new_anon_rmap - add pte mapping to a new anonymous page
 * @page:	the page to add the mapping to
 * @vma:	the vm area in which the mapping is added
 * @address:	the user virtual address mapped
 *
 * Same as page_add_anon_rmap but must only be called on *new* pages.
 * This means the inc-and-test can be bypassed.
 * Page does not have to be locked.
 */
void page_add_new_anon_rmap(struct page *page,
	struct vm_area_struct *vma, unsigned long address)
{
	VM_BUG_ON(address < vma->vm_start || address >= vma->vm_end);
	SetPageSwapBacked(page);设置这个page可以交换到磁盘
	atomic_set(&page->_mapcount, 0); /* increment count (starts at -1) 增加_mapcount计数,又有一个进程用了该page frame*/
	__inc_zone_page_state(page, NR_ANON_PAGES);增加zone和vm_stat匿名页的计数
	__page_set_anon_rmap(page, vma, address);
	if (page_evictable(page, vma))
		lru_cache_add_lru(page, LRU_ACTIVE_ANON);
	else
		add_page_to_unevictable_list(page);
}

static void __page_set_anon_rmap(struct page *page,
	struct vm_area_struct *vma, unsigned long address)
{
	struct anon_vma *anon_vma = vma->anon_vma;

	BUG_ON(!anon_vma);
	anon_vma = (void *) anon_vma + PAGE_MAPPING_ANON;
	page->mapping = (struct address_space *) anon_vma;
	page->index = linear_page_index(vma, address);计算page->index,即address相对于vma的偏移量(单位是PAGE_SIZE)
}

在页面回收或页面迁移时,例如在shrik_page_list->try_to_unmap->try_to_unmap_anon
try_to_unmap_anon:
根据struct page的mapping得到anon_vma结构体,这个anon_vma上挂入所有映射到该page的vma。之后就是遍历所有的vma。

vma_address:
根据page->index和vma->vm_pgoff,vma->vm_start可以得到address地址。由于page->index表示address相对于vma->vm_start偏移多少
个page_size,那么address = (page->index - vma->vm_pgoff) << PAGE_SHIFT + vma->vm_start。得到了address那么就可以得到pte表
项,并且可以取消pte表项的映射,具体取消映射是在try_to_unmap_one。

static int try_to_unmap_anon(struct page *page, enum ttu_flags flags)
{
	struct anon_vma *anon_vma;
	struct vm_area_struct *vma;
	int ret = SWAP_AGAIN;

	anon_vma = page_lock_anon_vma(page);
	if (!anon_vma)
		return ret;

	list_for_each_entry(vma, &anon_vma->head, anon_vma_node) {
		unsigned long address = vma_address(page, vma);
		if (address == -EFAULT)
			continue;
		ret = try_to_unmap_one(page, vma, address, flags);
		if (ret != SWAP_AGAIN || !page_mapped(page))
			break;
	}

	page_unlock_anon_vma(anon_vma);
	return ret;
}

/*
 * At what user virtual address is page expected in @vma?
 * Returns virtual address or -EFAULT if page's index/offset is not
 * within the range mapped the @vma.
 */
static inline unsigned long
vma_address(struct page *page, struct vm_area_struct *vma)
{
	pgoff_t pgoff = page->index << (PAGE_CACHE_SHIFT - PAGE_SHIFT);
	unsigned long address;

	address = vma->vm_start + ((pgoff - vma->vm_pgoff) << PAGE_SHIFT);
	if (unlikely(address < vma->vm_start || address >= vma->vm_end)) {
		/* page should be within @vma mapping range */
		return -EFAULT;
	}
	return address;

三、kernel 5.0 版本rmap(第三版rmap)

对于第二版本的rmap,由于vma->anon_vma和struct page的mapping均直接指向anon_vma结构体,anon_vma结构体有一个spin_lock,那么在进程进行fork时,会导致父进程和子进程的vma,struct page均指向同一个anon_vma,也就是不管进程有多少个,vma有多少个,整个系统同一个祖宗分支的进程大家共享只有一个anon_vma,那么这样就会导致一个问题。
假若有1000个子进程,每个子进程有1个vma映射了1000个pages,那么就有100万个page的page->mapping同时指向anon_vma,而anon_vma有一个spinlock_t lock用于Serialize access to vma list 。那么就会造成非常激烈的锁竞争。
第三版本rmap为了解决锁竞争激烈的问题,如何解决,让每个进程都有一份anon_vma,这样就能保证抢锁是进程内部的动作,而不是整个系统的。

rmap应用于文件映射、匿名映射、KSM映射,我们这里仅讨论匿名映射。

引用

https://blog.51cto.com/u_15015138/2557286
https://blog.csdn.net/u010923083/article/details/116456497

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值