计算机底层知识拾遗(六)理解页缓存page cache和地址空间address_space

在这篇计算机底层知识拾遗(五)理解块IO层 中讲了块缓存buffer cache块缓存,这篇说说页缓存page cache以及相关的地址空间address_space的要点。

在Linux 2.4内核中块缓存buffer cache和页缓存page cache是并存的,表现的现象是同一份文件的数据,可能即出现在buffer cache中,又出现在页缓存中,这样就造成了物理内存的浪费。Linux 2.6内核对两个cache进行了合并,统一使用页缓存在做缓存,只有极少数的情况下才使用到buffer cache。后面会说buffer cache和page cache的区别。先直观看看两者的容量是如何统计的。

在 /proc/meminfo中存储了当前系统的内存使用情况,比如下面这个例子,

Buffers表示buffer cache的容量

Cached表示位于物理内存中的页缓存page cache

SwapCached表示位于磁盘交换区的页缓存page cache

所以实际页缓存page cache的容量 =  Cached + SwapCached

buffer cache和page cache的区别

buffer cache是Unix和早期的Linux内核中主要的缓存组件。我们要理解的是不管是buffer cache还是page cache都是为了处理块设备和内存交互时高速访问的问题

1. buffer cache是面向底层块设备的,所以它的粒度是文件系统的块,块设备和系统采用块进行交互。块再转换成磁盘的基本物理结构扇区。扇区的大小是512KB,而文件系统的块一般是2KB, 4KB, 8KB。扇区和块之间是可以快速转换的

随着内核的功能越来越完善,块粒度的缓存已经不能满足性能的需要。内核的内存管理组件采用了比文件系统的块更高级别的抽象,页page,页的大小一般从4KB到2MB,粒度更大,处理的性能更高。所以缓存组件为了和内存管理组件更好地交互,创建了页缓存page cache来代替原来的buffer cache。

页缓存是面向文件,面向内存的。通过一系列的数据结构,比如inode, address_space, page,将一个文件映射到页的级别,通过page + offset就可以定位到一个文件的具体位置

2. buffer cache实际操作时按块为基本单位,page cache操作时按页为基本单位,新建了一个BIO的抽象,可以同时处理多个非连续的页的IO操作,也就是所谓的scatter/gather IO

3. buffer cache目前主要用在需要按块传输的场景下,比如超级块的读写等。而page cache可以用在所有以文件为单元的场景下,比如网络文件系统等等,缓存组件抽象了地址空间address_space这个概念来作为文件系统和页缓存的中间适配器,屏蔽了底层设备的细节

4. buffer cache可以和page cache集成在一起,属于一个page的块缓存使用buffer_head链表的方式组织,page_cache维护了一个private指针指向这个buffer_head链表,buffer_head链表维护了一个指针指向这个页page。这样只需要在页缓存中存储一份数据即可

5. 文件系统的inode实际维护了这个文件所有的块block的块号,通过对文件偏移量offset取模可以很快定位到这个偏移量所在的文件系统的块号,磁盘的扇区号。同样,通过对文件偏移量offset进行取模可以计算出偏移量所在的页的偏移量,地址空间address_space通过指针可以方便的获取两端inode和page的信息,所以可以很方便地定位到一个文件的offset在各个组件中的位置:

文件字节偏移量 --> 页偏移量 --> 文件系统块号 block  -->  磁盘扇区

页缓存page cache和地址空间address_space

上面比较page cache和buffer cache的时候基本把page cache的特点说了,它是面向内存,面向文件的。这正好说明了页缓存的作用,它位于内存和文件之间,文件IO操作实际上只和页缓存交互,不直接和内存交互

Linux内核使用page数据结构来描述物理内存页帧,内核创建了mem_map数组来表示所有的物理页帧,mem_map的数组项就是page。

page结构不仅表示了物理内存页帧,

1. 一些标志位flags来表示该页是否是脏页,是否正在被写回等等

2. _count, _mapcount表示这个页被多少个进程使用和映射

3. private指针指向了这个页对应的buffer cache的buffer_head链表,建立了页缓存和块缓存的联系

4. mapping指向了地址空间address_space,表示这个页是一个页缓存中页,和一个文件的地址空间对应

5. index是这个页在文件中的页偏移量,通过文件的字节偏移量可以计算出文件的页偏移量

页缓存实际上就是采用了一个基数树结构将一个文件的内容组织起来存放在物理内存page中。文件IO操作直接和页缓存交互。采用缓存原理来管理块设备的IO操作

一个文件inode对应一个地址空间address_space。而一个address_space对应一个页缓存基数树。这几个组件的关系如下

再看一下地址空间address_space的概念。address_space是Linux内核中的一个关键抽象,它是页缓存和外部设备中文件系统的桥梁,可以说关联了内存系统和文件系统,文件系统可以理解成数据源。

1. inode指向这个地址空间的宿主,也就是数据源

2. page_tree指向了这个地址空间对应的页缓存的基数树。这样就可以通过inode --> address_space -->  page_tree找打一个文件对应的页缓存页

读文件时,首先通过要读取的文件内容的偏移量offset计算出要读取的页,然后通过该文件的inode找到这个文件对应的地址空间address_space,然后在address_space中访问该文件的页缓存,如果页缓存命中,那么直接返回文件内容,如果页缓存缺失,那么产生一个页缺失异常,创业一个页缓存页,然后从磁盘中读取相应文件的页填充该缓存页,租后从页缺失异常中恢复,继续往下读。

写文件时,首先通过所写内容在文件中的偏移量计算出相应的页,然后还是通过inode找到address_space,通过address_space找到页缓存中页,如果页缓存命中,直接把文件内容修改更新在页缓存的页中。写文件就结束了。这时候文件修改位于页缓存,并没有写回writeback到磁盘文件中去。

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

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

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

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

关于文件IO我们常说两句话“普通文件IO需要复制两次,内存映射文件mmap复制一次”,"普通文件IO是堆内操作,内存映射文件是堆外操作"。我们来看一下这两句话。

对于普通文件需要复制两次,我们要理解到底是哪两次,大部分的书都没说清楚,只说是第一次复制是从磁盘到内存缓冲区,第二次是从内存缓冲区到进程的堆。这里的内存缓冲区实际上就是页缓存。

这篇文件Page Cache, the Affair Between Memory and Files中的几张图很形象,说明白了这中间实际发生底层操作。

加入一个进程render要读取一个scene.dat文件,实际发生的步骤如下

1. render进程向内核发起读scene.dat文件的请求

2. 内核根据scene.dat的inode找到对应的address_space,在address_space中查找页缓存,如果没有找到,那么分配一个内存页page加入到页缓存

3. 从磁盘中读取scene.dat文件相应的页填充页缓存中的页,也就是第一次复制

4. 从页缓存的页复制内容到render进程的堆空间的内存中,也就是第二次复制

最后物理内存的内容是这样的,同一个文件scene.dat的内容存在了两份拷贝,一份是页缓存,一份是用户进程的堆空间对应的物理内存空间

再来看看内存映射文件mmap只复制一次是如何做的,mmap只有一次页缓存的复制,从磁盘文件复制到也缓存中。

mmap会创建一个虚拟内存区域vm_area_struct,进程的task_struct维护着这个进程所有的虚拟内存区域信息,虚拟内存区域会更新相应的进程页表项,让这些页表项直接指向页缓存所在的物理页page。mmap新建的这个虚拟内存区域和进程堆的虚拟内存区域不是同一个,所以mmap是在堆外空间。

最后明确几个概念

1. 用户进程访问内存只能通过页表结构,内核可以通过虚拟地址直接访问物理内存。

2. 用户进程不能访问内核的地址空间,这里的地址空间指的是虚拟地址空间,这是肯定的,因为用户进程的虚拟地址空间和内核的虚拟地址空间是不重合的,内核虚拟地址空间必须特权访问

3. page结构表示物理内存页帧,同一个物理内存地址可以同时被内核进程和用户进程访问,只要将用户进程的页表项也指向这个物理内存地址。也就是mmap的实现原理。

参考资料:

Page Cache, the Affair Between Memory and Files

Linux Kernel: What is the major difference between the buffer cache and the page cache?

《深入Linux内核架构》

时间: 2024-08-28 09:51:47

计算机底层知识拾遗(六)理解页缓存page cache和地址空间address_space的相关文章

计算机底层知识拾遗(三)理解磁盘的机制

磁盘是一种重要的存储器,位于主存结构的下方,是永久存储的介质.在计算机底层知识拾遗(一)理解虚拟内存机制 这篇中说了虚拟内存是面向磁盘的,理解磁盘的工作原理对理解计算机的很多概念有很大的帮助.尤其是在数据库和分布式存储领域,要经常和磁盘打交道. 磁盘这块主要有几个部分的概念: 1. 磁盘的基本结构和工作原理 2. 如何在虚拟内存机制下与内存高效地交换数据 3. 磁盘如何保证数据存储的可靠性及故障恢复 磁盘的基本结构和工作原理 从单个磁盘来说,由一个个的同心圆组成,一个同心圆就是一个磁道,每个磁道

计算机底层知识拾遗(二)深入理解进程和线程

关于进程和线程,大家总是说的一句话是"进程是操作系统分配资源的最小单元,线程是操作系统调度的最小单元".这句话理论上没问题,我们来看看什么是所谓的"资源"呢. 什么是计算机资源 经典的冯诺依曼结构把计算机系统抽象成 CPU + 存储器 + IO,那么计算机资源无非就两种: 1. 计算资源 2. 存储资源 CPU是计算单元,单纯从CPU的角度来说它是一个黑盒,它只对输入的指令和数据进行计算,然后输出结果,它不负责管理计算哪些"指令和数据". 换句话

计算机底层知识拾遗(四)理解文件系统

操作系统的很多核心组件都是相互关联的,比如虚拟内存管理,物理内存管理,文件系统,缓存系统,IO,设备管理等等,都要放在一起来看才能从整体上理解各个模块到底是如何交互和工作的.这个系列的目的也就是从整体上来理解计算机底层硬件和操作系统的一些重要的组件是如何工作的,从而来指导应用层的开发.这篇讲讲文件系统的重要概念,为后面的IO系统做铺垫. 文件系统主要有三类 1. 位于磁盘的文件系统,在物理磁盘上存储文件,比如NTFS, FAT, ext3, ext4 2. 虚拟文件系统,在内核中生成,没有物理的

计算机底层知识拾遗(七)页缓存数据同步和页回收机制

这篇说说Linux的页缓存数据同步和页回收机制.数据同步和页回收是两个独立的概念,数据同步处理的是内存/缓存的数据和后备设备的数据一致问题,页回收处理的是在内存空间不足时如何回收已分配的物理内存页,来获得足够空间分配干净页,支持优先级更高的工作.数据同步在任意时刻都有可能触发,页回收则是在物理内存使用达到一定阀值的时候触发. 数据同步就是把物理内存和页缓存中的脏页写回到后备设备的文件中去.有两种方式可以调用数据同步 1. 周期性的调用,主要是pdflush机制 2. 强制调用,比如调用sync,

计算机底层知识拾遗(一)理解虚拟内存机制

这个系列会总结计算机,网络相关的一些重要的底层原理.很多底层原理大家上学的时候都学过,但是在学校的时候大部分的同学都是为了应付考试而学习,过几天全忘了.随着工作的时间越久,越体会到这些基础知识的重要性.做技术和练武功一样,当你到了一定的阶段,也会遇到一个瓶颈,突破了你的眼界就会大不同,突破不了,只能困在原地无法成长.我自己深有体会,这些基础知识,底层原理是助你打破瓶颈的灵丹妙药.当理解了一些底层原理之后,会发现现在很多热门技术,原理,常见的设计都是在底层基础上发展而来的. 这篇总结一下单机系统的

计算机底层知识拾遗(十)理解进程调度【转】

转自:http://www.cnblogs.com/zfyouxi/p/4504042.html 这篇说说内核的进程调度机制,进程调度是内核的一个重要工作,由调度器完毕. 进程状态 内核调度器调度的实体(KSE, kernal schedule entry)是进程和线程.内核必须知道全部进程和线程的状态,比方把时间片给一个堵塞的进程是没有意义的.从内核的角度来看,进程的状态有3种: 1. 执行,表示正在执行的进程 2. 等待,没有执行,可是等待时间片执行的进程 3. 睡眠,也就是堵塞,包含可中断

计算机底层知识

1 这篇万字长文,一下子把计算机底层知识说明白了 https://mp.weixin.qq.com/s?__biz=Mzg2NTA4OTUwOQ==&mid=2247486427&idx=3&sn=f03bc1a695a9e2f837673f3012d65c6c&chksm=ce5e2951f929a047413a06b85c26d898ca9126f1369147e35a763542d435134584987a4e45b6&mpshare=1&scene=

计算机基本知识拾遗(七)页面缓存数据的同步和恢复机制页

本讲座Linux页面缓存数据的同步和恢复机制页.数据同步和恢复是两个独立的页面概念.数据同步处理是存储器/数据一致性问题缓存数据和备份设备.页面回收是如何回收分配的内存空间不足的物理内存页.为了获得足够的空间来分配一个干净的页面,支持更高优先级的工作.步在随意时刻都有可能触发,页回收则是在物理内存使用达到一定阀值的时候触发. 数据同步就是把物理内存和页缓存中的脏页写回到后备设备的文件里去.有两种方式能够调用数据同步 1. 周期性的调用,主要是pdflush机制 2. 强制调用,比方调用sync,

从Java视角理解CPU缓存(CPU Cache)

从Java视角理解系统结构连载, 关注我的微博(链接)了解最新动态众所周知, CPU是计算机的大脑, 它负责执行程序的指令; 内存负责存数据, 包括程序自身数据. 同样大家都知道, 内存比CPU慢很多. 其实在30年前, CPU的频率和内存总线的频率在同一个级别, 访问内存只比访问CPU寄存器慢一点儿. 由于内存的发展都到技术及成本的限制, 现在获取内存中的一条数据大概需要200多个CPU周期(CPU cycles), 而CPU寄存器一般情况下1个CPU周期就够了. CPU缓存 网页浏览器为了加