详解Linux内核之脏页跟踪

Linux内核由于存在page cache, 一般修改的文件数据并不会马上同步到磁盘,会缓存在内存的page cache中,我们把这种和磁盘数据不一致的页称为脏页,脏页会在合适的时机同步到磁盘。为了回写page cache中的脏页,需要标记页为脏。

1.开场白

环境:

处理器架构:arm64

内核源码:linux-5.10.50

ubuntu版本:20.04.1

代码阅读工具:vim+ctags+cscope

Linux内核由于存在page cache, 一般修改的文件数据并不会马上同步到磁盘,会缓存在内存的page cache中,我们把这种和磁盘数据不一致的页称为脏页,脏页会在合适的时机同步到磁盘。为了回写page cache中的脏页,需要标记页为脏。

脏页跟踪是指内核如何在合适的时机记录文件页为脏,以便内核在进行脏页回写时,知道将哪些页面回写到磁盘。匿名页不需要跟踪脏页,因为不需要同步到磁盘;私有文件页也不需要跟踪脏页,因为映射的时候,可写页会映射为只读,写访问会发生写时复制,转变为匿名页;所以只有共享的文件页需要跟踪脏页。跟踪有两个层面:一个是页表项记录,一个是页描述符记录。

访问文件页有两种方式:一种是通过mmap映射文件,一种是通过文件系统的write接口操作文件,本文将对这两种方式进行讲解。在Linux内核中,因为跟踪脏页会涉及到文件回写、缺页异常、反向映射等技术,所以本文也重点讲解在Linux内核中如何跟踪脏页。

2.mmap映射的文件页

基本过程如下:

1)通过mmap映射共享文件。

2)第一次访问文件页时,发生缺页后读文件页到page cache, 如果是写访问则设置相应进程的页表项为脏、可写。

3)脏页回写时,会通过反向映射机制,查找映射这个页的每一个vma, 设置相应进程的页表项为只读,清脏标记。

4)假如第二次写访问这个文件页时,脏页的处理有两种情况:

  • page cache中的文件页还未回写到磁盘(3步骤之前), 此刻,这个文件页依然是脏页。因为相应进程的页表项为脏、可写,所以可以直接写这个页。
  • page cache中的文件页已经回写到磁盘(3步骤之后), 此刻,这个文件页不再是脏页。因为页表项为只读,所以写访问会发生写时复制缺页异常,异常处理中将处理共享文件页映射,重新将相应进程的页表项为设置为脏、可写。

分析如下:

2.1 第一次写访问文件页时

如果是mmap映射文件页,在没有填充页表情况下,写访问会发生转换表错误类型的缺页异常。

//mm/memory.c  
handle_pte_fault
   ->do_fault  
       ->do_shared_fault  
           ->__do_fault  //读文件页到page cache
               ->do_page_mkwrite  
                   ->vmf->vma->vm_ops->page_mkwrite()  
                       ->filemap_page_mkwrite, //对于ext2
                               ->set_page_dirty(page)  
                                   ->__set_page_dirty_buffers
                                       ->__set_page_dirty//page cache中标记页为脏  
                                           ->TestSetPageDirty(page) //设置页描述符脏标记
               ->finish_fault  //设置页表项
                   ->alloc_set_pte
                       ->if (write)  
                           entry = maybe_mkwrite(pte_mkdirty(entry), vma) //设置页表项脏、可写
1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.

2.2 脏页回写时

//mm/page-writeback.c  
write_cache_pages
->clear_page_dirty_for_io(page) //对于回写的每一个页
   ->page_mkclean(page) //清脏标记  mm/rmap.c  
       ->page_mkclean_one //反向映射查找这个页的每个vma,调用清脏标记和写保护处理
               ->entry = pte_wrprotect(entry);     //写保护处理,设置只读
                   entry = pte_mkclean(entry); //清脏标记 set_pte_at(vma->vm_mm, address, pte, entry) //设置到页表项中
   ->TestClearPageDirty(page) //清页描述符脏标记
1.2.3.4.5.6.7.8.

2.3 第二次写访问文件页时

1)脏页还没有回写时(确切的说是调用clear_page_dirty_for_io之前),页描述符已经设置了脏标记,页表项已经设置了脏标记、可写。

这时可以直接写访问文件页,不会发生缺页。

2)脏页已经回写时(确切的说是调用clear_page_dirty_for_io之后),页描述符已经清除了脏标记,页表项已经清除了脏标记,且只读。

这时写访问文件页会发生写时复制缺页异常(访问权限错误缺页)。

调用链如下:

//mm/memory.c  
handle_pte_fault  
->if (vmf->flags & FAULT_FLAG_WRITE) { //vma可写
           if (!pte_write(entry)) //页表项没有可写属性 return do_wp_page(vmf) //写时复制缺页异常处理
                   do_wp_page  
                           ->} else if (unlikely((vma->vm_flags & (VM_WRITE|VM_SHARED)) == (VM_WRITE|VM_SHARED))) { //是共享可写的文件映射vma  
                                       return   wp_page_shared(vmf);
                                       ->do_page_mkwrite  
                                           ->vmf->vma->vm_ops->page_mkwrite()
                                               ->filemap_page_mkwrite, //对于ext2                                                              ->set_page_dirty(page)
                                                              ->__set_page_dirty_buffers  //page cache中标记页为脏  
                                                                    ->TestSetPageDirty(page) //设置页描述符脏标记
                                                 ->finish_mkwrite_fault
                                                       ->wp_page_reuse
                                                                   ->entry = maybe_mkwrite(pte_mkdirty(entry), vma) //重新设置页表项脏、可写
1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.

2.4 再次写访问

重复上面步骤。

3.write接口操作的文件页

由于通过write接口访问文件页时,会读取文件页到page cache,不会映射到任何进程地址空间,所有这种方式跟踪脏页是通过设置/清除页描述符脏标记来实现。

3.1 第一次写访问文件页时

会首先读文件页到page cache,然后将用户空间写缓冲区数据写到page cache,调用链如下:

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() //写之前处理 分配page cache页                                               ->iov_iter_copy_from_user_atomic //户空间写缓冲区数据写到page cache页       -> a_ops->write_end() //写之后处理
                   ->block_write_end  
                           ->__block_commit_write
                                   ->mark_buffer_dirty
                                           if (!TestSetPageDirty(page)) {  //设置页描述符脏标记                                                        ->__set_page_dirty  //设置页为脏(设置页描述符脏标记)
1.2.3.4.5.6.7.8.9.

3.2 脏页回写时

write_cache_pages  //mm/page-writeback.c  
->clear_page_dirty_for_io  
       ->TestClearPageDirty(page) //清除页描述符脏标记
1.2.3.

3.3 第二次写访问文件页时

脏页回写之前,页描述符脏标志位依然被置位,等待回写, 不需要设置页描述符脏标志位。

脏页回写之后,页描述符脏标志位是清零的,文件写页调用链会设置页描述符脏标志位。

4.总结

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接口写访问时,再次通过文件系统的写文件的调用链会再次设置页描述符脏标志位,如此重复,从而跟踪了脏页。

原创文章,作者:晴川运维,如若转载,请注明出处:https://baike.qcidc.com/12174.html

(0)
晴川运维晴川运维
上一篇 4天前
下一篇 4天前

相关推荐

  • 细说nginx负载均衡

    Nginx是一个高性能的HTTP和反向代理服务器,也是一个IMAP/POP3/SMTP代理服务器,Nginx是一款轻量级的Web服务器/反向代理服务器以及电子邮件代理服务器,并在一…

    Linux系统 2025年7月6日
  • 使用python实现斐波那契数列

    斐波那契数列(Fibonacci sequence),又称黄金分割数列、因数学家列昂纳多·斐波那契(Leonardoda Fibonacci1)以兔子繁殖为例子而引入,故又称为“兔…

    Linux系统 2025年7月3日
  • 详解Linux下端口映射

    端口映射器可实现端口映射功能,一个主要用途就是可以在局域网的网关上运行本程序,将网关服务器的某个端口映射为局域网中某台机器的某个端口,这样,局域网外部访问网关服务器的8081端口就…

    Linux系统 2025年6月21日
  • 详解Sass 变量

    Sass(Syntactically Awesome StyleSheets)是css的一个扩展开发工具,它允许你使用变量、条件语句等,使开发更简单可维护。 变量用于存储一些信息,…

    Linux系统 2025年6月10日
  • Linux中Postfix反病毒和垃圾邮件工具(十)

    amavisd-new amavisd-new呼叫器是一个连接MTA和内容检测工具(诸如病毒扫描工具和SpamAssassin)的高性能接口程序,使用perl语言写成。它一般通过S…

    Linux系统 2025年6月28日
  • Linux下ulimit具体使用方法

    ulimit 是一个计算机命令,用于shell启动进程所占用的资源,可用于修改系统资源限制,本篇文章重点为大家分享一下Linux下ulimit具体使用方法,有需要的小伙伴可以参考一…

    Linux系统 2025年7月3日
  • bios关闭网卡具体流程

    华硕电脑开机时显示一连串字符并且没办法正常进入系统中,多次重启都出现这样的状况。经过查询,所显示的内容表示电脑从网卡启动,一般提示的内容是“PXE-MOF:Exiting PXE …

    Linux系统 2025年6月11日
  • Python3 re模块match与search

    正则表达式(regular expression)描述了一种字符串匹配的模式(pattern),可以用来检查一个串是否含有某种子串、将匹配的子串替换或者从某个串中取出符合某个条件的…

    Linux系统 4天前
  • Linux下使用JMeter进行压力测试

    JMeter是Apache组织的开放源代码项目,它是功能和性能测试的工具,100%的用java实现,本篇文章重点为大家讲解一下Linux下运行JMeter具体方法。 准备工作 1.…

    Linux系统 2025年6月8日
  • win10下Linux子系统的安装具体步骤

    相信大家都会对Linux系统有一定程度的需求,但是目前所有Linux发行版的桌面环境实在是不太友好,并不能完全满足我们所有的学习和娱乐。于是我们会想到安装Windows+Linux…

    Linux系统 2025年6月12日

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注