深入理解Linux内核脏页跟踪转载本文请联系Linux内核航海者公众号。1.开启环境:处理器架构:arm64内核源码:linux-5.10.50ubuntu版本:20.04.1代码阅读工具:vim+ctags+cscopeLinux内核由于pagecache的存在,一般修改文件数据不会同步到diskimmediately,会缓存在内存的pagecache中。我们把这种与磁盘数据不一致的页面称为脏页,脏页会在合适的时候同步到磁盘中。为了写回页缓存中的脏页,需要将页标记为脏页。脏页跟踪是指内核如何在适当的时候将文件页记录为脏页,以便在执行脏页回写时内核知道将哪些页写回磁盘。匿名页面不需要跟踪脏页,因为它们不需要同步到磁盘;私有文件页不需要跟踪脏页,因为在映射时,可写页会被映射为只读页,写访问会在写时被复制,成为匿名页;所以只有共享文件页需要跟踪脏页。跟踪有两个层次:一个是页表项记录,一个是页描述符记录。访问文件页有两种方式:一种是通过mmap映射文件,另一种是通过文件系统的写接口操作文件。本文将对这两种方式进行说明。在Linux内核中,由于跟踪脏页涉及到文件回写、缺页异常、反向映射等技术,因此本文也重点介绍如何在Linux内核中跟踪脏页。2、mmap映射文件页的基本过程如下:1)通过mmap映射共享文件。2)当第一次访问文件页时,发生页错误后,将文件页读入页缓存,如果是写访问,则设置对应进程的页表项为脏可写。3)脏页回写时,会使用反向映射机制,找到每一个映射该页的vma,将对应进程的页表项设置为只读,并清除脏标记。4)如果第二次访问文件页,脏页处理有两种情况:pagecache中的文件页还没有写回磁盘(第3步之前),此时,文件页仍然是脏页。因为对应进程的页表项是脏可写的,所以可以直接写入这个页。pagecache中的文件页已经被写回磁盘(经过3步),此时文件页不再是脏页。因为页表项是只读的,所以在写访问期间会发生写时复制页错误。在异常处理中,会处理共享文件页映射,将相应进程的页表项重新设置为脏可写。分析如下:2.1如果文件页在第一次写访问时是mmap-mapped,如果页表没有填满,写访问会导致转换表错误类型的pagefault异常。//mm/memory.chandle_pte_fault->do_fault->do_shared_fault->__do_fault//读取文件页到pagecache->do_page_mkwrite->vmf->vma->vm_ops->page_mkwrite()->filemap_page_mkwrite,//forext2->set_page_dirty(page)->__set_page_dirty_buffers->__set_page_dirty//将页缓存中的页标记为脏->TestSetPageDirty(page)//设置页描述符脏标记->finish_fault//设置页表条目->alloc_set_pte->if(write)entry=maybe_mkwrite(pte_mkdirty(entry),vma)//设置页表项dirty,writable2.2回写脏页时//mm/page-writeback.cwrite_cache_pages->clear_page_dirty_for_io(page)//对每个回写页->page_mkclean(page)//cleanmarkmm/rmap.c->page_mkclean_one//反向映射找到本页的每一个vma,调用cleanmark和写保护处理->entry=pte_wrprotect(entry);//写保护处理,设置只读entry=pte_mkclean(entry);//清除标记set_pte_at(vma->vm_mm,address,pte,entry)//设置为页表项->TestClearPageDirty(page)//清除PageDescriptorDirtyMark2.3当第二次访问一个文件页时1)当脏页还没有被写回时(准确的说,在调用clear_page_dirty_for_io之前),页描述符已经设置了dirtymark,页表项已经设置脏标记,可写。此时可以直接写访问文件页,不会出现页错误。2)当脏页被写回后(准确的说是调用clear_page_dirty_for_io之后),页描述符已经清除了脏标记,页表项已经清除了脏标记,并且是只读的。此时,写访问文件页时会出现copy-on-writepagefault异常(accesspermissionerrorpagefault)。调用链如下://mm/memory.chandle_pte_fault->if(vmf->flags&FAULT_FLAG_WRITE){//vma可写if(!pte_write(entry))//页表项没有可写属性returndo_wp_page(vmf)//write复制时pagefault异常处理do_wp_page->}elseif(unlikely((vma->vm_flags&(VM_WRITE|VM_SHARED))==(VM_WRITE|VM_SHARED))){//是共享可写文件映射vmareturnwp_page_shared(vmf);->do_page_mkwrite->vmf->vma->vm_ops->page_mkwrite()->filemap_page_mkwrite,//forext2->set_page_dirty(page)->__set_page_dirty_buffers//pagecache标记页脏->TestSetPageDirty(page)//setPagedescriptordirtymark->finish_mkwrite_fault->wp_page_reuse->entry=maybe_mkwrite(pte_mkdirty(entry),vma)//重置页表入口dirty且可写2.4再次写访问重复以上步骤。3、write接口操作的文件页在通过write接口访问文件页时,会将文件页读取到pagecache中,不会映射到任何进程地址空间。所有这种跟踪脏页的方式都是通过设置/清除页面描述符脏标记来实现的。3.1第一次访问一个文件页时,会先将文件页读到pagecache中,然后将用户空间的writebuffer数据写入到pagecache中。调用链如下:ext2_file_write_iter//fs/ext2/file.c->generic_file_write_iter//mm/filemap.c->__generic_file_write_iter->generic_perform_write->a_ops->write_begin()//写入前处理并分配pagecache页->iov_iter_copy_from_user_atomic//用户空间写缓冲区数据写入pagecachepage->a_ops->write_end()//写完后的流程->block_write_end->__block_commit_write->mark_buffer_dirtyif(!TestSetPageDirty(page)){//设置页描述符dirtymark->__set_page_dirty//Setpagetodirty(setpagedescriptordirtymark)3.2脏页回写时write_cache_pages//mm/page-writeback.c->clear_page_dirty_for_io->TestClearPageDirty(page)//清除脏页描述符标志位仍然设置,等待回写,不需要设置页面描述符脏标志。脏页写回后,页面描述符脏标志被清除,文件写页调用链将设置页面描述符脏标志。4.总结1)对于mmap映射的共享文件页,由于这个文件页可能被多个vma中的多个进程共享,所以通过页表项的dirtyflag位来跟踪脏页:第一次写访问发生的pagefault异常会将文件页读入pagecache,并设置进程页表项的dirtyflag。在回写之前(clear_page_dirty_for_io完成之前),设置页表项的脏标志。回写时(clear_page_dirty_for_io的调用)会通过反向映射机制清除映射该页的所有页表项的脏标志,并设置只读权限。回写后(clear_page_dirty_for_io完成后),写访问会导致copy-on-writepagefault异常。再次设置页表项的脏标志位,以此类推,从而对脏页进行跟踪。2)对于直接通过write接口访问的文件页,因为这个文件页只会被读入pagecache,不会映射到任何进程地址空间,进程写访问是通过copy_from_user,所以通过pagedescriptor来记录脏页。在回写之前(clear_page_dirty_for_io完成之前),写文件时会通过文件系统的调用链设置页描述符脏标志,回写时(clear_page_dirty_for_io的调用)清除页描述符脏标志。写入完成后(clear_page_dirty_for_io完成后),再次通过write接口进行写入访问时,通过文件系统写入文件的调用链会再次设置pagedescriptordirtyflag,依此类推,从而对dirtypage进行跟踪。
