address_space------ 它是内核页缓存(Page Cache) 的核心管理结构体,也是连接「文件系统」「虚拟内存」「物理内存」的关键枢纽,所有文件读写、tmpfs 数据存储、mmap 文件映射都绕不开它,是内核内存管理的「隐形核心」。
address_space 是内核页缓存的「总管家」 :每个可缓存的对象(普通文件 /tmpfs 文件 / 块设备)都对应一个address_space,负责管理该对象的所有页缓存物理页、脏页、映射关系;
核心作用:统一管理「文件数据 <-> 物理页」的映射关系,提供页缓存的分配 / 查找 / 脏页管理 / 写回等核心接口,是文件系统与内存管理的「桥梁」;
核心关联:
- 与
struct page:每个页缓存物理页通过page->mapping指向所属的address_space; - 与
inode:普通文件 /tmpfs 文件的inode->i_mapping指向其address_space; - 与 VMA:映射文件的 VMA 通过
vm_file->f_mapping关联到address_space,缺页异常时从这里获取页缓存; - 与 tmpfs:tmpfs 的文件数据完全存储在
address_space管理的页缓存中,address_space是其数据存储的核心;
核心接口 :find_get_page()(查找页缓存)、page_cache_alloc()(分配页缓存)、mark_page_dirty()(标记脏页)、writeback_inode()(脏页写回)。
address_space核心数据结构

核心字段解析
| 字段 | 核心作用 | 关联知识点 |
|---|---|---|
host |
指向所属的inode(文件 / 设备),是address_space与文件系统的核心关联 |
tmpfs_inode_info/ext4_inode_info |
i_pages |
用 xarray(高效数组)存储「文件偏移(页号)→ struct page」的映射,是页缓存的核心存储 | 物理页管理(struct page) |
i_mmap |
存储所有映射该address_space的 VMA(带 VM_SHARED 标志),实现多进程共享映射 |
VMA/mmap 共享内存 |
i_mmap_cnt |
共享映射的 VMA 数量,计数为 0 时可安全释放页缓存 | 内存回收 /kswapd |
a_ops |
页缓存的操作集,不同文件系统(ext4/tmpfs)实现不同的读写逻辑 | tmpfs/ext4 文件系统实现 |
tree_lock |
保护 i_pages 的自旋锁,避免多线程并发操作页缓存的竞态条件 | 内核同步 / 自旋锁 |
页缓存
页缓存(Page Cache) 是连接「文件数据」和「物理内存」的核心,而address_space就是页缓存的「管理中枢」,其核心定位可总结为:
address_space = 页缓存的「索引 + 操作中心」:
- 「索引」:通过
i_pages维护「文件偏移→物理页」的映射,快速查找任意偏移对应的页缓存; - 「操作中心」:通过
a_ops提供页缓存的读 / 写 / 脏页标记 / 写回等标准化操作,适配不同文件系统(ext4/tmpfs/ 块设备)。
页缓存的基本概念
页缓存是 Linux 内核基于物理内存 实现的文件数据缓存层 ,以4KB 物理页 为单位缓存磁盘文件的内容,是内核对所有文件 IO 的统一抽象 ;普通文件 IO (read/write) 和 mmap 文件映射的底层都通过页缓存交互,主缺页异常仅在文件页未缓存时触发;
页缓存的核心作用是将磁盘 IO 转化为内存 IO,减少主缺页次数和磁盘访问,提升系统整体性能。
页缓存是内核为提升文件读写性能设计的「内存缓存层」,核心规则:
- 读取文件时,内核先从页缓存(
address_space的i_pages)查找数据,命中则直接返回,未命中则从磁盘加载到页缓存后返回; - 写入文件时,内核先将数据写入页缓存,标记为「脏页」,再由内核线程(
pdflush/kswapd)异步写回磁盘; - 所有页缓存物理页都由
address_space统一管理,每个文件 / 设备对应唯一的address_space。
Linux 内核的核心设计思想 :对所有磁盘文件的访问,都必须经过页缓存,内核不允许进程直接访问磁盘 。无论是read()/write()的普通文件 IO,还是mmap()的文件映射,最终都是对页缓存的读写,磁盘仅作为页缓存的持久化后端。
页缓存(Page Cache,也叫文件页缓存)是 Linux 内核在物理内存 中开辟的一块缓存区域,专门用于缓存磁盘文件的内容和元数据(如 inode、目录项) ;
页缓存核心特性:
- 缓存单位 :以 Linux 最小物理页单位4KB为基本缓存单元,无论文件大小多少,都按页切分缓存;
- 数据来源:磁盘文件的内容,仅当页缓存中无对应数据时,才从磁盘读取(触发主缺页 / 磁盘 IO);
- 统一抽象:是内核对所有文件系统(ext4/xfs/btrfs)和块设备的统一缓存层,上层 IO 操作无需关心底层存储;
- 按需加载 :采用懒加载策略,仅当进程访问文件的某一页时,才将该页从磁盘加载到页缓存,不一次性加载整个文件;
- 持久化保障 :内核通过刷盘机制将页缓存中的脏页(修改后未同步到磁盘)异步 / 同步写入磁盘,保证数据一致性;
- 全局共享:页缓存属于内核全局资源,所有进程共享同一页缓存,一个进程加载的文件页,其他进程可直接复用,避免重复磁盘 IO。
页缓存的核心价值
页缓存是 Linux 系统性能的核心支柱 ,没有页缓存,文件 IO 的性能会下降1000 倍以上(磁盘 IO vs 内存 IO),核心价值体现在 3 点:
- 将磁盘 IO 转化为内存 IO :磁盘的随机寻道时间≈10ms,内存访问时间≈10ns,差距100 万倍,页缓存让绝大多数文件访问都在内存中完成;
- 减少主缺页异常次数 :mmap 文件映射时,若文件页已在页缓存中,仅触发次缺页异常 (无磁盘 IO),否则触发主缺页异常(磁盘 IO);
- 实现进程间数据共享:所有进程共享页缓存,一个进程加载的文件页,其他进程可直接复用,避免重复的磁盘读取和物理内存占用。
页缓存的内核管理机制
核心数据结构(页缓存的管理载体)
Linux 内核通过以下 3 个核心数据结构,实现对页缓存的高效管理,所有结构都存储在物理内存中:
页描述符:struct page
这是 Linux 内核管理每一个物理页 的核心结构,页缓存中的每一个缓存页,都对应一个struct page,核心字段:
mapping:指向该页所属的地址空间 (struct address_space),关联到具体的文件;index:该页在文件中的偏移页号(如文件的第 10 个 4KB 页,index=10);flags:页的状态标志,核心标志:PG_uptodate:页缓存中的数据与磁盘一致(干净页);PG_dirty:页缓存中的数据被修改,未同步到磁盘(脏页);PG_locked:页被锁定(如正在从磁盘加载数据,防止并发修改);
count:页的引用计数,记录当前使用该页的进程数 / 内核模块数。
struct page是物理页的唯一标识,页缓存、进程虚拟内存、物理内存管理都基于该结构。
地址空间:struct address_space
这是文件与页缓存的核心关联结构 ,每个文件的 inode(struct inode)中都包含一个i_mapping字段,指向该文件的address_space,核心作用是将文件的偏移量映射到页缓存的物理页,核心字段:
page_tree:红黑树 ,以「文件偏移页号 (index)」为键,存储该文件的所有缓存页(struct page),实现缓存页的快速查找(时间复杂度 O (logN));nrpages:该文件在页缓存中的缓存页数;ops:页缓存的操作函数集 (struct address_space_operations),包含缓存页的读取、写入、刷盘等核心操作;dirty_pages:该文件的脏页链表,存储所有被修改但未刷盘的缓存页。
核心作用:通过
address_space,内核能快速根据文件 + 文件偏移,找到对应的页缓存物理页,无需遍历整个页缓存。
索引节点:struct inode
文件的元数据载体,每个文件对应一个唯一的 inode,核心与页缓存相关的字段:
i_mapping:指向该文件的address_space,是文件与页缓存的唯一关联入口;i_atime/i_mtime/i_ctime:文件的访问 / 修改 / 状态改变时间;i_size:文件大小,用于限制页缓存的最大偏移。
页缓存的核心操作(查 / 加 / 删)
内核对页缓存的所有操作,都围绕「根据文件 + 偏移查找缓存页」展开,核心有 3 个操作,是文件 IO 和 mmap 映射的底层基础:
查找缓存页:find_get_page(mapping, index)
- 入参:
mapping= 文件的address_space,index= 文件的偏移页号; - 逻辑:遍历
mapping的红黑树page_tree,根据index查找对应的struct page; - 结果:找到则返回
struct page并将引用计数 + 1,未找到则返回 NULL。
添加缓存页:add_to_page_cache(page, mapping, index)
- 入参:
page= 分配的物理页描述符,mapping= 文件的address_space,index= 文件的偏移页号; - 逻辑:将
page插入到mapping的红黑树page_tree中,设置page->mapping和page->index,初始化页状态; - 核心:添加前会通过伙伴系统分配空闲的物理页,用于存储从磁盘读取的文件数据。
删除缓存页:delete_from_page_cache(page)
- 入参:
page= 要删除的缓存页描述符; - 逻辑:将
page从mapping的红黑树page_tree中移除,清空page->mapping和page->index,减少引用计数; - 触发场景:物理内存不足时,内核回收页缓存的干净页;文件被删除 / 截断时,删除对应的缓存页。
页缓存与文件 IO 的核心交互
普通文件 IO(read()/write()系统调用)是用户态访问文件的最常用方式,其底层完全基于页缓存实现,全程无直接的磁盘访问,所有操作都是对页缓存的读写。
read () 系统调用底层流程(读文件 = 读页缓存)
核心逻辑:先查页缓存,有则直接读,无则从磁盘加载到页缓存后再读 ,仅当缓存未命中时触发磁盘 IO + 主缺页异常,命中则仅内存操作,流程如下(共 6 步):
步骤 1:用户态调用 read (),内核接收参数
进程调用read(fd, buf, count),触发系统调用进入内核态,内核通过文件描述符fd找到对应的文件 inode ,进而获取文件的address_space(inode->i_mapping),并计算要读取的文件偏移页号 (index) 和页内偏移。
步骤 2:内核查找页缓存(find_get_page)
内核遍历该文件address_space的红黑树,根据文件偏移页号查找对应的页缓存物理页:
- 缓存命中 :找到对应的
struct page,直接进入步骤 5(读页缓存); - 缓存未命中 :未找到对应的
struct page,进入步骤 3(加载磁盘数据到页缓存)。
步骤 3:内核分配物理页,添加到页缓存
内核通过伙伴系统 分配一块空闲的 4KB 物理页,调用add_to_page_cache()将该物理页添加到文件的address_space红黑树中,设置页状态为PG_locked(锁定,防止并发修改)。
步骤 4:内核发起磁盘 IO,加载数据到页缓存
内核根据文件的偏移量,计算对应的磁盘物理块 ,向块设备层发起磁盘读 IO 请求,将磁盘文件的对应页数据加载到刚分配的物理页中:
- IO 完成后,内核将页状态标记为
PG_uptodate(数据与磁盘一致,干净页),并解除锁定(清除PG_locked); - 此步骤是唯一的磁盘 IO 操作 ,也是
read()性能瓶颈的唯一来源。
步骤 5:内核将页缓存数据拷贝到用户态缓冲区
内核将页缓存物理页中的数据,拷贝到用户态传入的 buf 缓冲区(用户态虚拟地址),完成数据读取:
- 若读取的字节数跨多个物理页,内核会重复步骤 2~5,直到读取完所有数据;
- 此步骤是唯一的数据拷贝操作(页缓存→用户态缓冲区)。
步骤 6:内核返回用户态,read () 调用完成
内核将实际读取的字节数返回给用户态进程,read()系统调用完成,进程可直接访问buf中的数据。
核心结论:read () 的本质是「页缓存的查找 + 数据拷贝」,磁盘 IO 仅在缓存未命中时触发,且数据会被缓存到页缓存,后续对该文件的读取会直接命中缓存。
write () 系统调用底层流程(写文件 = 写页缓存)
核心逻辑:先查页缓存,有则直接写,无则分配物理页并添加到页缓存后再写 ,写操作仅修改页缓存,不立即同步到磁盘 ,被修改的页会被标记为脏页,由内核异步刷盘,流程如下(共 6 步):
步骤 1:用户态调用 write (),内核接收参数
进程调用write(fd, buf, count),进入内核态,内核通过fd找到文件 inode 和address_space,计算写入的文件偏移页号和页内偏移。
步骤 2:内核查找 / 创建页缓存
内核通过find_get_page()查找对应偏移的页缓存:
- 缓存命中 :找到对应的
struct page,进入步骤 4(写页缓存); - 缓存未命中 :分配物理页,调用
add_to_page_cache()添加到页缓存,标记为PG_locked,进入步骤 3。
步骤 3:(缓存未命中时)加载磁盘数据到页缓存
若写入的区域是文件的已有区域 (非文件尾部追加),内核会发起磁盘 IO,将该页的原始数据从磁盘加载到页缓存(保证数据完整性);若为文件尾部追加,则无需加载磁盘数据,直接初始化物理页为 0 即可。
步骤 4:内核将用户态数据拷贝到页缓存
内核将用户态buf中的数据,拷贝到页缓存的物理页中(内核态→内存操作),完成写入:
- 若写入跨多个物理页,重复步骤 2~4;
- 此步骤是唯一的数据拷贝操作(用户态缓冲区→页缓存)。
步骤 5:内核标记页缓存为脏页,解除锁定
内核将该页的状态标志设置为PG_dirty(脏页,修改后未同步到磁盘),并清除PG_locked标志;
核心 :此时数据仅存在于页缓存中,未写入磁盘,write () 调用至此已完成,无需等待磁盘 IO。
步骤 6:内核返回用户态,write () 调用完成
内核将实际写入的字节数返回给用户态进程,write () 调用完成,进程可继续执行其他逻辑,无需等待磁盘刷盘。
核心结论:write () 的本质是「页缓存的查找 + 数据拷贝 + 标记脏页」 ,全程无同步磁盘 IO,这是 write () 高性能的核心原因;磁盘刷盘由内核异步完成,不阻塞用户态进程。
普通文件 IO 的核心性能瓶颈
普通文件 IO(read/write)的性能瓶颈主要来自两个方面,也是后续优化的核心:
- 数据拷贝 :read 有「页缓存→用户态」一次拷贝,write 有「用户态→页缓存」一次拷贝,两次拷贝是 mmap 比普通 IO 快的核心原因;
- 磁盘 IO:仅当页缓存未命中时触发,主缺页异常也由此产生,磁盘 IO 的耗时远大于内存操作和数据拷贝。
页缓存与 mmap 文件映射的核心交互
mmap 的本质是将文件的页缓存直接映射到进程的虚拟地址空间,进程可直接访问页缓存,无需数据拷贝,这是 mmap 比 read/write 快的根本原因。
mmap 文件映射与页缓存的绑定关系
mmap 文件映射的全程,始终与页缓存深度绑定,无页缓存则无 mmap 的零拷贝,核心绑定逻辑:
- mmap 系统调用阶段 :内核为进程创建 VMA(虚拟内存区域),并将 VMA 与文件的
address_space(页缓存)关联,不分配物理内存,不加载磁盘数据; - 首次访问映射地址阶段 :触发缺页异常 ,内核通过 VMA 找到文件的
address_space,执行页缓存的查找 / 加载逻辑; - 正常访问阶段 :进程的虚拟地址直接映射到文件的页缓存物理页,读写操作直接作用于页缓存,无任何数据拷贝。
核心结论:mmap 文件映射 = 进程虚拟地址空间 ↔ 文件页缓存物理页 的直接映射,页缓存是 mmap 的唯一数据载体。
mmap 文件映射的读流程(与缺页异常协同)
mmap 读文件的核心是缺页异常 + 页缓存命中 / 未命中 ,全程零拷贝,流程如下:
- 进程调用
mmap(),内核创建 VMA 并关联文件的address_space,返回虚拟地址(仅分配虚拟地址,无物理内存); - 进程首次访问 映射虚拟地址,触发缺页异常 ,进入内核
do_page_fault(); - 内核通过 VMA 找到文件的
address_space,调用find_get_page()查找页缓存:- 缓存命中 :触发次缺页异常,内核直接将该页缓存的物理页映射到进程虚拟地址,建立页表,无磁盘 IO;
- 缓存未命中 :触发主缺页异常,内核分配物理页→添加到页缓存→发起磁盘 IO 加载数据→建立页表映射;
- 缺页异常处理完成,进程返回用户态,直接访问虚拟地址即可读取页缓存数据,无任何数据拷贝。
mmap 文件映射的写流程(MAP_SHARED/MAP_PRIVATE 区别)
mmap 写文件的流程依赖flags参数(MAP_SHARED/MAP_PRIVATE),但无论哪种,写操作都直接作用于页缓存 ,核心区别是是否将脏页同步到磁盘 / 其他进程:
MAP_SHARED(共享映射):写页缓存 = 写文件
- 进程写入映射虚拟地址,直接修改共享的页缓存物理页;
- 内核将该页标记为
PG_dirty(脏页),与普通 write () 的脏页管理逻辑一致,由内核异步刷盘到磁盘; - 所有映射该文件的进程,都能立即看到页缓存的修改(共享特性);
- 核心:无数据拷贝,修改直接同步到页缓存,最终异步刷盘到磁盘。
MAP_PRIVATE(私有映射):写页缓存触发 COW,不影响原文件
- 进程写入映射虚拟地址,触发写时复制 (COW) 缺页异常(页表项为只读);
- 内核在缺页异常中,为该进程分配新的物理页 ,将原页缓存的数据拷贝到新物理页,并建立新的页表映射;
- 进程后续的写操作,仅修改自己的私有物理页,不会标记原页缓存为脏页,也不会同步到磁盘 / 其他进程;
- 核心:仅首次写触发 COW 拷贝,后续写无拷贝,不影响原文件的页缓存。
address_space 的核心接口
address_space 提供了标准化的页缓存操作接口,是内核文件系统开发的核心 API,以下是最常用的接口;
页缓存查找 / 分配
| 接口 | 作用 |
|---|---|
find_get_page(mapping, index) |
从address_space的i_pages查找指定页号的页缓存,返回struct page* |
find_lock_page(mapping, index) |
查找并锁定页缓存(避免并发修改) |
page_cache_alloc(mapping) |
为address_space分配一页页缓存(物理连续) |
page_cache_release(page) |
释放页缓存的引用计数,计数为 0 时可回收 |
脏页管理
| 接口 | 作用 |
|---|---|
mark_page_dirty(page) |
将页缓存标记为脏页,加入address_space的脏页链表 |
mapping_set_dirty(mapping) |
标记整个address_space为脏(有脏页) |
writeback_inode(inode) |
将inode关联的address_space的脏页异步写回磁盘 / Swap |
sync_inode(inode) |
同步写回address_space的所有脏页(阻塞直到完成) |
页缓存插入 / 删除
| 接口 | 作用 |
|---|---|
add_to_page_cache(page, mapping, index, gfp) |
将页缓存插入address_space的i_pages |
remove_from_page_cache(page) |
从address_space的i_pages删除页缓存 |
truncate_inode_pages(mapping, offset) |
截断address_space中指定偏移后的所有页缓存 |
VMA 关联操作
| 接口 | 作用 |
|---|---|
vma_interval_tree_insert(vma, &mapping->i_mmap) |
将 VMA 插入address_space的 i_mmap 红黑树 |
vma_interval_tree_remove(vma, &mapping->i_mmap) |
从address_space删除 VMA |
mapping_mapped(mapping) |
判断address_space是否有 VMA 映射(i_mmap_cnt>0) |
address_space 管理页缓存的核心流程
步骤 1:文件打开(open("/home/test", O_RDWR))
- 内核找到文件的
inode(ext4_inode_info),初始化其i_mapping字段,指向该文件的address_space; - 初始化
address_space的host为该inode,a_ops为ext4_aops(ext4 的页缓存操作集),i_pages为空(无页缓存);
步骤 2:文件读取(read(fd, buf, 4096))
- 内核调用
address_space的readpage方法(ext4_readpage); - 计算读取偏移对应的「页号」(如偏移 0 对应页号 0,4096 对应页号 1);
- 调用
find_get_page(mapping, page_index)从i_pages查找页缓存:- 命中(页缓存存在):直接将页缓存数据拷贝到用户态 buf;
- 未命中(缺页):调用
page_cache_alloc()分配物理页(伙伴系统),从磁盘读取数据到该页,将「页号→page」插入i_pages,再拷贝数据到用户态 buf;
步骤 3:文件写入(write(fd, buf, 4096))
- 内核调用
address_space的writepage方法(ext4_writepage); - 分配页缓存物理页(若不存在),将用户态 buf 数据拷贝到该页;
- 调用
mark_page_dirty(page)标记页为脏页,更新address_space的脏页链表; - 内核线程
pdflush异步遍历address_space的脏页链表,将脏页写回磁盘,写回完成后清除脏页标记;
步骤 4:文件关闭(close(fd))
- 若文件无引用(
i_count=0),内核仍保留address_space和页缓存(供后续复用); - 当系统物理内存不足时,
kswapd回收address_space的冷页缓存(未被访问的页),释放物理页到伙伴系统。
address_space 与 VMA/mmap
当你调用mmap()映射文件时,address_space是连接「VMA」和「页缓存」的关键,完整链路:
核心流程(mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0))
- 内核执行
do_mmap(),新建vm_area_struct,其vm_file指向文件的struct file; struct file的f_mapping指向文件的address_space(即inode->i_mapping);- 进程首次访问映射地址时,触发缺页异常
do_page_fault(); - 内核在缺页处理中:
- 通过
VMA->vm_file->f_mapping找到address_space; - 调用
find_get_page(mapping, page_index)查找页缓存(文件偏移 = VMA 偏移); - 若页缓存不存在,从磁盘加载到页缓存;
- 将页缓存物理页映射到进程的虚拟地址空间(建立页表);
- 通过
- 多进程共享映射 :多个进程映射同一个文件时,共享同一个
address_space的页缓存,进程写入数据会实时同步到页缓存,其他进程可立即读取 ------ 这是mmap共享内存的核心实现。
address_space 与 tmpfs
tmpfs 的文件数据完全存储在address_space管理的页缓存中,这是 tmpfs 与普通磁盘文件系统的核心区别:
tmpfs 的address_space 特殊之处
address_space的a_ops为tmpfs_aops(tmpfs 的页缓存操作集),其readpage/writepage方法无磁盘 IO :tmpfs_readpage:直接从i_pages的页缓存读取数据(内存→内存);tmpfs_writepage:直接将数据写入页缓存,标记脏页后由kswapd换出到 Swap(而非写回磁盘);
- tmpfs 的
address_space无磁盘关联,i_pages的页缓存就是数据的「实际存储层」(而非缓存层); - 当进程映射 tmpfs 文件时,缺页异常会直接从
address_space的页缓存映射物理页,无需磁盘加载,性能远高于映射磁盘文件。
tmpfs 文件写入的核心流程
(write("/dev/shm/test", buf, 4096)):
- 内核调用
tmpfs_writepage,分配物理页(伙伴系统),将数据写入页缓存; - 标记页为脏页,加入
address_space的脏页链表; - 若系统物理内存不足,
kswapd将该脏页换出到 Swap,更新address_space的i_pages(记录页的 Swap 位置); - 进程读取该数据时,若页被换出,内核从 Swap 换入物理页,更新
i_pages,再返回数据。
address_space 与 struct page
每个页缓存物理页的struct page有两个核心字段与address_space关联:
page->mapping:指向该页所属的address_space(若为页缓存页);page->index:该页对应的「文件偏移页号」(如偏移 4096 对应 index=1);
核心规则:
- 普通页缓存页(ext4 文件):
mapping= 文件的address_space,index= 文件偏移页号; - tmpfs 页缓存页:
mapping=tmpfs 文件的address_space,index= 文件偏移页号; - 匿名页(堆 / 栈 /mmap 匿名映射):
mapping=NULL(无文件关联),index无意义; - 内核态页(kmalloc/vmalloc):
mapping=NULL,index无意义。
address_space 与内存回收(kswapd/OOM)
address_space 是内核内存回收的核心依据,kswapd回收页缓存的流程:
- 遍历系统中所有
address_space的i_pages,按「访问频率」排序(活跃页 / 非活跃页); - 优先回收「非活跃、非脏」的页缓存(如很久未访问的 ext4 文件页),释放物理页到伙伴系统;
- 若回收不足,回收「非活跃、脏」的 tmpfs 页缓存,将其换出到 Swap,更新
address_space的i_pages; - 最后才考虑回收匿名页(堆 / 栈),避免影响进程运行。
address_space 的典型应用场景
普通文件系统(ext4/xfs)
address_space作为页缓存的管理中枢,实现「磁盘文件→内存缓存」的高效映射;- 核心价值:提升文件读写性能,减少磁盘 IO 次数(热点文件数据常驻内存)。
tmpfs(内存文件系统)
address_space是 tmpfs 的「数据存储层」,其i_pages管理的页缓存就是 tmpfs 文件的实际数据;tmpfs_aops的readpage/writepage无磁盘 IO,仅操作内存 / Swap,这是 tmpfs 高性能的核心。
块设备(如 /dev/sda1)
- 块设备的
inode(bdev_inode)关联address_space,管理块设备的页缓存; - 用于块设备的直接读写(如
dd if=/dev/sda1 of=/tmp/test),提升块设备 IO 性能。
进程间共享内存(mmap 映射文件 /tmpfs)
- 多个进程映射同一个文件时,共享同一个
address_space的页缓存; - 进程写入数据到映射地址,会同步到
address_space的页缓存,其他进程可实时读取 ------ 这是共享内存通信的核心实现。
内核页缓存回收(系统内存不足时)
kswapd通过遍历address_space的i_pages,回收冷页缓存,释放物理内存;- 优先回收普通文件的页缓存(可从磁盘重新加载),其次回收 tmpfs 的页缓存(换出到 Swap),最后回收匿名页。
address_space 与 vm_area_struct 的核心区别
address_space 和vm_area_struct是内核内存管理的两个核心结构体,容易混淆,核心区别如下:
| 维度 | address_space |
vm_area_struct (VMA) |
|---|---|---|
| 核心定位 | 页缓存的「总管家」(文件 / 设备维度) | 进程虚拟地址的「分区管家」(进程维度) |
| 管理对象 | 物理页(页缓存),关联「文件偏移→物理页」 | 虚拟地址区间,关联「虚拟地址→物理页」 |
| 所属层级 | 内核态(文件系统 / 内存管理) | 进程私有(用户态虚拟地址) |
| 核心关联 | 与inode/struct page强关联 |
与mm_struct/struct file强关联 |
| 共享性 | 全局共享(所有进程可访问同一个address_space) |
进程私有(每个进程有独立的 VMA) |
| 核心操作 | 页缓存的查找 / 分配 / 脏页管理 / 写回 | 虚拟地址映射 / 缺页异常 / 权限检查 |
| 典型场景 | 文件读写 /tmpfs 数据存储 / 页缓存回收 | mmap 映射 / 堆 / 栈 / 代码段管理 |
核心联动总结
VMA是「进程视角」的虚拟地址管理,address_space是「文件视角」的页缓存管理;当进程通过mmap映射文件时,VMA通过vm_file->f_mapping关联到address_space,实现「进程虚拟地址→文件页缓存→物理页」的完整映射。