Linux 内核 struct page 超详细深度解析
struct page 是 Linux 内核内存管理子系统的绝对核心。系统中每一个物理内存页(Page Frame,通常大小为 4KB),在内存中都对应一个 struct page 结构体实例。
内核并不是直接通过物理地址操作内存,而是通过操作这些 struct page 对象来管理页的分配、引用、缓存、锁定、回收等全生命周期。
一、 核心定义与宏观视角
1. 基本定义
在经典的 Linux 内核版本(如 2.6 系列)中,这个结构体被定义为 mem_map_t(typedef struct page mem_map_t)。
在源码中,所有物理页的 struct page 实例组成一个全局数组 mem_map。这意味着:物理内存的第一页(物理地址 0x0)对应 mem_map[0],第二页对应 mem_map[1],以此类推。
2. 结构体的演变
随着内核版本迭代(从 2.6 到 5.x/6.x),这个结构体发生了大幅变化:
精简:移除了
age等冗余字段。位域优化:使用位操作更紧密地存储信息。
联合体:通过
union复用字段空间,减少内存占用。抽象增强:新增
_mapcount等字段,更精细地管理页表引用。
但无论怎么变,核心逻辑不变。
二、 逐字段 360° 深度解剖
1. struct list_head list;
本质:这是一个通用双向链表节点。
作用:因为一个页在不同时期有不同身份(空闲页、被分配给进程的页、脏页等),它需要被加入不同的“队列”。
伙伴系统 (Buddy System):空闲页挂入
free_area链表,用于快速分配。SLAB 分配器:内核小对象分配时,页挂入 slab 的内部链表。
区别:它与下面的
lru是独立的。list是通用接口,lru专用于回收。
2. struct address_space *mapping;
本质:页的“户口本”或“归属地”。
作用:指向该页所属的地址空间。这是 Linux 内存管理中最关键的指针之一,它直接决定了页的类型。
文件页 (Page Cache):
mapping指向磁盘上某个文件的address_space。表示这页缓存是文件的内容。匿名页 (Anonymous Page):
mapping指向anon_vma(或特殊的交换空间)。表示这页是进程的堆、栈、数据,没有对应磁盘文件。内核页:
mapping通常为NULL。
3. unsigned long index;
本质:页在地址空间中的位置索引。
作用:与
mapping配合使用。文件页:表示该页在文件中的偏移量(以页为单位)。例如
index=5代表文件的第 20KB 处(5 * 4KB)。匿名页:表示虚拟地址空间中的偏移量,用于缺页中断时找回数据。
4. struct page *next_hash; struct page **pprev_hash;
本质:哈希表指针。
作用:为了在海量页缓存中快速找到文件页,内核使用了哈希表。
哈希表的 Key 是
(mapping, index)。next_hash:指向哈希冲突链中的下一个页。pprev_hash:指向前一个节点的next_hash指针。这是一个二级指针,实现了 O(1) 时间复杂度的删除操作,非常高效。
5. atomic_t count; 核心中的核心
本质:引用计数器(原子操作)。
作用:记录当前有多少个内核组件/进程在引用这个页。它是决定页生死的关键。
状态机:
count > 0:页正在被使用,绝对不能被回收。count = 0:页空闲,无人使用,可以被分配给新任务或回收。count < 0:内核 Bug,引用计数失衡。
操作:通过
get_page()(原子加1) 和put_page()(原子减1) 操作。
6. unsigned long flags;
本质:状态标志位。
作用:用一个整数的每一位(bit)来表示页的状态。支持原子操作。
常见标志 (Flags):
PG_locked:页被锁定。正在进行 IO 操作,其他进程需等待。PG_dirty:脏页。内容已修改,需要写回磁盘。PG_uptodate:数据已就绪。文件页已从磁盘加载完成,可以使用。PG_referenced/PG_active:LRU 回收标记。表示页最近被访问过,处于活跃状态。
7. struct list_head lru;
本质:LRU 链表节点。
作用:专用于内存回收 (Swap/Reclaim)。
原理:内核维护两个大链表: 页通过
lru字段在这两个链表间移动。当内存不足时,优先回收inactive_list头部的页(最久未用)。active_list:活跃页列表,最近频繁使用。inactive_list:不活跃页列表,久未使用。
8. unsigned long age;
本质:页的“年龄”。
作用:在旧内核中,用于判断页的活跃程度。访问时年龄加一,定期扫描时年龄减一。
现状:在现代内核(3.x+)中,这个字段已被移除。其功能完全整合到
flags的PG_referenced位中。
9. wait_queue_head_t wait;
本质:等待队列头。
作用:配合
PG_locked使用。场景:当一个进程想访问被锁定(IO 中)的页时,它不能忙等。进程会把自己加入到
page->wait这个等待队列中睡眠。当 IO 完成,页解锁,内核唤醒这个队列中的进程继续执行。
10. struct buffer_head * buffers;
本质:块设备缓冲区关联。
作用:早期内核用于块设备(磁盘)读写。一页内存通常包含 8 个磁盘扇区。
buffers指向页内第一个buffer_head,每个buffer_head描述一个磁盘块的状态。现状:现代内核多用
bio结构来批量处理 IO,此字段逐渐过时,但仍保留用于兼容。
11. void *virtual;
本质:内核虚拟地址。
作用:对于高端内存(Highmem)页,内核无法直接通过物理地址访问,必须建立临时的内核虚拟地址映射。
32 位系统:
virtual非空表示通过kmap()临时映射了。64 位系统:虚拟地址空间极大,所有物理内存都永久映射,此字段基本无用。
12. struct zone_struct *zone;
本质:内存域 (Zone) 指针。
作用:指向该页所属的内存管理区域。Linux 将物理内存划分为不同区域(Zone)以满足硬件限制。
ZONE_DMA:低地址内存(<16MB),用于老旧 DMA 设备。
ZONE_NORMAL:普通内存,内核直接映射。
ZONE_HIGHMEM:高端内存,仅用户态可用。
价值:内存分配时必须指定从哪个 Zone 分配。
三、 核心逻辑与生命周期
1. 页的一生
启动/初始化:内核启动时,物理内存被扫描,所有页初始化,
count=0,挂入free_area空闲链表。分配 (Alloc):伙伴系统从
free_area摘下一页。count设为 1,zone指向所属域。映射 (Map):
进程页:建立页表映射,
mapping指向anon_vma,count原子加 1。文件页:读入文件内容,
mapping指向文件address_space,加入页缓存哈希表。
访问 (Access):CPU 访问页,触发 MMU,设置
PG_referenced标志。修改 (Modify):修改页内容,设置
PG_dirty标志。IO 操作 (IO):页数据需要写入磁盘,设置
PG_locked,数据写入后清除标志并解锁。回收 (Reclaim):内存不足时,扫描 LRU 链表。若
count降至 0 或为不可驻留页,将其清空并重新放回free_area。
2. 关键机制总结
- 字段精简位域压缩匿名页优化映射计数分离句柄
(注:文档部分内容可能由 AI 生成)
评论
欢迎友好交流,理性讨论