[内核内存] page cache

文章目录

  • 1 page cache 和 buffer cache
  • 2 page cache
  • 3 带page cache的文件读写基本流程(转)
    • 3.1 读文件
    • 3.2 写文件
    • 3.3 小结

1 page cache 和 buffer cache

linux在I/O的过程中,磁盘读写数据的速度远远低于内存读写数据的速度。因此为了加快内存处理磁盘数据的速度,linux将从磁盘中读取的数据缓存到内存的某一空闲区域。这些缓存磁盘数据的内存区域被称为缓冲区。

在linux不支持虚拟内存机制前linux不存在页的概念,内存读取磁盘数据以设备块为单位,因而linux I/O操作以块为单位对磁盘数据进行缓存,我们将以块为单位的缓冲区称为buffer cache。linux应用虚拟内存机制管理内存后,页作为了虚拟内存管理的最小单元,内存缓冲区可以采用以页为单来对磁盘数据进行缓存,我们将以页为单位的缓冲区称为page cache。

由上我们可以得出page cache 和buffer cache两者最大区别在与缓存的粒度大小不一致:

  1. buffer cache面向的是虚拟文件系统(VFS)对应的块,是对原始磁盘块数据的临时存储,也就是用来缓冲磁盘的数据。通过buffer cache,内核可以把分散的写集中起来统一优化磁盘的写入,例如把多次小的写合并成单次大的写等等。
  2. page cache面向的是内存管理系统中比VFS块粒度更小的更高级抽象—页(page)。page cache是从磁盘读取文件的页缓存,就是用来缓存从文件读取的数据。这样下次访问这些文件时,可以直接从内存中快速获取,而不需要访问缓慢的磁盘。通常和内存管理交互的缓存都使用的是page cache。

2 page cache

linux中几乎所有的文件I/O操作都会依赖于page cache,只有当O_DIRECT标志置位才跳过page cache使用buffer cache.也就是说linux系统的文件I/O往往只会和page cache进行交互,并不会直接和系统内存交互。上文提到page cache是以内存管理系统中的页(page)为单位进行数据缓存的,那么page cache是如何实现将一个文件映射到内存页中的呢?实际上linux通过struct inode,struct address_space和struct page这3个数据结构将一个磁盘文件中一个页的数据映射到一个内存页中,对应关系如图1所示。

在这里插入图片描述

1.inode--address_space--page映射关系
  1. inode:linux系统中当一个磁盘文件被打开后,内核的VFS会在内存中为该文件创建一个struct inode结构实例,该实例中的i_mapping域会指向一个address_space结构,于是一个内存中已打开的文件就会对应一个address_space结构。同时VFS中的inode结构和文件所在磁盘中的inode一一对应,所以可以很方便通过VFS中的inode函数快速定位到文件所在磁盘的具体物理位置。
  2. address_space:是linux page cache中的一个关键抽象,它用来维护一个文件缓存在内存中所有文件缓存页的描述符struct page。address_space中的host成员指向对应文件在VFS中的inode结构。而address_space中的page_tree成员是一个struct radix_tree_root结构体,指向一个基数树的根,page_tree指向的基数树中存储大量物理内存页的描述符struct page,这些page指向的内存页存储着address_space对应文件中的相关数据。利用page_tree基数树我们只需要通过相关的文件偏移就能很快定位到文件该段数据缓存到内存中的哪个物理页中(引入基树page_tree的原因就是为了能够通过偏移量快速索引到需要的struct page数据)。
  3. page:struct page用于描述一个内存物理页:
    1. 若page中的mapping成员不为0且第0位不等于0,则该page是一个文件缓存页(或者文件映射页,文件映射页和文件缓存页一样都是文件页,在磁盘上都有后备文件关联数据。但用户态进程不能直接通过虚拟地址访问文件缓存页,只能将文件缓存页中的数据拷贝到进程虚拟地址映射的物理页中才可以访问。而用户态进程可以通过mmap函数直接将虚拟地址映射到文件映射页上进行访问)。
    2. 对于一个文件缓存页mapping成员指向一个文件VFS中的address_space空间。对于文件缓存页linux可以通过page的mapping成员定位到该文件在磁盘中的具体位置(address_space中的host成员指向文件在VFS中的inode结构),然后通过page中的index确定该页在文件中的具体位置。注意这个index对于的偏移量是相对于该address_space中的偏移并不是相对于磁盘文件中的偏移,因为address_space可能只维护了文件中的一段空间,所以在定位文件具体位置时还要计算address_space相对于文件的偏移量。
    3. stucet page中的flags标志位记录着该文件缓存页是否为脏页和是否正在回写等信息。

结合上面的描述下面大致介绍一下文件页缓冲机制建立的流程:

  1. 若一磁盘文件中的A区域要第一次与物理内存交互数据,首先linux会通过VFS在内存中建立相应的数据结构来维护对应的磁盘文件,让内核能在内存中对磁盘文件A区域进行读写操作。这其中VFS会建立一个inode数据结构与磁盘文件的inode一一对应,还会创建一个address_space结构用来控制内存与磁盘文件A区域的数据交互,并将VFS中文件的address和自身的inode相关联。
  2. 接着linux内存管理系统会在内核态分配多个物理内存页。
  3. 然后将交互文件A区域中的数据通过相关方法拷贝到刚分配的那些物理内存页中,此时那些物理内存页称为文件缓存页,它们构成的内存区域称为页缓冲区(page cache)。
  4. 最后page cache通过刚分配的address_space数据结构来维护上面提到的这些文件缓存页,具体做法是将这些物理页的描述符strcut page按照它们在磁盘文件中对应的先后顺序逐个添加到address_space的page_tree成员指向的基数树上(参考图1)。

文件的页缓冲机制建立起来后。若内存想读取该磁盘文件A区域的数据,内核会跳过磁盘直接从内存page cahe区域对应的文件缓存页中进行数据拷贝,以此加快数据读取速度。同样若内存想往磁盘文件的A区域写入数据时,内核会先将数据写到page cache区域对应的文件缓存页中,被写入数据的文件缓存页会被标记为脏页,可以采用延迟和分段的方法将这些脏页数据写到磁盘文件A区域中,这样既加快了内存写入磁盘操作的速度,还能避免磁盘因短时过多数据涌入而造成的损坏。

ps:脏文件缓存页延迟写入磁盘的方法:

  1. 手动调用sync()或者fsync()系统调用把脏页写回
  2. pdflush进程会定时把脏页写回到磁盘

3 带page cache的文件读写基本流程(转)

3.1 读文件

  1. 进程通过库函数向文件发起读文件请求
  2. 内核通过检查进程的文件描述符定位到虚拟文件系统的已打开文件列表表项
  3. 调用该文件可用的系统调用函数read()
  4. read()函数通过文件表项链接到目录项模块,根据传入的文件路径,在目录项模块中检索,找到该文件的inode;
  5. 在inode中,通过文件内容偏移量计算出要读取的页;
  6. 通过inode找到文件对应的address_space;
  7. 在address_space中访问该文件的页缓存树,查找对应的页缓存结点:
    1. 如果页缓存命中,那么直接返回文件内容;
    2. 如果页缓存缺失,那么产生一个页缺失异常,创建一个页缓存页,同时通过inode找到文件该页的磁盘地址,读取相应的内容填充该缓存页
  8. 返回的文件内容存储到内核为进程创建的物理页中(通过进程虚拟地址访问该物理页)

3.2 写文件

  1. 进程调用库函数向内核发起写文件请求;
  2. 内核通过检查进程的文件描述符定位到虚拟文件系统的已打开文件列表表项;
  3. 调用该文件可用的系统调用函数write()
  4. write()函数通过文件表项链接到目录项模块,根据传入的文件路径,在目录项模块中检索,找到该文件的inode;
  5. 在inode中,通过文件内容偏移量计算出要读取的页;
  6. 通过inode找到文件对应的address_space;
  7. 如果页缓存命中,直接把文件内容修改更新在页缓存的页中。写文件就结束了。这时候文件修改位于页缓存,并没有写回到磁盘文件中去。
  8. 如果页缓存缺失,那么产生一个页缺失异常,创建一个页缓存页,同时通过inode找到文件该页的磁盘地址,读取相应的页填充该缓存页。此时缓存页命中,进行第6步。

一个页缓存中的页如果被修改,那么会被标记成脏页。脏页需要写回到磁盘中的文件块。有两种方式可以把脏页写回磁盘:

(1)手动调用sync()或者fsync()系统调用把脏页写回

(2)pdflush进程会定时把脏页写回到磁盘

同时注意,脏页不能被置换出内存,如果脏页正在被写回,那么会被设置写回标记,这时候该页就被上锁,其他写请求被阻塞直到锁释放。

3.3 小结

带page cache的常规文件操作,因为在读文件时需要将磁盘文件数据拷贝到页缓存,而页缓存页处于内核空间内,不能直接被用户进程进行寻址,所以还需要将页缓存中的数据再次拷贝到用户空间对应的内存空间中,这样经过两次数据拷贝,用户进程才完成了对磁盘文件的读取任务。磁盘文件写操作也是一样需要进行两次数据拷贝操作,用户进程用户态内存数据需要先写入内核态页缓冲区中,然后将页缓冲区中的数据写回磁盘文件中。为了提高用户进程对磁盘数据处理效率,对于大型磁盘文件处理许多用户进程通过mmap函数来操纵文件,mmap直接将用户空间主存中的物理页作为文件页来与磁盘文件数据页进行映射,而放弃使用页缓存机制(mmap操纵磁盘数据只需进行一次数据拷贝操作)。

ps:最近在网上看到一张很牛叉的诠释page cache原理的示意图然后我将该图重构了一下,下面贴出方便大家理解page cache。

在这里插入图片描述

2 page cache原理示意图

知识来源

  1. https://www.cnblogs.com/huxiao-tee/p/4657851.html
  2. https://www.zhihu.com/question/52639327

你可能感兴趣的:(linux内存,page,cache,页缓存)