使用PageCache读取文件元数据块

探讨Linux2.4中PageCache与BufferCache的统一管理机制,解释如何通过PageCache读取文件元数据块,以及BufferCache在这一过程中的角色转变。

使用PageCache读取文件元数据块

Linux 2.4的最大贡献是统一的PageCache与BufferCache,准确来说,它是讲所有数据都保存在了PageCache中,但是仍然保留了BufferCache的接口,以供如superblocks,bitmap,inode table,block table等文件元数据读写的使用。也即,与块设备交互时,我们依然使用bh抽象,但是,bh不会有自己的空间,其使用的是PageCache中对应页的空间。具体文件系统内部使用时,会在页上提供Buffer head抽象,从而,具体文件系统可以使用block抽象。

那么问题来了,我们提到过,PageCache缓存的是文件页,它将每个文件的内容抽象为一个地址空间(address_space),并在此空间上读写,可以元数据并不在文件上啊?为了解决这个问题,内核将一个块设备也看作了一个文件,这个块设备上的数据是一个地址空间。于是乎,当我们需要读写superblock时,我们只需要读写这个特殊文件的第一个页的第一个块。

例子,ext2文件系统的read_super函数,最终会调用bread(dev, 1, block_size),熟悉BufferCache的同学应该了解,bread是BufferCache的一个非常重要的接口。虽然BufferCache的接口依然保留,但是其内部实现已经化归到PageCache上了。

struct buffer_head* bread(kdev_t dev, int block, int size)
{
    struct buffer_head* bh;

    bh = getblk(dev, block, size);
    if (buffer_uptodate(bh))
	return bh;
    set_bit(BH_Sync, &bh->b_state);
    ll_rw_block(READ, 1, &bh);
    wait_on_buffer(bh);
    if (buffer_uptodate(bh))
	return bh;
    brelse(bh);
    return NULL;
}

对比之前的代码,之个函数可以说是毫无变化,但是,我们提到,BufferCache构建于PageCache之上,所以,这里的getblk是关键。默认的,它会直接查找BufferCache的hash表,如果存在,直接返回,如果不存在,它会调用grow_buffers(dev, block, size)来分配为此dev的block来分配Page,并创建配置buffer_head。具体过程如下(可以把getblk和grow_buffers一路看下去)。

  • 如果bh已经在BufferCache中了,直接返回,否则
  • 从PageCache中取出或者新建block所在页
    • 锁PageCache
    • 从dev的特殊inode的mapping中获取block所在的页
    • 放锁PageCache
    • 如果页不存在,分配一个新页,对PageCache加锁,将其放入PageCache
    • 如果页存在,锁定它
    • 注意,此时,页的引用计数为二,一是PageCache的,一是当前操作持有的
  • 为页分配若干buffer_head,将buffer_head的data指向页上的空间(一般的,一个页是4k,因此,我们分配4个bh,将其data指针分别页上的对应区域)
  • 将页上的bh link起来,并将页引用加一,此时,引用为三!
  • 将页上的bh放入BufferCache的hash表中
  • 解除页锁定
  • 释放页引用:此时,页的引用为二,一是PageCache所持有的,一是BufferCache所持有的

 

至此,我们成功地将所有缓存空间都由PageCache统一化管理了,但是,我们依然保留有bh的接口,因为目前对块设备进行读写时,我们依赖使用bh抽象。BufferCache提供了对bh对象(类似一个句柄吧,其实际空间在PageCache的页上)的管理,bh对象不对单独出现,它一定属于某个具体的文件页,无论是文件系统文件的页,还是表现块设备的特殊的文件的页。

是不是觉得这很绕?没错,对于内核的改动是渐进的,我们无法一下子完成去掉BufferCache,因此这一方面涉及到块设备的改动,一方面涉及到所有具体文件系统的改动,内核维护者只能以这样一个渐进的方式来处理此问题。


文件地址空间操作的实现

不同具体文件系统对address_space_operations的实现有所不同,这里,我们将分析默认版本,即基于块设备的文件系统的实现。相关代码主要在fs/buffer.c中。

 

readpage

基于块设备的readpage实现是block_read_full_page,它的参数是一个page,即要读的页;另一个是get_block策略函数,即,将页所对应的文件块的逻辑块index,转换成块在块设备中的index。每个文件系统会提供不同的get_block策略函数,因属于文件系统对文件的空间分配的部分。

 

  • 操作开始时
    • 页的引用至少为二,一是PageCache的,一是执行操作的线程的
    • 页处于锁定状态
  • 如果页当前没有配置bh,为其创建bh(创建好后,引用会额外加一,表示此页已经被BufferCache使用了)
  • 根据get_block策略,设置block的物理index
  • 接下来的操作就没有什么了,因此主要是将bh提交给块设备层,进行读操作

writepage

注意,writepage只被mmap相关函数所使用,VFS读写时,使用的是另一套函数。其实现是block_write_full_page,第一个参数为page,第二个参数为get_block策略函数。

如果需要写的页在文件最后一个页之内,则调用__block_write_full_page,其过程与block_read_full_page类似。

如果需要写的页在文件正好为文件的最后一个页,我们还需要做很多额外的工作。因为实际上,最后一个页可能不完整。我们知道,文件系统是以块为粒度管理空间的,但是VFS读写时,是以页为粒度的,这就意味着,如果读文件最后一个页,它可能只有一个块。此时,操作退化成prepare_write与commit_write。

prepare_write/commit_write

一个困扰我们的问题是,writepage与prepare_write/commit_write有什么区别?区别在于,writepage写的是一整页,而prepare_write/commit_write写得是部分页。这也是为什么前者用于mmap,后者用于VFS的原因。

prepare_write/commit_write的参数都是(page, from, to, get_block_t),没错,它也需要一个get_block策略来将逻辑块index转换成物理块index。

先看block_prepare_write:

  • 如果页无bh,为其准备bh
  • 对于[from, to)范围内涉及到的partial bh(即bh所对应的空间不完全被[from, to)所包含),,发起读请求,以确保之后的写是对齐的。
  • 等待读完成

再看block_commit_write:

  • 注意,执行commit_write时,要写的内容已经在page上了,我们需要commit此page上的最新的写
  • 看了下,没有做什么大不了的事情,不分析了

BufferCache与PageCache的联系与区别

之前提到,一个页一旦被分配了bh,它的引用计数会加一,那么问题来了,这个引用计数是什么时候减少的呢?代码见kswapd,简单来说,当kswapd尝试要回收一个页,但发现页有bh,它会检查bh是为非lock非dirty,如果页的所有bh都是非lock非dirty,则回收bh,页的引用减少一。

<think>我们正在讨论PageCache在操作系统中的层次归属问题。根据引用内容,PageCache由内核维护,以4KB为单位存储数据,对应磁盘上的block。它位于内存中,用于加速文件读写,但存在数据丢失风险(如断电时)。结合引用:-引用[2]和[4]明确指出PageCache是操作系统内存管理的一部分,属于内存中的缓存区域。-引用[3]提到PageCache由内核维护,作为中间层向应用程序提供数据。-引用[1]则对比了Buffercache和PageCache的历史演变,说明它们都是操作系统层的缓存机制。因此,PageCache属于**操作系统层**,具体来说是内核中管理的内存缓存机制,服务于文件系统但本身不属于文件系统层。文件系统层负责管理磁盘数据结构,而PageCache作为下层支撑,通过缓存磁盘(block)来加速文件访问。下面用层次结构说明:1.**应用层**:用户程序(如NVR软件)2.**文件系统层**:EXT4/NTFS等,管理文件目录结构3.**操作系统层**:-**PageCache**:缓存文件数据(4KB单位)-**VFS(虚拟文件系统)**:抽象不同文件系统的接口4.**设备层**:磁盘驱动、RAID管理5.**物理存储层**:HDD/SSDPageCache在层次上属于操作系统内核的内存管理模,其位置高于设备层但低于文件系统层(因为文件系统会调用PageCache接口)。当应用程序读取文件时:1.文件系统向VFS请求数据2.VFS检查PageCache是否存在所需数据3.若存在则直接返回(避免磁盘访问);若不存在则调用设备层读取磁盘这种设计使得PageCache文件系统透明——文件系统只需操作逻辑,实际物理加载由操作系统通过PageCache自动处理。>📌**关键点总结**>PageCache是**操作系统内核层**的核心组件,而非文件系统层。它抽象了磁盘的内存缓存,为所有文件系统提供统一的加速机制。其4KB的粒度设计(引用[3][4])兼容多数文件系统大小,并通过LRU等算法管理内存(引用[4])。---###相关问题1.PageCache与BufferCache在Linux内核中的主要区别是什么?2.如何验证一个文件的数据当前是否被缓存在PageCache中?3.为什么DirectI/O可以绕过PageCache?这对高性能存储场景有何意义?4.当系统内存不足时,内核如何处理PageCache中的"脏页"(dirtypages)?</think>### Page Cache所属系统层次分析 #### **核心结论** **Page Cache属于操作系统层**,具体位于操作系统内核的内存管理子系统。它作为**内核级的磁盘缓存机制**,介于文件系统层和设备驱动层之间。 --- #### **层次定位详解** 1. **与文件系统层的关系** - Page Cache**服务于文件系统**,但不是文件系统组成部分 - 文件系统读写操作通过VFS(虚拟文件系统)接口调用Page Cache - 文件系统处理逻辑结构(如目录树、inode),Page Cache处理物理数据缓存[^3] 2. **在操作系统中的具体位置** ```mermaid graph LR A[用户空间] --> B[系统调用接口] B --> C[文件系统层 EXT4/XFS/NTFS] C --> D[Page Cache<br/>操作系统内核层] D --> E[设备驱动层] E --> F[物理磁盘] ``` - Page Cache在内核空间实现,通过`mmap()`等系统调用暴露给应用[^3] - 由内核统一管理,所有进程共享(引用计数控制)[^3] 3. **区别于存储硬件层** - 抽象物理存储设备:将磁盘block映射为内存page(通常4KB单位)[^4] - 屏蔽硬件差异:对HDD/SSD/NVMe使用统一缓存机制 - 实现策略:LRU淘汰、脏页回写、预读等均由内核调度[^4] --- #### **关键特性佐证** 1. **内核维护机制** - 缓存分配/回收由内核内存管理系统控制(`kswapd`守护进程)[^4] - 通过`/proc/meminfo`的`Cached`字段暴露状态(用户空间只读)[^2] 2. **跨文件系统通用性** - 为EXT4/XFS/NTFS等不同文件系统提供统一缓存层[^3] - 文件系统只需关注元数据,数据缓存由Page Cache托管 3. **硬件无关操作** - 以page为单位操作(通常4KB),与磁盘物理扇区大小解耦[^4] - 通过bio(Block I/O)层对接不同设备驱动[^1] --- #### **与相关组件的对比** | 组件 | 所属层级 | 与Page Cache的关系 | |---------------|---------------|-----------------------------| | Buffer Cache | 操作系统层 | 历史遗留,现基本被合并[^1] | | 文件系统inode | 文件系统层 | Page缓存其指向的数据 | | SSD FTL | 存储硬件层 | Page Cache的下游,无直接交互| > 💡 **技术本质**:Page Cache是**操作系统实现的核心性能优化层**,通过空间换时间策略(内存缓存磁盘数据)解决I/O瓶颈。其断电易失性[^4]正是因其属于易失性内存管理范畴,而非持久化存储层。 --- ### 相关问题 1. Page Cache与Buffer Cache在Linux内核中的演进关系是怎样的?[^1] 2. 如何通过`/proc`文件系统监控Page Cache的使用效率和命中率? 3. 为什么数据库系统(如MySQL)常建议绕过Page Cache使用Direct I/O? 4. 当系统发生OOM(Out-Of-Memory)时,内核如何优先回收Page Cache?[^4]
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值