我是靠谱客的博主 矮小皮卡丘,最近开发中收集的这篇文章主要介绍Linux中PageCache与文件IO,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

1.页缓存的作用

1.1 解决进程读取数据时,磁盘IO与内存IO的速度差异

1.2 解决不同进程之间的数据共享问题

 

2.页缓存、文件IO、内存之间的联系

2.1 读

所有的文件内容的读取(无论一开始是命中页缓存还是没有命中页缓存)最终都是直接来源于页缓存。

当将数据从磁盘复制到页缓存之后,还要将页缓存的数据通过CPU复制到read调用提供的缓冲区中,这就是普通文件IO需要的两次复制数据复制过程。

其中第一次是通过DMA的方式将数据从磁盘复制到页缓存中,本次过程只需要CPU在一开始的时候让出总线、结束之后处理DMA中断即可,中间不需要CPU的直接干预,CPU可以去做别的事情;第二次是将数据从页缓存复制到进程自己的的地址空间对应的物理内存中,这个过程中需要CPU的全程干预,浪费CPU的时间和额外的物理内存空间。

 

为了能够深入理解页缓存和文件IO操作之间的关系,假设系统中现在存在一个名为render的进程,该进程打开了文件scene.dat,并且每次读取其中的512B(一个扇区的大小),将读取的文件数据放入到堆分配的块中(每个进程自己的地址空间对应的物理内存)。先以普通IO为例介绍一下读取数据的过程,第一次读取的过程大致如图4

  进程发起读请求的过程如下:

        1.进程调用库函数read()向内核发起读文件的请求;

        2.内核通过检查进程的文件描述符定位到虚拟文件系统已经打开的文件列表项。

        3.通过文件表项链接到目录项模块,根据传入的文件路径在目录项中检索,找到该文件的inode;

        4.inode中,通过文件内容偏移量计算出要读取的页;

        5.通过该inode查找对应的页缓存节点;

        (1)如果页缓存节点命中,那么直接返回文件内容;

        (2)如果页缓存缺失,那么产生一个缺页异常,首先创建一个新的空的物理页框,通过该inode找到文件中该页的磁盘地址,读取相应的页填充该页缓存(DMA的方式将数据读取到页缓存),更新页表项;重新进行第5步的查找页缓存的过程;

        6.文件内容读取成功;

 

 

2.2 写

        由于页缓存的架构,当一个进程调用write系统调用的时候,对于文件的更新仅仅是被写到了文件的页缓存中,相应的页被标记为dirty。具体过程如下:

        前面5步和读文件是一致的,在address_space中查询对应页的页缓存是否存在:

        6.如果页缓存命中,直接把文件内容修改写在页缓存的页中。写文件就结束了。这时候文件修改位于页缓存,并没有写回到磁盘文件中去。

        7.如果页缓存缺失,那么产生一个页缺失异常,创建一个页缓存页,同时通过inode找到该文件页的磁盘地址,读取相应的页填充页缓存。此时缓存页命中,进行第6步。

        普通的IO操作需要将写的数据从自己的进程地址空间复制到页缓存中,完成对页缓存的写入;但是mmap通过虚拟地址(指针)可以直接完成对页缓存的写入,减少了从用户空间到页缓存的复制。        

        由于写操作只是写到了页缓存中,因此进程并没有被阻塞到磁盘IO发生,因此当计算机崩溃的时候,写操作所引起的改变可能并没有发生在磁盘上。所以,对于一些要求严格的写操作,比如数据库系统,就需要调用fsync等操作及时将数据同步到磁盘上(虽然这中间也可能存在磁盘的驱动程序崩溃的情况)。读操作与写不同,一般会阻塞到进程读取到数据(除非调用非阻塞IO,即使使用IO多路复用技术也是将进程阻塞在多个监听描述符上,本质上还是阻塞的)。为了减轻读操作的这种延迟,linux操作系统的内核使用了"预读"技术,也就是当从磁盘中读取你所需要的数据的时候,内核将会多读取一些页到页缓存中。

        普通文件IO中所有的文件内容的读取(无论一开始是命中页缓存还是没有命中页缓存)最终都是直接来源于页缓存。当将数据通过缺页中断从磁盘复制到页缓存之后,还要将页缓冲的数据通过CPU复制到read调用提供的缓冲区中。这样,必须通过两次数据拷贝过程,才能完成用户进程对文件内容的获取任务。写操作也是一样的,待写入的buffer在用户空间,必须将其先拷贝到内核空间对应的主存中,再写回到磁盘中,也是需要两次数据拷贝。mmap的使用减少了数据从用户空间到页缓存的复制过程,提高了IO的效率,尤其是对于大文件而言;对于比较小的文件而言,由于mmap执行了更多的内核操作,因此其效率可能比普通的文件IO更差。

 

  那么此时存在另一个问题就是当最后一个render进程退出之后,存储scene.dat的页缓存是不是会被马上释放掉?当然不是!在一个进程中打开一个文件使用完之后该进程退出,然后在另一个进程中使用该文件这种情况通常是很常见的,页缓存的管理中必须考虑到这种情况。况且从页缓存中读取数据的时间是ns级别,但是从硬盘中读取数据的时间是ms级别,因此如果能够在使用数据的时候命中页缓存,那么对于系统的性能将非常有帮助。那么,问题来了,什么时候该文件对应的页缓存要被换出内存呢?就是系统中的内存紧张,必须要换出一部分物理页到硬盘中或者交换区中,以腾出更多的空间给即将要使用的数据的时候。所以只要系统中存在空闲的内存,那么页缓存就不会被换出,直到到达页缓存的上限为止。是否换出某一页缓存不是由某一个进程决定的,而是由操作系统在整个系统空间中的资源分配决定的。毕竟,从页缓存中读取数据要比从硬盘上读取数据要快的多了。

 

 

3. 页缓存中数据如何实现和后备存储之间的同步?

       普通文件IO,都是将数据直接写在页缓存上,那么页缓存中的数据何时写回后备存储?怎么写回?

何时写回

        1.空闲内存的值低于一个指定的阈值的时候,内核必须将脏页写回到后备存储以释放内存。因为只有干净的内存页才可以回收。当脏页被写回之后就变为PG_uptodate标志,变为干净的页,内核就可以将其所占的内存回收;

        2.当脏页在内存中驻留的时间超过一个指定的阈值之后,内核必须将该脏页写回到后备存储,以确定脏页不会在内存中无限期的停留;

        3.当用户进程显式的调用fsync、fdatasync或者sync的时候,内核按照要求执行回写操作。

由谁写回

        为了能够不阻塞写操作,并且将脏页及时的写回后备存储。linux在当前的内核版本中使用了flusher线程负责将脏页回写。

        为了满足第一个何时回写的条件,内核在可用内存低于一个阈值的时候唤醒一个或者多个flusher线程,将脏页回写;

        为了满足第二个条件,内核将通过定时器定时唤醒flusher线程,将所有驻留时间超时的脏页回写。

最后

以上就是矮小皮卡丘为你收集整理的Linux中PageCache与文件IO的全部内容,希望文章能够帮你解决Linux中PageCache与文件IO所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(44)

评论列表共有 0 条评论

立即
投稿
返回
顶部