脏页跟踪流程

对于文件的写操作,一般有两种方式:通过write系统调用或者通过mmap系统调用。不管通过哪种方式,写操作带来的自然是相关页会变成脏的。我们知道,page结构体的flag里面有代表页面为脏的bit,另外,页表pte上也有表示映射的页面为脏的bit,那么内核是怎么来处理这些脏页的呢?相关bit又是什么时候set,什么时候clear的呢?
本文分析基于linux内核4.19.195.

首先分析mmap文件页的情况。

首先,我们来看这些页是怎么得到的。下面查看内核里通用的vm_operations_struct实例generic_file_vm_ops。

const struct vm_operations_struct generic_file_vm_ops = {
	.fault		= filemap_fault,
	.map_pages	= filemap_map_pages,
	.page_mkwrite	= filemap_page_mkwrite,
};

可知,mmap文件后,第一次写文件内容时会调用filemap_fault函数完成相关文件内容的填充动作。

vm_fault_t filemap_fault(struct vm_fault *vmf)
{
	****

	/*
	 * Do we have something in the page cache already?
	 */
	page = find_get_page(mapping, offset);
	if (likely(page) && !(vmf->flags & FAULT_FLAG_TRIED)) {
		/*
		 * We found the page, so try async readahead before
		 * waiting for the lock.
		 */
		do_async_mmap_readahead(vmf->vma, ra, file, page, offset);
	} else if (!page) {
		/* No page in the page cache at all */
		do_sync_mmap_readahead(vmf->vma, ra, file, offset);
		count_vm_event(PGMAJFAULT);
		count_memcg_event_mm(vmf->vma->vm_mm, PGMAJFAULT);
		ret = VM_FAULT_MAJOR;
retry_find:
		page = find_get_page(mapping, offset);
		if (!page)
			goto no_cached_page;
	}

	if (!lock_page_or_retry(page, vmf->vma->vm_mm, vmf->flags)) {
		put_page(page);
		return ret | VM_FAULT_RETRY;
	}

	/* Did it get truncated? */
	if (unlikely(page->mapping != mapping)) {
		unlock_page(page);
		put_page(page);
		goto retry_find;
	}
	VM_BUG_ON_PAGE(page->index != offset, page);

	/*
	 * We have a locked page in the page cache, now we need to check
	 * that it's up-to-date. If not, it is going to be due to an error.
	 */
	if (unlikely(!PageUptodate(page)))
		goto page_not_uptodate;

	/*
	 * Found the page and have a reference on it.
	 * We must recheck i_size under page lock.
	 */
	max_off = DIV_ROUND_UP(i_size_read(inode), PAGE_SIZE);
	if (unlikely(offset >= max_off)) {
		unlock_page(page);
		put_page(page);
		return VM_FAULT_SIGBUS;
	}

	vmf->page = page;
	return ret | VM_FAULT_LOCKED;
no_cached_page:
	*************
}

filemap_fault函数会调用find_get_page去查找相关文件的pagecache,若找不到就会跳转到no_cached_page标签处去读取相关的pagecache。大部分情况下,能够从page cache中获取到相关的页面,从而把相关页赋值给vmf->page。在后续的finish_fault函数流程中会将设置相关的pte项从而完成映射。
在文件页的fault函数调用完成后,会紧接着调用.page_mkwrite钩子,也就是filemap_page_mkwrite函数。

vm_fault_t filemap_page_mkwrite(struct vm_fault *vmf)
{
	struct page *page = vmf->page;
	struct inode *inode = file_inode(vmf->vma->vm_file);
	vm_fault_t ret = VM_FAULT_LOCKED;

	sb_start_pagefault(inode->i_sb);
	file_update_time(vmf->vma->vm_file);
	lock_page(page);
	if (page->mapping != inode->i_mapping) {
		unlock_page(page);
		ret = VM_FAULT_NOPAGE;
		goto out;
	}
	/*
	 * We mark the page dirty already here so that when freeze is in
	 * progress, we are guaranteed that writeback during freezing will
	 * see the dirty page and writeprotect it again.
	 */
	set_page_dirty(page); //page cache中标记页为脏 
	wait_for_stable_page(page);
out:
	sb_end_pagefault(inode->i_sb);
	return ret;
}

可见,该函数会调用TestSetPageDirty,将page->flag的相关bit进行set操作以表示这个页是脏的,而在缺页中断的finish_fault流程中,调用alloc_set_pte,然后调用maybe_mkwrite(pte_mkdirty(entry), vma);从而也把页表pte项的相关bit设置为脏。
因而,对于mmap第一次写文件,缺页中断流程就会把相关的page结构体以及pte表项的相关bit进行set操作。

那么,什么时候会把这几个bit给clear呢?
当然是把脏页回写到磁盘上的时候啦。
脏页回写流程会走到函数write_cache_pages,进而走到clear_page_dirty_for_io函数,这个函数会调用page_mkclean完成pte表项脏bit的clear操作,然后将pte表项设置为只读,然后,clear_page_dirty_for_io函数还会调用TestClearPageDirty完成page->flag的脏bit的clear动作。
为什么,脏页回写的时候,要将相关pte表项设置为只读呢?
上面分析的是mmap文件后第一次写的场景,让我们考虑第二次写的场景。
如果第二次写时,第一次写带来的脏页还没进行回写,那么,由上面的分析可知,此时页表pte是可读可写的,脏的bit为1,相关page->flag的脏bit也为1,并不需要改动,继续写即可。
如果第二次写时,第一次写带来的脏页已经进行回写,那么,由上面的分析可知,此时页表pte是只读的,脏的bit为0,相关page->flag的脏bit也为0,此时的写操作,必然会把页给写脏,那么,内核应该怎么标记脏页以及set pte表项的脏标记呢?所以,前面说的将相关pte表项设置为只读的作用就在于此,在第二次写触发的缺页中断中,会走入到do_wp_page的流程中。将page->flag的脏bit置一以及将pte的脏bit置一的操作分别在vma->vm_ops->page_mkwrite流程和wp_page_reuse中。
因而,对于mmap第二次写文件,如果页面没有被回写,则无需额外操作,page->flag以及pte的脏标志位依然为1;如果页面被回写了,则需要通过缺页中断的流程将相关标志位置1.

接下来分析使用write系统调用的情况

在通用的write_end函数generic_write_end中,会调用block_write_end,接着调用__block_commit_write,然后再调用mark_buffer_dirty,这个函数会调用TestSetPageDirty将page->flag的脏bit给置一。
注意,使用write系统调用,只将page->flag的脏bit给置一了,并没有修改pte的脏标记,毕竟,根本没有内存映射到了文件,这是与mmap写文件时最大的不同。

总结

参考文献中的总结写的很好,直接copy过来了。
1)对于mmap映射的共享文件页,因为这个文件页可能会被多个进程共享到多个vma中,所以通过页表项的脏标志位来跟踪脏页:第一次写访问发生缺页异常会读文件页到page cache中并设置进程的页表项的脏标志,回写之前(clear_page_dirty_for_io完成之前),页表项的脏标志是置位的,回写的时候(clear_page_dirty_for_io的调用)会通过反向映射机制将所有映射这个页的页表项的脏标志位清零并设置只读权限,回写之后(clear_page_dirty_for_io完成之后),再次的写访问会发生写时复制缺页异常,再次设置页表项的脏标志位,如此重复,从而跟踪了脏页。

2)对于直接通过write接口访问的文件页,因为这个文件页只会被读取到page cache中,并没有映射到任何进程地址空间,进程写访问是通过copy_from_user的方式,所以通过页描述符记录脏页。回写之前(clear_page_dirty_for_io完成之前),写文件的时候通过文件系统的写文件的调用链会设置页描述符脏标志位,回写的时候(clear_page_dirty_for_io的调用)会清除页描述符脏标志位,回写之后(clear_page_dirty_for_io完成之后),再次通过write接口写访问时,再次通过文件系统的写文件的调用链会再次设置页描述符脏标志位,如此重复,从而跟踪了脏页。

参考文献:

  1. https://mp.weixin.qq.com/s/y7yrW0ilDb3qYmVzebEY8w
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值