dirty file page

2024-01-12 22:04
文章标签 file page dirty

本文主要是介绍dirty file page,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

转自:https://www.cnblogs.com/zhiminyu/p/17330763.html

0.前言
Linux 内核Page Cache 和Buffer Cache 关系及演化历史 一文中讲过Linux 2.4之后将Page Cache和Buffer Cache 进行了融合,在buffer_head 中添加了b_page,很容易就能找到缓存的Page Cache,而buffer_head 的存在就是能够快速确定页中的一个块在磁盘中的地址。

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

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

访问文件页有两种方式:

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

1. 引入脏页提高性能
Linux在设计IO模块的时候,认为读写操作的紧迫性是不一样的,读操作的紧迫性要高于写操作。所以Linux的写默认是延迟的,因为这样不仅仅可以显著的提高效率:

写操作合并:多次写操作才会真正的发起一次写物理磁盘,大幅提升IO性能;
IO栈中还有通用块层和IO调度层,在具体的文件系统层有文件在文件系统中的逻辑块映射,也就是文件的文件块在磁盘上对应的磁盘块的地址,这时候在通用块层会把这些信息封装成BIO对象,然后提交到gendisk->request_queue,在提交的过程中,如果BIO和已有的request中的BIO在物理地址上是连续的,那么这些BIO就会合并成一个request。在IO调度层,一次request就意味着一次IO,因为这时候磁盘需要重新寻址,所以这个合并其实是在io调度层入队列的时候完成的;
你可以通过iostat查看物理盘IO情况的时候看到对应的监控指标:rrqm/s、wrqm/s等;
读操作比写操作更紧迫:
通常延迟写物理磁盘不会导致进程挂起,写到page cache然后修改该页的PG_dirty标志即可返回(如果open的时候设置了sync标志或者调用了fsync、sync函数,那么会将写page cache的内容封装成一个IO request放到IO调度层的request_queue中,然后由调度层的调度算法来写到物理磁盘);
但是如果读操作读的时候page cache中没有缓存数据,读操作就需要去读物理磁盘,此时延迟读就会让用户感知,这就很糟糕了,用户就会感觉OS卡顿,Linux作为桌面系统的时候,他还是要优先保证和用户良好的交互性;
你还可以从IO scheduler的调度算法deadline的默认配置感受一下,读延迟是500ms,写延迟是5s,这就很明显的感受到Linux对读写操作紧迫性的区别对待;
2. mmap 映射的文件页
基本过程如下:

step1,通过mmap映射共享文件;
step2,第一次访问文件页时,发生缺页后读文件页到page cache,如果是写访问则设置相应进程的页表项为脏、可写;
step3,脏页回写时,会通过反向映射机制,查找映射这个页的每一个vma, 设置相应进程的页表项为只读,清脏标记;
step4,假如第二次写访问这个文件页时,脏页的处理有两种情况:
page cache中的文件页还未回写到磁盘(step3 之前), 此刻,这个文件页依然是脏页。因为相应进程的页表项为脏、可写,所以可以直接写这个页;
page cache中的文件页已经回写到磁盘(step3 之后), 此刻,这个文件页不再是脏页。因为页表项为只读,所以写访问会发生写时复制缺页异常,异常处理中将处理共享文件页映射,重新将相应进程的页表项为设置为脏、可写
2.1 第一次写访问文件页

/mm/memory.chandle_pte_fault->do_fault->do_shared_fault|->__do_fault //读文件页到page cache|->do_page_mkwrite|    ->vmf->vma->vm_ops->page_mkwrite|        ->filemap_page_mkwrite /mm/filemap.c|            ->set_page_dirty|                ->__set_page_dirty_buffers|                    ->TestSetPageDirty  //设置页描述符脏标记|                    ->__set_page_dirty  //page cache中标记页为脏 |->finish_fault  //设置页表项->alloc_set_pte->maybe_mkwrite  //设置页表项脏、可写

2.2 脏页回写

 1 /mm/page_writeback.c2  3 write_cache_pages4  5     ->clear_page_dirty_for_io  //对于回写的每一个页6  7         |->page_mkclean  //清脏标记  mm/rmap.c 8  9         |   ->page_mkclean_one  //反向映射查找这个页的每个vma,调用清脏标记和写保护处理
10  
11         |        ->entry = pmdp_invalidate(vma, address, pmd);
12         |          entry = pmd_wrprotect(entry);  //写保护处理,设置只读
13         |           entry = pmd_mkclean(entry);  //清脏标记 set_pte_at(vma->vm_mm, address, pte, entry) //设置到页表项中
14  
15         |->set_page_dirty

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

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

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

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

调用链如下:

 1 /mm/memory.c2  3 handle_pte_fault4  5     ->if (vmf->flags & FAULT_FLAG_WRITE) {  //vma可写6           if (!pte_write(entry))  //页表项没有可写属性7               do_wp_page(vmf)  //写时复制缺页异常处理8  9                   ->if (unlikely((vma->vm_flags & (VM_WRITE|VM_SHARED))  //是共享可写的文件映射vma
10                         wp_page_shared
11  
12                             |->do_page_mkwrite
13  
14                             |    ->vmf->vma->vm_ops->page_mkwrite
15  
16                             |        ->filemap_page_mkwrite /mm/filemap.c
17  
18                             |            ->set_page_dirty
19  
20                             |                ->__set_page_dirty_buffers
21  
22                             |                    ->TestSetPageDirty  //设置页描述符脏标记
23  
24                             |                    ->__set_page_dirty  //page cache中标记页为脏 
25                             |->finish_mkwrite_fault
26  
27                                  ->wp_page_reuse
28  
29                                      ->entry = maybe_mkwrite(pte_mkdirty(entry), vma);  //重新设置页表项脏、可写

2.4 再次写访问
重复上面步骤

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

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

 1 /fs/ext4/file.c2  3 ext4_file_write_iter4  5     ->__generic_file_write_iter6  7         ->generic_perform_write8  9             ->a_ops->write_begin   //写之前处理 分配page cache页
10  
11                 ->__block_commit_write
12  
13                     ->mark_buffer_dirty
14  
15                         ->__set_page_dirty  //设置页为脏(设置页描述符脏标记)
16  
17             ->iov_iter_copy_from_user_atomic  //用户空间写缓冲区数据写到page cache页

3.2 脏页回写

1 write_cache_pages  //mm/page-writeback.c 
2  
3 ->clear_page_dirty_for_io 
4  
5         ->TestClearPageDirty(page) //清除页描述符脏标记

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

这篇关于dirty file page的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/599302

相关文章

Open a folder or workspace... (File -> Open Folder)

问题:vscode Open with Live Server 时 显示Open a folder or workspace... (File -> Open Folder)报错 解决:不可以单独打开文件1.html ; 需要在文件夹里打开 像这样

android java.io.IOException: open failed: ENOENT (No such file or directory)-api23+权限受权

问题描述 在安卓上,清单明明已经受权了读写文件权限,但偏偏就是创建不了目录和文件 调用mkdirs()总是返回false. <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/><uses-permission android:name="android.permission.READ_E

bash: arm-linux-gcc: No such file or directory

ubuntu出故障重装了系统,一直用着的gcc使用不了,提示bash: arm-linux-gcc: No such file or directorywhich找到的命令所在的目录 在google上翻了一阵发现此类问题的帖子不多,后来在Freescale的的LTIB环境配置文档中发现有这么一段:     # Packages required for 64-bit Ubuntu

编译linux内核出现 arm-eabi-gcc: error: : No such file or directory

external/e2fsprogs/lib/ext2fs/tdb.c:673:29: warning: comparison between : In function 'max2165_set_params': -。。。。。。。。。。。。。。。。。。 。。。。。。。。。。。。。 。。。。。。。。 host asm: libdvm <= dalvik/vm/mterp/out/Inte

file-max与ulimit的关系与差别

http://zhangxugg-163-com.iteye.com/blog/1108402 http://ilikedo.iteye.com/blog/1554822

瑞芯微Parameter File Format解析

Rockchip android系统平台使用parameter文件来配置一些系统参数 主要包含:串口号:nandflash分区 固件版本,按键信息等; 如下是台电P98HD的parameter参数: FIRMWARE_VER:4.1.1        // 固件版本 //固件版本,打包 updata.img 时会使用到,升级工具会根据这个识别固件版本。 //Boot loader 会读取

vue中路由管理(vue-router,page)使用总结

现在的项目都以模块化的方式去开发,所以在这样的开发模式下,如何更好的去管理路由是开发中所需要考虑的重点,幸运的是当前的开发中已经有了成熟的中间件去管理,我们只需要用就可以了 下面是我在学习vue-router的时候在原来基础上修改出来的demo,也是为了有助于对vue-router的理解 首先理解下vue官网的一个示例demo https://jsfiddle.net/yyx990803/x

error while loading shared libraries: libnuma.so.1: cannot open shared object file:

腾讯云CentOS,安装Mysql时: 1.yum remove libnuma.so.1 2.yum install numactl.x86_64

Vue3图片上传报错:Required part ‘file‘ is not present.

错误 "Required part 'file' is not present" 通常表明服务器期望在接收到的 multipart/form-data 请求中找到一个名为 file 的部分(即文件字段),但实际上没有找到。这可能是因为以下几个原因: 请求体构建不正确:在发送请求时,可能没有正确地将文件添加到 FormData 对象中,或者使用了错误的字段名。 前端代码错误:在前端代码中,可能

File 的创建

在做音频录制的时候,用到创建一个音频文件,所以用到了如下方法 File file = new File(Environment.getExternalStorageDirectory().getAbsolutePath()+"/"+ System.currentTimeMillis()+".mp3");if (!file.exists()){file.createNewFile();} 可是