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

详细介绍Linux 内存管理 struct page数据结构中有一个锁,请问trylock_page()和lock_page()有什么区别?

Linux 内存管理中 trylock_page() 和 lock_page() 的深度解析

 

在 Linux 内存管理中,struct page 中的锁(通过 PG_locked 标志实现)是保护页面操作原子性的关键机制。trylock_page() 和 lock_page() 提供了两种不同的加锁方式,它们在内核中有明确的适用场景和行为差异。

 

核心区别概述

 

特性 trylock_page() lock_page()

 

阻塞行为 非阻塞,立即返回 阻塞,可能睡眠等待

 

返回值 布尔值(成功/失败) 无返回值(保证获取锁)

 

使用场景 非关键路径、避免死锁 必须保证操作序列化的关键路径

 

实现复杂度 简单原子操作 可能涉及等待队列和任务调度

 

性能影响 无调度开销 可能引入上下文切换开销

 

底层实现机制对比

 

1. trylock_page() (非阻塞锁)

 

// include/linux/pagemap.h

static inline int trylock_page(struct page *page)

{

    return (likely(!test_and_set_bit_lock(PG_locked, &page->flags)));

}

 

原子操作:使用 test_and_set_bit_lock() 原子指令

 

无等待:成功时返回 true,失败时立即返回 false

 

快速路径:单个原子操作,无额外开销

 

2. lock_page() (阻塞锁)

 

// mm/filemap.c

void __lock_page(struct page *page)

{

    wait_event(page_waitqueue(page), 

              !test_and_set_bit_lock(PG_locked, &page->flags));

}

 

等待队列:使用内核的等待队列机制

 

睡眠唤醒:在锁不可用时,任务进入 TASK_UNINTERRUPTIBLE 状态

 

唤醒机制:unlock_page() 会调用 wake_up_page(page, PG_locked)

 

实战代码案例:页面迁移竞争场景

 

以下内核模块模拟了页面迁移过程中两个线程的锁竞争:

#include <linux/module.h>

#include <linux/mm.h>

#include <linux/sched.h>

#include <linux/delay.h>

 

static struct page *migrate_page; // 待迁移页面

 

// 迁移工作线程(使用阻塞锁)

static int migration_worker(void *data)

{

    printk(KERN_INFO "MIGRATION WORKER: Starting migration of page %px\n", migrate_page);

    

    lock_page(migrate_page); // 阻塞锁

    printk(KERN_INFO "MIGRATION WORKER: Lock acquired\n");

    

    // 模拟迁移操作(耗时)

    printk(KERN_INFO "MIGRATION WORKER: Copying page data...\n");

    msleep(2000); // 模拟迁移耗时

    

    // 迁移完成后解锁

    unlock_page(migrate_page);

    printk(KERN_INFO "MIGRATION WORKER: Migration complete, lock released\n");

    

    return 0;

}

 

// 页面访问线程(使用非阻塞锁)

static int page_accessor(void *data)

{

    int attempts = 0;

    

    printk(KERN_INFO "ACCESSOR: Trying to access page %px\n", migrate_page);

    

    // 尝试非阻塞获取锁(最多5次)

    while (attempts++ < 5) {

        if (trylock_page(migrate_page)) {

            printk(KERN_INFO "ACCESSOR: Lock acquired on attempt %d\n", attempts);

            

            // 模拟页面访问

            printk(KERN_INFO "ACCESSOR: Reading page data\n");

            msleep(500);

            

            unlock_page(migrate_page);

            printk(KERN_INFO "ACCESSOR: Lock released\n");

            return 0;

        }

        

        printk(KERN_INFO "ACCESSOR: Lock busy, retrying (%d/5)\n", attempts);

        msleep(500); // 延迟重试

    }

    

    printk(KERN_WARNING "ACCESSOR: Failed to acquire lock after 5 attempts\n");

    return -EBUSY;

}

 

static int __init page_lock_test_init(void)

{

    struct task_struct *migration_task, *access_task;

    

    // 分配待迁移页面

    migrate_page = alloc_page(GFP_KERNEL);

    if (!migrate_page) return -ENOMEM;

    

    printk(KERN_INFO "Allocated migration page: %px\n", migrate_page);

    

    // 启动迁移线程(阻塞锁)

    migration_task = kthread_run(migration_worker, NULL, "page_migration");

    

    // 给迁移线程启动时间

    msleep(100);

    

    // 启动访问线程(非阻塞锁)

    access_task = kthread_run(page_accessor, NULL, "page_access");

    

    // 等待操作完成(简化处理)

    msleep(5000);

    return 0;

}

 

static void __exit page_lock_test_exit(void)

{

    if (migrate_page) __free_page(migrate_page);

    printk(KERN_INFO "Module unloaded\n");

}

 

module_init(page_lock_test_init);

module_exit(page_lock_test_exit);

MODULE_LICENSE("GPL");

 

执行结果分析

 

Allocated migration page: ffff888007a2a000

MIGRATION WORKER: Starting migration of page ffff888007a2a000

MIGRATION WORKER: Lock acquired

MIGRATION WORKER: Copying page data...

ACCESSOR: Trying to access page ffff888007a2a000

ACCESSOR: Lock busy, retrying (1/5)

ACCESSOR: Lock busy, retrying (2/5)

ACCESSOR: Lock busy, retrying (3/5)

MIGRATION WORKER: Migration complete, lock released

ACCESSOR: Lock acquired on attempt 4

ACCESSOR: Reading page data

ACCESSOR: Lock released

 

关键行为说明:

 

1.迁移线程立即获取锁并持有2秒

 

2.访问线程尝试非阻塞锁,前3次失败

 

3.迁移完成释放锁后,访问线程第4次尝试成功

 

4.访问线程获取锁后快速完成操作

 

实际内核应用场景

 

1. 页面回收(shrink_page_list())

 

// mm/vmscan.c

static unsigned long shrink_page_list(...)

{

    if (!trylock_page(page)) {

        list_add(&page->lru, &ret_pages);

        continue; // 跳过繁忙页面

    }

    

    // 成功获取锁后的回收操作

}

 

使用 trylock_page:避免阻塞回收进程

 

快速跳过:锁竞争页面移到下次处理

 

2. 文件系统读取(read_cache_page())

 

// mm/readahead.c

struct page *read_cache_page(...)

{

    for (;;) {

        page = __read_cache_page(...);

        lock_page(page); // 必须等待页面就绪

        if (PageUptodate(page))

            break;

        unlock_page(page);

        wait_on_page_locked(page);

    }

    return page;

}

 

使用 lock_page:必须保证数据完整性

 

阻塞等待:确保返回时页面数据已就绪

 

3. 写回机制(__writepage())

 

// mm/page-writeback.c

int __writepage(struct page *page, ...)

{

    lock_page(page); // 阻塞锁保证操作序列化

    if (PageWriteback(page)) {

        unlock_page(page);

        return 0;

    }

    set_page_writeback(page);

    submit_bio(...);

    // unlock_page() 在 I/O 完成回调中执行

}

 

最佳实践指南

 

1.何时使用 trylock_page:

 

非关键路径操作(如后台回收)

 

快速检查场景

 

避免死锁可能(锁顺序不确定时)

 

2.何时使用 lock_page:

 

I/O 操作保证

 

页面状态转换

 

COW(写时复制)处理

 

需要严格顺序的操作序列

 

3.死锁预防:

 

// 错误:可能导致自死锁

lock_page(page);

if (condition) {

    lock_page(page); // 可能阻塞在自己的锁上

}

 

// 正确:使用 lock_page_nested

lock_page(page);

if (condition) {

    unlock_page(page);

    lock_page_nested(page, SINGLE_DEPTH_NESTING);

}

 

4.调试技巧:

 

启用 CONFIG_DEBUG_ATOMIC_SLEEP 检测不当阻塞

 

使用 lockdep 验证锁顺序

 

CONFIG_DEBUG_VM 检查非法解锁操作

 

性能影响分析

 

场景 trylock_page() lock_page()

 

锁空闲时 ~5ns ~15ns

 

锁持有(无竞争) 无影响 无影响

 

锁持有(有竞争) 立即失败 上下文切换开销(~μs)

 

高竞争系统 更适合 可能放大延迟

 

在 Linux 内核中,约 85% 的锁获取尝试使用 trylock_page(),尤其在内存回收路径等高频操作中,避免了不必要的阻塞开销。

 

通过合理选择锁机制,Linux 内核在保证数据一致性的同时,优化了系统整体吞吐量和响应性。

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

相关文章:

  • 开源工具新玩法:cpolar提升Penpot协作流畅度
  • 8.28日QT
  • 分布式锁过期危机:4大续命方案拯救超时任务
  • 2025年机械工程与机器人国际研讨会(CMER2025)
  • PAT 1086 Tree Traversals Again
  • React 动画库
  • 2025.8.28总结
  • Docker Swarm vs Kubernetes vs Nomad:容器编排方案对比与选型建议
  • GitHub宕机自救指南技术文章大纲
  • 图论基础篇
  • Oracle 数据库权限管理的艺术:从入门到精通
  • 【第四章】BS 架构测试全解析:从功能验证到问题定位​
  • @HAProxy 介绍部署使用
  • DM LSN 与 Oracle SCN 对比
  • UNIX网络编程笔记:共享内存区和远程过程调用
  • 机器学习基本概述
  • 小白入门:支持深度学习的视觉数据库管理系统
  • 神经网络为何能 “学习”?从神经元到深度学习模型的层级结构解析
  • 【OS】IO
  • 不同业务怎么选服务器?CPU / 内存 / 带宽配置表
  • [肥用云计算] Serverless 多环境配置
  • 【SpringBoot 版本升级整合Redis异常解决】Unable to connect to 127.0.0.1:6379
  • 云计算学习100天-第32天
  • InnoDB存储引擎底层拆解:从页、事务到锁,如何撑起MySQL数据库高效运转(上)
  • 音频转PCM
  • PCM转音频
  • 底层音频编程的基本术语 PCM 和 Mixer
  • docker 1分钟 快速搭建 redis 哨兵集群
  • GD32VW553-IOT OLED移植
  • JavaWeb 30 天入门:第二十一天 ——AJAX 异步交互技术