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

Linux内核架构浅谈37-深入理解Linux页帧标志:从PG_locked到PG_dirty的核心原理与实践

1. 页帧标志的核心作用

在Linux内核中,每个物理内存页(页帧)都通过struct page结构体描述,而flags成员是该结构体的“灵魂”——它用一系列比特位(页帧标志)记录页帧的状态、属性和使用场景。这些标志是内核内存管理、I/O操作、进程调度等子系统协同工作的“语言”,确保页帧在多任务、多核心环境下被安全、高效地使用。

页帧标志的设计遵循两个核心原则:

  • 原子性:所有标志操作(设置、清除、查询)均通过原子操作实现,避免多核心并发访问导致的竞态条件。
  • 体系结构无关性:标志的语义在所有Linux支持的体系结构(x86、ARM、ARM64等)中保持一致,具体实现由内核统一封装。

本文将聚焦最常用的两类页帧标志(PG_lockedPG_dirty),并延伸讲解其他关键标志的用途,最后通过代码示例展示如何在实际开发中操作这些标志。

2. PG_locked:页帧的“独占锁”

2.1 核心语义

PG_locked标志用于标记页帧处于“锁定状态”——此时内核的其他部分(如进程、中断处理程序)不允许访问该页帧,直至锁被释放。这种“独占性”是避免页帧在关键操作(如I/O数据传输、内存迁移)中被意外修改的核心保障。

典型应用场景包括:

  • 从磁盘读取数据到页帧时,锁定页帧防止进程同时写入该页,导致数据不一致。
  • 页帧迁移(如NUMA节点间的内存平衡)时,锁定页帧避免迁移过程中页帧被释放或修改。
  • 内存压缩(如ZRAM)时,锁定页帧确保压缩过程中数据不被篡改。

2.2 工作机制

当内核需要锁定页帧时,会通过SetPageLocked()宏设置PG_locked标志;释放时则调用ClearPageLocked()。如果一个进程试图访问已锁定的页帧(如通过read()系统调用读取页数据),内核会通过wait_on_page_locked()函数让进程进入睡眠状态,直至锁被释放后自动唤醒。

下图展示了PG_locked的生命周期:

// 1. 锁定页帧(原子操作)
struct page *page = alloc_page(GFP_KERNEL); // 分配一个页帧
if (!page) return -ENOMEM;
SetPageLocked(page); // 标记页帧为锁定状态// 2. 执行关键操作(如I/O传输)
struct bio *bio = bio_alloc(GFP_KERNEL, 1);
bio_add_page(bio, page, PAGE_SIZE, 0);
submit_bio(READ, bio); // 提交I/O请求,读取数据到页帧// 3. 等待I/O完成(期间页帧保持锁定)
wait_for_completion(&bio->completion);// 4. 释放锁,允许其他进程访问
ClearPageLocked(page);
bio_put(bio);
__free_page(page); // 释放页帧

2.3 常见误区

开发者常将PG_locked与内核自旋锁(spinlock_t)混淆,二者的核心区别在于:

特性PG_lockedspinlock_t
作用对象单个页帧(struct page共享数据结构(如链表、哈希表)
阻塞行为等待时进程进入睡眠(可中断)等待时自旋(不睡眠,适用于中断上下文)
使用场景长时间操作(如I/O传输)短时间临界区(如修改链表节点)

例如:在磁盘I/O场景中,若用自旋锁保护页帧,会导致CPU在I/O等待期间持续自旋,浪费计算资源;而PG_locked配合睡眠机制,可让CPU调度其他进程,提升系统整体吞吐量。

3. PG_dirty:追踪页帧的“修改状态”

3.1 核心语义

PG_dirty标志用于标记页帧的“脏状态”——即页帧在内存中的数据与持久化存储(如磁盘、SSD)中的数据不一致(页帧被修改过)。内核通过该标志识别需要“回写”(Writeback)的页帧,确保修改后的数据最终被同步到磁盘,避免系统崩溃导致数据丢失。

需要注意的是:PG_dirty仅追踪“内存与磁盘的一致性”,不关心页帧被哪个进程修改。即使多个进程共享同一页帧(如共享内存),只要有一个进程修改了页帧,PG_dirty就会被设置。

3.2 与回写机制的协作

Linux内核通过“页回写子系统”(Page Writeback Subsystem)管理脏页的同步,PG_dirty是该子系统的核心触发器。其协作流程如下:

  1. 脏页产生:进程通过write()mmap()修改页帧时,内核自动设置PG_dirty(通过SetPageDirty())。
  2. 脏页追踪:内核将所有脏页加入“活跃脏页链表”(zone->active_list),定期扫描并判断是否需要回写。
  3. 回写触发:当脏页数量超过阈值(如内存的20%),或脏页存活时间超过阈值(如30秒),内核会唤醒pdflush线程(或kworker工作队列),将脏页数据同步到磁盘。
  4. 回写完成:数据同步到磁盘后,内核调用ClearPageDirty()清除PG_dirty标志,标记页帧恢复“干净状态”。
示例:手动触发脏页回写

在调试场景中,开发者可能需要手动将某个脏页同步到磁盘,可通过以下代码实现:

#include 
#include // 手动回写单个脏页
int sync_dirty_page(struct page *page) {if (!PageDirty(page)) {pr_info("Page is not dirty, no need to sync\n");return 0;}// 锁定页帧,避免回写期间被修改if (!trylock_page(page)) { // 非阻塞尝试锁定,避免睡眠pr_err("Failed to lock page\n");return -EBUSY;}// 触发回写:将页帧数据同步到磁盘struct address_space *mapping = page->mapping;if (mapping) {// 调用回写函数,等待回写完成write_one_page(page, mapping, 0);// 回写完成后清除脏标志ClearPageDirty(page);}// 释放页帧锁unlock_page(page);pr_info("Dirty page synced to disk successfully\n");return 0;
}

注意:write_one_page()是内核内部函数,实际开发中更推荐使用sync_page_range()msync()(用户空间)等标准接口。

4. 其他关键页帧标志

除了PG_lockedPG_dirty,Linux内核还定义了多个常用页帧标志,以下是最核心的几个:

4.1 PG_referenced与PG_active:页帧的“活跃度”追踪

这两个标志配合使用,用于判断页帧的“活跃度”,是页帧回收(Page Reclaim)子系统的核心依据:

  • PG_referenced:标记页帧最近被访问过(如进程读取页数据)。CPU会自动设置该标志,内核定期扫描并根据该标志判断页帧是否“有用”。
  • PG_active:标记页帧属于“活跃页链表”(zone->active_list)。活跃页帧被回收的优先级低于“不活跃页链表”(zone->inactive_list)中的页帧。

例如:当内存不足时,内核会优先回收PG_active=0PG_referenced=0的页帧,因为这类页帧长时间未被访问,回收后对系统性能影响最小。

4.2 PG_highmem:高端内存页的“身份标识”

该标志仅在32位体系结构(如x86)中有效,用于标记页帧属于“高端内存”(HighMem)——即无法直接映射到内核虚拟地址空间的页帧(通常是物理内存超过896MB的部分)。

对于高端内存页,内核需要通过kmap()函数将其临时映射到内核地址空间后才能访问,访问完成后调用kunmap()释放映射。例如:

// 访问高端内存页的示例
struct page *highmem_page = alloc_page(GFP_HIGHUSER); // 分配高端内存页
if (!highmem_page) return -ENOMEM;// 临时映射高端内存页到内核地址空间
void *vaddr = kmap(highmem_page);
if (!vaddr) {__free_page(highmem_page);return -ENOMEM;
}// 访问页帧数据
memcpy(vaddr, "hello, highmem", 14);// 释放映射并释放页帧
kunmap(highmem_page);
__free_page(highmem_page);

4.3 PG_swapcache:交换缓存页的“特殊标记”

当页帧被换出到交换分区(如SSD、磁盘)后,若该页帧再次被访问,内核会将其从交换分区读回内存,并标记为PG_swapcache——表示该页帧属于“交换缓存”,其数据与交换分区中的数据保持一致。

该标志的核心作用是避免同一页帧被多次换入换出:若页帧已在交换缓存中,内核直接复用该页帧,无需重复从交换分区读取数据。

5. 页帧标志操作API与实践示例

5.1 核心操作宏

内核为每个页帧标志提供了统一的操作宏,下表列出最常用的API(以PG_lockedPG_dirty为例):

操作类型PG_locked相关宏PG_dirty相关宏功能描述
查询PageLocked(page)PageDirty(page)判断标志是否置位,返回布尔值
设置SetPageLocked(page)SetPageDirty(page)原子设置标志,忽略原状态
清除ClearPageLocked(page)ClearPageDirty(page)原子清除标志,忽略原状态
测试并设置TestSetPageLocked(page)TestSetPageDirty(page)原子设置标志,并返回原状态

所有宏均定义在<linux/page-flags.h>头文件中,且操作均为原子性,可安全用于多核心环境。

5.2 实践示例:实现一个简单的页帧锁管理器

以下代码实现一个简单的页帧锁管理器,支持锁定、释放页帧,并等待锁释放,模拟实际驱动开发中的页帧保护逻辑:

#include 
#include 
#include // 等待队列:用于等待页帧解锁
static wait_queue_head_t page_lock_waitq;// 初始化等待队列
static int __init page_lock_manager_init(void) {init_waitqueue_head(&page_lock_waitq);pr_info("Page lock manager initialized\n");return 0;
}// 锁定页帧,若已锁定则等待
int lock_page_safe(struct page *page) {// 循环等待,直到页帧解锁while (1) {// 尝试锁定页帧(非阻塞)if (TestSetPageLocked(page)) {// 页帧已锁定,进入等待队列睡眠pr_debug("Page %p is locked, waiting...\n", page);wait_event_interruptible(page_lock_waitq, !PageLocked(page));// 检查是否被信号中断if (signal_pending(current)) {pr_err("Wait for page lock interrupted by signal\n");return -ERESTARTSYS;}} else {// 锁定成功pr_debug("Page %p locked successfully\n", page);return 0;}}
}// 释放页帧锁,并唤醒等待队列
void unlock_page_safe(struct page *page) {if (!PageLocked(page)) {pr_warn("Page %p is not locked, skip unlock\n", page);return;}// 释放锁ClearPageLocked(page);// 唤醒所有等待该页帧的进程wake_up_all(&page_lock_waitq);pr_debug("Page %p unlocked, woke up waiters\n", page);
}static void __exit page_lock_manager_exit(void) {pr_info("Page lock manager exited\n");
}module_init(page_lock_manager_init);
module_exit(page_lock_manager_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Simple Page Lock Manager");
MODULE_AUTHOR("Linux Kernel Developer");

该示例的核心逻辑:

  • 使用wait_event_interruptible()让进程在页帧锁定时进入睡眠,避免CPU自旋浪费资源。
  • 释放锁时调用wake_up_all()唤醒所有等待的进程,确保公平性。
  • 支持信号中断(signal_pending(current)),符合Linux信号处理的标准语义。

6. 总结

页帧标志是Linux内核内存管理的“基石”,其中PG_lockedPG_dirty分别解决了页帧的“独占访问”和“数据一致性”问题,其他标志(如PG_referencedPG_highmem)则支撑起页帧回收、高端内存管理等复杂功能。

在实际开发中,操作页帧标志需遵循以下最佳实践:

  • 始终使用内核提供的标准宏(如SetPageLocked()PageDirty()),避免直接操作struct page->flags导致的兼容性问题。
  • 锁定页帧后,务必在操作完成后释放锁,避免页帧长期锁定导致内存泄漏或死锁。
  • 在多核心环境下,通过wait_on_page_locked()wait_on_page_writeback()等函数安全等待页帧状态变化,避免轮询浪费CPU资源。

深入理解页帧标志的语义和机制,是掌握Linux内核内存管理、设备驱动开发、虚拟化等高级主题的关键前提。阅读内核源代码(linux/page-flags.hmm/page_alloc.c)加深理解。

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

相关文章:

  • 建设网站的功能及目的是什么wordpress好用的地图
  • 佛山找企业的网站WORDPRESS添加前台会员注册
  • 【完整源码+数据集+部署教程】 【零售和消费品&存货】条形码检测系统源码&数据集全套:改进yolo11-TADDH
  • 上海建网站公司排名常用的设计软件有哪些
  • 10、一个简易 vector:C++ 模板与 STL
  • 营销网站设计网站获取访客qq 原理
  • Aosp14桌面壁纸和锁屏壁纸的设置和加载分析
  • 作业1.1
  • 减肥养生网站建设360极速浏览器网站开发缓存
  • 网站开发人员资质北京汽车网站建设
  • 网站模板修改教程活动推广文案
  • 专门做旅行用品的网站沈阳市浑南区城乡建设局网站
  • 哪一个景区网站做的最成熟wordpress loop count
  • JDK高版本特性总结与ZGC实践
  • 搜索优化整站优化在线阅读网站开发教程
  • 网站免费正能量小说爱网之家
  • 网站建设数据库系统网站开发api平台
  • 信管女生做网站开发兰州大学网页与网站设计
  • Unet-初探
  • 小鱼儿网站做啥用的悬赏做海报的网站
  • 如何做网站数据库关于网站运营
  • 免费培训seo网站找人做的网站怎么运行
  • 织梦网站空间如何清理长沙网站建设公司排名
  • 网站开发 自我评价wordpress搭建群空间
  • 高效构建AI智能体的上下文工程
  • 网站被墙 做301跳转企业网站制作免费下载
  • 网站建设性能指标做游戏交易网站有哪些内容
  • dirsearch工具的使用
  • 网站素材 图标新零售分销系统开发
  • 无忧seo博客关键词优化排名易下拉软件