当前位置: 首页 > news >正文

Linux内核架构浅谈36-Linux页帧描述:struct page数据结构的设计与关键成员

1. 引言:为什么需要struct page?

在Linux内核中,物理内存被划分为固定大小的"页帧"(Page Frame),是内存管理的最小单位(通常为4KB、8KB或更大)。为了跟踪每个页帧的状态(如是否空闲、被哪个进程使用、是否被锁定等),内核设计了struct page数据结构——每个物理页帧都对应一个struct page实例,堪称内核内存管理的"身份证"。

关键设计目标:由于系统中页帧数量极大(例如32GB内存、4KB页帧对应800万个页帧),struct page必须尽可能紧凑,避免占用过多内存;同时需兼顾灵活性,支持内存管理、文件缓存、设备驱动等多个子系统的需求。

2. struct page的核心结构与设计巧思

Linux内核通过"联合体(union)"和"条件编译"等技术,在有限的内存空间中实现了多场景适配。以下是简化后的核心结构(基于Linux 2.6.24版本,兼顾现代内核设计思想):

#include <linux/mm_types.h>struct page {unsigned long flags;          // 页帧状态标志(原子操作)atomic_t _count;              // 页帧引用计数union {atomic_t _mapcount;       // 页表映射计数(内存管理子系统)unsigned int inuse;       // SLUB分配器:已使用对象数};union {struct {unsigned long private; // 私有数据指针(如buffer_head、swap_entry)struct address_space *mapping; // 地址空间映射(文件/匿名内存)};struct kmem_cache *slab;  // SLUB分配器:指向所属slabstruct page *first_page;  // 复合页:指向首页(Head Page)};union {pgoff_t index;            // 映射内偏移量(如文件页的偏移)void *freelist;           // SLUB分配器:空闲对象链表};struct list_head lru;         // LRU链表节点(页面回收用)
#ifdef WANT_PAGE_VIRTUALvoid *virtual;                // 高端内存页的虚拟地址(部分架构)
#endif
};

2.1 设计巧思:联合体(Union)的灵活运用

由于不同场景下页帧的用途差异极大(如普通内存页、SLUB分配器页、复合页),内核通过联合体复用内存空间,避免结构冗余:

  • _mapcountinuse:前者用于内存管理子系统(统计页帧被页表映射的次数),后者用于SLUB分配器(统计页帧中已使用的内存对象数),二者不会同时生效。
  • private/mappingslab/first_page:普通页用private存储私有数据(如块设备缓冲区的buffer_head),SLUB页用slab指向所属缓存,复合页用first_page指向首页。

注意:联合体成员的访问需严格遵循场景约束,例如访问slab前必须确认页帧属于SLUB分配器(通过PG_slab标志判断),否则会导致数据混乱。

3. struct page关键成员解析

以下按功能分类,解析struct page中最核心的成员及其使用场景:

3.1 页帧状态与生命周期:flags与_count

成员类型核心作用典型场景
flagsunsigned long原子性状态标志,记录页帧的核心属性• PG_locked:页帧被锁定(如I/O期间)
• PG_dirty:页帧数据已修改(需回写磁盘)
• PG_highmem:页帧属于高端内存(无法直接映射)
• PG_slab:页帧属于SLUB分配器
_countatomic_t页帧引用计数,决定页帧是否可回收• _count > 0:页帧被引用(不可回收)
• _count == 0:页帧空闲(可被伙伴系统分配)
• _count == -1:页帧待回收(页面回收子系统处理)
技术示例:操作页帧状态与引用计数

内核提供标准化宏操作flags_count,确保原子性和安全性:

// 1. 锁定页帧(防止并发访问)
SetPageLocked(page);// 2. 标记页帧为"脏"(数据已修改)
SetPageDirty(page);// 3. 增加页帧引用计数(如进程映射该页)
get_page(page); // 等价于 atomic_inc(&page->_count)// 4. 减少引用计数,若为0则释放到伙伴系统
if (put_page_testzero(page)) {// 页帧已无引用,可执行释放逻辑__free_page(page);
}

3.2 内存映射与地址空间:mapping与index

这两个成员主要用于"文件页"或"匿名页"的映射管理:

  • mapping:指向struct address_space实例,关联页帧所属的地址空间(如文件的inode->i_mapping)。通过mapping,内核可快速找到页帧对应的文件或匿名内存区域。
  • index:页帧在地址空间中的偏移量(以页为单位)。例如,文件页的index表示该页对应文件的第几个页(从0开始计数)。
技术示例:通过page找到对应的文件与偏移
#include <linux/fs.h>// 假设page是一个文件页(需先通过PG_private或mapping判断)
struct address_space *mapping = page->mapping;
struct inode *inode = mapping->host; // 页帧对应的inode(文件)
pgoff_t page_offset = page->index;   // 页在文件中的偏移(页单位)// 计算该页对应的文件字节偏移
loff_t file_offset = (loff_t)page_offset * PAGE_SIZE;printk(KERN_INFO "Page %p maps to file %s, offset %lld bytes\n",page, inode->i_sb->s_root->d_name.name, file_offset);

3.3 页面回收:lru链表节点

struct list_head lru是页帧加入LRU(Least Recently Used,最近最少使用)链表的节点。Linux内核通过LRU机制区分"活动页"和"不活动页":

  • 活动页(active_list):近期被访问过,优先保留在内存中。
  • 不活动页(inactive_list):近期未被访问,内存不足时优先换出到磁盘。

内核通过page->lru将页帧挂载到对应的LRU链表,页面回收子系统(如kswapd守护进程)会定期扫描这些链表,选择合适的页帧进行回收。

3.4 高端内存适配:virtual成员

在32位架构(如IA-32)中,内核虚拟地址空间仅1GB,无法直接映射超过896MB的物理内存(高端内存)。此时:

  • 普通内存页(<896MB)可通过线性映射(__va(pfn << PAGE_SHIFT))快速获取虚拟地址。
  • 高端内存页(>896MB)需通过kmap()动态映射到内核虚拟地址空间,映射后的地址存储在page->virtual中。
技术示例:访问高端内存页
#include <linux/highmem.h>// 假设page是高端内存页(PageHighMem(page)为真)
void *vaddr;// 动态映射高端内存页到内核虚拟地址空间
vaddr = kmap(page);
if (!vaddr) {printk(KERN_ERR "Failed to map highmem page %p\n", page);return -ENOMEM;
}// 访问高端内存数据(例如复制数据到用户空间)
copy_to_user(user_buf, vaddr, PAGE_SIZE);// 解除映射(必须调用,避免内存泄漏)
kunmap(page);

4. struct page的实际应用场景

作为内核内存管理的核心结构,struct page贯穿多个子系统,以下是典型应用场景:

4.1 伙伴系统:管理空闲页帧

伙伴系统是Linux内核的物理内存分配器,负责分配连续的页帧块。每个内存域(如ZONE_NORMAL)通过zone->free_area管理空闲页帧,其中的free_list链表存储了空闲的struct page实例。

// 简化的伙伴系统分配逻辑(分配order阶的连续页块)
struct page *alloc_pages(unsigned int gfp_mask, unsigned int order) {struct zone *zone;struct free_area *area;struct page *page = NULL;// 1. 选择合适的内存域(如ZONE_NORMAL)zone = get_zone_from_gfp_mask(gfp_mask);if (!zone) return NULL;// 2. 找到对应order的free_areaarea = &zone->free_area[order];// 3. 从free_list中获取空闲页帧if (!list_empty(&area->free_list[MIGRATE_NORMAL])) {page = list_first_entry(&area->free_list[MIGRATE_NORMAL],struct page, lru);list_del(&page->lru);area->nr_free--;}return page;
}

4.2 文件缓存:页帧与文件数据关联

Linux将文件内容缓存到内存中(页缓存),每个缓存页对应一个struct page实例。通过mappingindex,内核可快速定位文件的某个页是否已缓存,避免重复读取磁盘。

4.3 设备驱动:管理DMA内存

设备驱动程序常需要使用DMA(直接内存访问)与硬件交互。驱动通过dma_alloc_coherent()分配DMA可用的页帧,返回的struct page实例需满足硬件对齐要求,且通过page->private存储DMA地址等私有信息。

5. 总结与最佳实践

struct page是Linux内核内存管理的基石,其设计兼顾了紧凑性和灵活性,通过联合体复用内存、条件编译适配不同架构,支撑了内核多个子系统的高效运作。在使用struct page时,需遵循以下最佳实践:

  • 状态检查优先:访问任何成员前,先通过flags确认页帧状态(如PageLocked(page)PageHighMem(page)),避免非法访问。
  • 引用计数安全:通过get_page()put_page()管理引用计数,防止页帧被提前回收导致悬空指针。
  • 高端内存特殊处理:32位架构下,高端内存页需通过kmap()/kunmap()动态映射,避免直接访问物理地址。

深入理解struct page的设计思想与成员语义,是掌握Linux内核内存管理的关键一步。后续可进一步学习页表映射(pte_t)、页面回收(kswapd)等相关机制,形成完整的内存管理知识体系。

http://www.dtcms.com/a/485766.html

相关文章:

  • 道路车辆功能安全标准(FuSa)基础(七)
  • 【Linux系列】解码 Linux 内存地图:从虚拟到物理的寻宝之旅
  • vue+spring boot 利用ffmpeg实现大视频切片
  • 长沙手机网站建设公司wordpress 做笔记
  • Java基于Web3j调用智能智能合约案例
  • 关于联想ThinkCentre M950t-N000 M大师电脑恢复预装系统镜像遇到的一点问题
  • 有关优化网站建设的书籍深圳网络推广方法
  • 招聘网站做竞品分析南昌网站seo多少钱
  • 【实战总结】Docker部署MySQL完整教程:附docker-compose模板与常用命令大全
  • C++ string类的使用
  • 【数据结构】:C 语言常见排序算法的实现与特性解析
  • C语言数据结构:算法复杂度(1)
  • 16km无人机WiFi中继图传模块,高速传输画质高清不卡顿
  • Linux系统C++开发环境搭建工具(二)—— etcd 使用指南
  • AI+大数据时代:如何从架构到生态重构时序数据库的价值?
  • 小小 Postgres,何以替代 Redis、MongoDB 甚至 ES?
  • Win10正式谢幕!附最后更新版本
  • 前端自动翻译插件webpack-auto-i18n-plugin的使用
  • 山东官方网站建设沧州网络推广渠成网络
  • 贺州网站建设公司家装设计需要学什么软件
  • 网站在百度上搜索不到丽水山耕品牌建设网站
  • 漂亮的门户网站dedecms游戏门户网站源码
  • thinkphp2.1网站挂文件国有企业投资建设项目
  • 网站首页的动态视频怎么做的建网站的流程和费用
  • 一些可以做翻译的网站微信小程序制作文档
  • 东莞公司网站开发首页制作教程
  • 河北大名网站建设招聘深圳网站设计首选柚米
  • 网站友链中英文外贸网站模版
  • html5企业网站模版经营一个网站要怎么做
  • 专业点网站制作公司龙泉市建设局网站