首先,mmap与ioremap是完全不同的概念,一个是对文件进行map,一个是对系统io资源的map。
mmap的调用流程:
sys_mmap // 位于文件sys_x86_64.c (arch\x86\kernel)
SYSCALL_DEFINE6(mmap, unsigned long, addr, unsigned long, len,unsigned long, prot, unsigned long, flags,unsigned long, fd, unsigned long, off)
do_mmap_pgoff // 位于文件mmap.c (mm)
mmap_region
函数do_mmap_pgoff 调用get_unmapped_area分配可用的虚拟地址空间,之后权限检查,找到file映射对应的inode,进行后续mmap_region处理。
函数mmap_region主要是对地址空间的处理,之后主要工作是设置vma的操作函数是generic_file_vm_ops,其fault指向filemap_fault函数。
这里并没有分配物理页框,只是分配了虚拟地址空间,当用户第一访问该空间时,会产生缺页异常。
do_page_fault、handle_mm_fault中函数为触发缺页异常的地址address分配各级的页目录,即现在拥有一个和address配对的pte,但pte如何去映射物理页框,内核又得根据pte的状态进行分类和判断,具体可以看函数handle_pte_fault,它检查address地址所对应的页表项。并决定如何为进程分配一个新页框。
在handle_pte_fault中根据实际属性有多种映射方式,如:do_linear_fault、do_anonymous_page、do_nonlinear_fault、do_swap_page、do_numa_page、do_wp_page。
【以下是网上收集而来】
在内核中,和数据相关的脏位有两个地方:页表项中有一个脏位;page描述符的flags标志中有一个PG_dirty位。
页表项中的脏位在写这个页表项时由MMU自动置位,但硬件不会清除这个位。这个位的清除是要由内核来完成的。而且内核也可以在适当的时候主动置这个位。比如缺页写异常时,内核会自己先置这个位。
page描述符里面的那个脏位就由内核管理了,这个位就是用来判断这个页是不是脏页。比如说,有多个进程mmap了这个页,其中一个进程修改了这个页,那么那个进程的页表项中的脏位就会由MMU置位。但page描述符里的脏位还未置啊。所以在回收周期扫描到这个页时,会通过逆向映射查找映射这个页的页表项,如果发现页表项中脏位被置位了,那么就会将page描述符的脏位置位表明这是一个脏页。
MMU确实会主动置页表项中的脏位,但不负责清除。内核负责清除。而且内核确实也会主动设置页表项中的脏位。就像缺页写异常那样。不会重复,是因为MMU只有在脏位未被置位的时候才会置位。
或者我举个例子。进程第一次读文件中的内容,内核会fault进对应页面到pagecache中去,因为是读异常,所以内核不会置脏位吧。接下来进程又要对同一页内容进行修改了,那么此时页已经在pagecache中了,也不需要进行什么缺页异常了,直接由MMU进行虚拟地址到物理地址的转换去写pagecache了,那么这个时候内核都不“参与”了,谁去置页表项的脏位呢?MMU自己硬件不关心page_struct。它只会置页表项中的脏位。前面说了,页表项中的脏位会在回收周期中通过逆向映射找到,并设到page_struct 的flags标志中去,这样内核就知道这是一个脏页
这个分CPU像Intel,是硬件自动至PTE_Dirty像ARM,是清PTE_Dirty时,置为只读;清PTE_Access时,置为未映射。 反之,映射前置上PTE_Access;写映射前上PTE_Dirty。
真正保证mmap dirty page被写入磁盘的机制应该是这样的:
mmap后,第一次写入,此时会page fault,然后mkdirty,此后,pte和page中都同时设置好了dirty标记,相应的dirty page能保证被写入磁盘。(这个流程没有疑问,之前已经清楚)
后续再次写入时,分两种情况:
1、再次写入时,原来的dirty page还没来得及写入,此时应该直接修改掉原有page中的内容即可,由于原有的pte和page中dirty标记都还在,所以,此时肯定也能保证相应dirty page被写入。
2、再次写入时,原理的dirty page已经writeback过了,相应的dirty标记都已经清除,就是我们这里正讨论的情况。这种情况应该是通过如下机制保证的:
在第一次写入后的dirty page的wirteback流程中会设置该page为页保护:
clear_page_dirty_for_io
page_mkclean
page_mkclean_file
page_mkclean_one
entry = pte_wrprotect(entry);
然后,当该page被再次修改时,会触发page fault,而在page fault的流程中,会判断mmap页写保护的情况,这种情况下,会根据pte的dirty标记设置page的dirty标记,并将该page重新设置为可写的:
__do_page_fault
handle_pte_fault
do_wp_page
...
else if (unlikely((vma->vm_flags & (VM_WRITE|VM_SHARED)) == (VM_WRITE|VM_SHARED))) {
/*
* Only catch write-faults on shared writable pages,
* read-only shared pages can get COWed by
* get_user_pages(.write=1, .force=1).
*/
if (vma->vm_ops && vma->vm_ops->page_mkwrite) {
struct vm_fault vmf;
int tmp;
vmf.virtual_address = (void __user *)(address & PAGE_MASK);
vmf.pgoff = old_page->index;
vmf.flags = FAULT_FLAG_WRITE|FAULT_FLAG_MKWRITE;
vmf.page = old_page;
/*
* Notify the address space that the page is about to
* become writable so that it can prohibit this or wait
* for the page to get into an appropriate state.
*
* We do this without the lock held, so that it can
* sleep if it needs to.
*/
page_cache_get(old_page);
pte_unmap_unlock(page_table, ptl);
tmp = vma->vm_ops->page_mkwrite(vma, &vmf);
...
用systemTap打点,相应的堆栈如下:
Returning from: 0xffffffff811292b0 : set_page_dirty+0x0/0x70 [kernel]
Returning to : 0xffffffff811b0a1f : __block_page_mkwrite+0xdf/0x120 [kernel]
0xffffffffa022d39a : ext4_page_mkwrite+0x17a/0x390 [ext4]
0xffffffff8113f89e : do_wp_page+0x5ee/0x8d0 [kernel]
0xffffffff8114036d : handle_pte_fault+0x2dd/0xb70 [kernel]
0xffffffff81140de4 : handle_mm_fault+0x1e4/0x2b0 [kernel]
0xffffffff81042b39 : __do_page_fault+0x139/0x490 [kernel]
0xffffffff814fbfce : do_page_fault+0x3e/0xa0 [kernel]
0xffffffff814f9325 : page_fault+0x25/0x30 [kernel]
ffffea0003b22398