发布时间:2026/6/15 17:07:14
Linux mlock锁定页面mlock_all与mlockall系统调用 mlock 与 mlockall页面锁定机制的内核实现mm/mlock.c 中 mlock 的入口点是 SYSCALL_DEFINE2(mlock)经过 do_mlock 到达核心函数 mlock_fixup。mlock_fixup 的原型为cstatic int mlock_fixup(struct vm_area_struct *vma, struct vm_area_struct **prev,unsigned long start, unsigned long end, unsigned int newflags)该函数首先检查目标区间是否跨越多个 VMA。若 start 或 end 落在 VMA 内部而非边界则调用 split_vma 将其分割确保 [start, end) 精确对应一个完整的 VMA。这是 mlock 实现中第一个边界条件的处理点split_vma 可能因 slab 分配失败而返回 -ENOMEM导致整个 mlock 调用失败且用户空间看到部分 VMA 已被分裂。内核对此不做回滚因为分裂本身没有改变内存布局的可见语义。mlock_fixup 的第二个关键步骤是调用 populate_vma_page_rangecstatic long populate_vma_page_range(struct vm_area_struct *vma,unsigned long start, unsigned long end, bool *nonblocking)该函数通过 __get_user_pages 以 FOLL_MLOCK | FOLL_FORCE | FOLL_WRITE | FOLL_POPULATE 为标志遍历 [start, end) 区间的所有虚拟地址。__get_user_pages 会触发缺页将尚未物理分配的页面从零页或文件页分配物理内存然后通过 rmap 路径调用 mlock_page。对于已经存在的页面__mlock_vma_pages_range 使用 walk_page_range 配合 mlock_pte 回调进行页表扫描cstatic void mlock_pte(struct page *page, unsigned long addr,unsigned long end, struct mm_walk *walk){if (!page || is_zone_device_page(page))return;if (page_mapcount(page) !PageUnevictable(page))mlock_vma_page(page);}这里 page_mapcount(page) 的检查隐含了一个竞争场景当两个线程同时 mlock 同一个页面时第一个线程设置 PG_unevictable 后第二个线程的 page_mapcount 条件仍然满足但 PageUnevictable 已经为真因此 mlock_vma_page 会被跳过第二次操作成为空操作。这是故意的——mlock 是幂等的。但若其中一个线程先 munlock则会出现竞态munlock 清除 PG_mlocked 并试图将页面移回可回收 LRU 链表而此时另一个线程正通过 mlock_pte 重新锁定。这个窗口受 mmap_lock 保护mlock 和 munlock 都需要持有 mmap_lock 的写锁。页面级别的 mlock 状态由两个页标志位管理PG_mlocked 和 PG_unevictable。mlock_vma_page 的调用链为cvoid mlock_vma_page(struct page *page){/* Serialize with page migration */get_page(page);lru_add_drain();local_lock(mlock_lock);if (!TestSetPageMlocked(page))__mlock_page(page, lruvec);local_unlock(mlock_lock);put_page(page);}__mlock_page 检查页面是否已在 unevictable LRU 上。若不在则调用 lru_cache_add_inactive_or_unevictable 将其置入 unevictable 链表。这里使用 local_lock(mlock_lock) 来保护同一个 page 上的并发操作而非使用全局锁——页面隔离isolate_page和 LRU 操作本身通过 lruvec-lru_lock 做更粗粒度的同步。这一层的局部锁只防范 mlock_count 的并发更新。PG_mlocked 和 PG_unevictable 的关系需要精确理解PG_mlocked 是每页的锁定标记PG_unevictable 是 LRU 链表收容标记。当页面被 migrate 时migrate_page_move_mapping 会转移 PG_mlocked而新页面的 PG_unevictable 由迁移代码的 lru_cache_add 路径重新建立。这发生在 __unmap_and_move 中cstatic int __unmap_and_move(struct page *page, struct page *newpage,int force, enum migrate_mode mode){/* ... */if (PageMlocked(page)) {/* Holding the page locked ensures PG_mlocked cannot change */SetPageMlocked(newpage);ClearPageMlocked(page);}/* ... */}PageMlocked 的检查在 page lock 下进行——这是防止 PG_mlocked 在迁移期间被 concurrent munlock 修改的关键设计。同样mlock_vma_page 中的 get_page/put_page 确保页面在操作期间不会被释放。mlockall 的实现通过 apply_mlockall_flagscstatic int apply_mlockall_flags(int flags){struct vm_area_struct *vma, *prev NULL;vm_flags_t to_add 0;if (flags MCL_CURRENT) {/* ... */vma_iter_init(vmi, current-mm, 0);for_each_vma(vmi, vma) {vm_flags_t newflags vma-vm_flags ~VM_LOCKED_MASK;newflags | to_add;if (!(newflags VM_LOCKED))newflags ~VM_LOCKEDONFAULT;/* ... mlock_fixup on each vma ... */}}if (flags MCL_FUTURE)current-mm-def_flags | VM_LOCKED; // or VM_LOCKEDONFAULTreturn 0;}MCL_CURRENT 遍历所有 VMA对每个 VMA 调用 mlock_fixup这意味着 VMA 数量极大的进程如大量 mmap 了文件映射的数据库会在 mlockall(MCL_CURRENT) 中产生 O(n_vmas) 次 split_vma 和 populate_vma_page_range 调用且每次调用都会竞争 mmap_lock 的写锁。这是 mlockall 的核心性能问题即使每个 VMA 的页面已经 residentwalk_page_range 的页表扫描也会消耗大量 CPU 时间。MCL_FUTURE 的语义很简单只需修改 current-mm-def_flags。此后所有通过 do_mmap 创建的 VMA 都会继承 VM_LOCKED。但这里有一个容易被忽略的细节def_flags 不应用于通过 brk 扩展的堆也不应用于通过 shmat 获得的共享内存段。堆的扩展在 do_brk_flags 中独立处理shmat 的 VMA 创建在 do_mmap_pgoff 路径中也绕过了 def_flags 的继承——除非显式在架构代码中做了处理。MCL_ONFAULT 是 4.4 内核引入的标志使 mlock 不立即触发缺页而是设置 VM_LOCKEDONFAULT。该标志与 VM_LOCKED 的核心区别在于 populate_vma_page_range 的调用行为VM_LOCKEDONFAULT 不会在 mlock_fixup 中调用缺页。页面只在首次访问的缺页路径中通过 __mlock_page 被锁定。这是减少 mlockall 延迟的关键优化。RLIMIT_MEMLOCK 的检查由 can_do_mlock 和 account_locked_vm 共同完成cstatic inline int can_do_mlock(void){if (rlimit(RLIMIT_MEMLOCK) ! 0)return 1;if (capable(CAP_IPC_LOCK))return 1;return 0;}int account_locked_vm(struct mm_struct *mm, unsigned long pages, bool inc){if (inc) {if (!capable(CAP_IPC_LOCK) (mm-locked_vm pages) rlimit(RLIMIT_MEMLOCK))return -ENOMEM;mm-locked_vm pages;} else {mm-locked_vm - pages;}return 0;}locked_vm 存储在 mm_struct 中是进程粒度的计数。mlock_fixup 在调用 populate_vma_page_range 之前计算新增锁定页数量通过 account_locked_vm 预校验后再实际执行缺页。若缺页过程中某些页面分配失败当前实现不会回退 locked_vm 计数导致计数偏高但实际锁定页偏少的状况。这一差异在正常系统上极少出现但在 cgroup 内存上限压力下__get_user_pages 的分配可能因 memory cgroup 限制而部分失败。在 LRU 回收路径中页面被锁定在 unevictable list 上vmscan 的 shrink_active_list 和 shrink_inactive_list 会跳过这些页面。check_move_unevictable_pages 是专门用于在 compaction 后将页面移回 unevictable list 的函数处理因页面迁移导致的 LRU 归属变化。透明大页与 mlock 的交互较为复杂。当 __split_huge_page 分裂一个 2M THP 时所有 512 个 base page 继承 head page 的 PG_mlocked 标志但 mlock 的 per-page 计数需要同时调整。在 split_huge_page_to_list 中cvoid split_huge_page_to_list(struct page *page, struct list_head *list){/* ... */for (i 0; i HPAGE_PMD_NR; i) {if (PageMlocked(head))SetPageMlocked(page i);/* ... */}/* ... */}但如果 head page 只有一个 mlock 计数mlock_count分裂后 512 个 page 各自持有 PG_mlocked后续 munlock 时每个子页面都需要单独清除标志并尝试从 unevictable LRU 移除。这是 THP 分裂场景下 munlock 性能显著下降的原因——一次分裂将 O(1) 的 munlock 操作放大为 O(512)。mlock_count 是 struct page 中一个与 page-lru 复用的字段仅在 PG_mlocked 设置时有效。当同一个页面被多次 mlock例如父子进程 share 同一个匿名页后分别 mlockmlock_count 递增munlock 时递减至零才真正清除 PG_mlocked 并解除 unevictable 状态。mlock_count 的更新受 page lock 保护cstatic inline void __mlock_page(struct page *page, struct lruvec *lruvec){int nr_pages thp_nr_pages(page);if (page_mapcount(page) !PageUnevictable(page))lru_cache_add_inactive_or_unevictable(page, lruvec);if (TestSetPageMlocked(page))page-mlock_count nr_pages;elsepage-mlock_count nr_pages;}munlock 的批量处理是性能优化的关键。__munlock_pagevec 函数累积最多 MMVEC_PAGEVEC_SIZE默认 15个待清理页面后一次性获取 lruvec-lru_lock 进行处理减少锁获取的缓存行乒乓cstatic void __munlock_pagevec(struct pagevec *pvec, struct lruvec *lruvec){int i;for (i 0; i pagevec_count(pvec); i) {struct page *page pvec-pages[i];if (TestClearPageMlocked(page)) {/* ... */if (page_count(page) 1 page_mapcount(page))/* only referenced by mapping, can be reclaimed */__munlock_isolate_lru_page(page, lruvec, pvec_mapped);elseunlock_page(page);} else {unlock_page(page);}}/* ... batch move from unevictable to active/inactive ... */}这里 page_count(page) 1 page_mapcount(page) 的判定是 munlock 的一个微妙边界条件。该等式成立表示该页面仅被其 mapping 引用mapping 的 ref 计为 1和进程页表引用mapcount没有额外的 pin 或内核引用。如果页面还被 get_user_pages 固定例如被 v4l2 或 RDMA 驱动 pin 住page_count 会更高则 __munlock_isolate_lru_page 不会将该页移出 unevictable 链表避免回收正在被 DMA 写入的页面。mlock 与 OOM killer 的关系是设计上的权衡locked 页面不会被 OOM killer 回收。当系统内存不足且大量页面被 mlock 时OOM killer 只能选择杀死持有少量 mlock 的进程以释放非锁定内存。shrink_node 中的 shrink_list 调用 isolate_lru_pages 时指定了 sc-may_unevictable 标志但 unevictable LRU 上的页面不会被正常回收扫描触及。若 mlock 占用了大部分可回收内存系统可能被迫频繁触发 direct reclaim 和 OOM而 mlock 页面本身不受影响。mlockall(MCL_CURRENT | MCL_FUTURE) 是性能敏感场景中最重的操作。它将遍历所有 VMA通过 populate_vma_page_range 触发所有映射页面的缺页并设置 future VMA 继承 VM_LOCKED。对于内存映射数据库如 MongoDB 或 Oracle DBmlockall 可能导致持续数十秒的 mmap_lock 写锁持有期间所有线程的 page fault、mmap、munmap、brk 都会阻塞。缓解手段是将 mlockall 放在进程初始化阶段、使用 MCL_ONFAULT 延迟缺页或在 NUMA 系统上先 bind 内存再 mlock 以减少 remote node 的缺页延迟。最后一个需要注意的实现细节mlock 传递到 mmap_lock 的竞争。mlock 系统调用以 write lock 持有 mmap_lock但 populate_vma_page_range 中的 __get_user_pages 在缺页路径中可能触发 disk I/O文件映射的 page cache 页面不在内存时此时 mmap_lock 被 write held 会导致整个进程阻塞在 I/O 上。内核通过 FOLL_NOWAIT 标志限制某些场景的行为但 mlock 的缺页路径默认允许阻塞。在 mmap_lock 从读写信号量迁移到 rwsem 后write lock 下的 I/O 阻塞仍可能触发 lockdep 警告但实际执行中是通过 down_write_killable 来避免死锁。

相关新闻

2026/6/15 17:07:14

Linux memory compaction内存压缩触发条件

Linux Memory Compaction 触发条件mm/compaction.c 中的 compact_zone() 和 isolate_migratepages() 是内存压缩的核心路径,但触发这些函数的入口点散布在整个内存管理子系统中。理解触发条件是诊断碎片化问题的前提。c // mm/compaction.c enum compact_result { C…

2026/6/15 17:07:14

Linux memcg_wb_domain脏页写回domain与cgroup关联

Linux memcg_wb_domain脏页写回domain与cgroup关联memcg_wb_domain是memory cgroup与writeback(回写)子系统之间的关键桥梁。在cgroup v2环境下,每个memory cgroup拥有独立的脏页写回domain,这使得内核可以基于cgroup粒度控制脏页比例、回写阈值和回写速…

2026/6/15 17:07:14

3个关键升级让魔兽争霸3在现代电脑上焕发新生

3个关键升级让魔兽争霸3在现代电脑上焕发新生 【免费下载链接】WarcraftHelper Warcraft III Helper , support 1.20e, 1.24e, 1.26a, 1.27a, 1.27b 项目地址: https://gitcode.com/gh_mirrors/wa/WarcraftHelper 你是否还在为魔兽争霸3的卡顿、黑边、地图限制而烦恼&am…

2026/6/15 18:07:15

C语言进阶的书籍推荐

很多人看完学校教材、入门教程,只能写简单循环、分支,一碰到指针、动态内存、复杂工程代码就各种崩溃,代码漏洞多、看不懂底层逻辑。如果想要系统进阶 C 语言,业内公认三本经典进阶书籍,各司其职,一套补齐 …

2026/6/15 18:07:15

如何3分钟永久激活IDM:完整操作指南与最佳实践

如何3分钟永久激活IDM:完整操作指南与最佳实践 【免费下载链接】IDM-Activation-Script-ZH IDM激活脚本汉化版 项目地址: https://gitcode.com/gh_mirrors/id/IDM-Activation-Script-ZH 还在为Internet Download Manager(IDM)的30天试…

2026/6/15 18:07:15

如何用Maccy剪贴板管理器提升你的macOS工作效率

如何用Maccy剪贴板管理器提升你的macOS工作效率 【免费下载链接】Maccy Lightweight clipboard manager for macOS 项目地址: https://gitcode.com/gh_mirrors/ma/Maccy Maccy是一款专为macOS设计的轻量级剪贴板管理器,能够智能记录您的复制历史,…

2026/6/15 18:07:15

Anthropic探讨AI自进化,四位科学家智源大会共商人类与AI共生之道

01 什么是AI自进化?大概一周前,正在筹备上市的Anthropic在官方博客更新文章探讨“AI自进化”,指出AI参与构建更强模型比预想更快。AI自进化并非新技术,科学家既恐惧又利用其能力。田渊栋创立的RSI获6.5亿美元融资。在智源大会上&a…

2026/6/15 17:07:14

Linux mlock锁定页面mlock_all与mlockall系统调用

mlock 与 mlockall:页面锁定机制的内核实现 mm/mlock.c 中 mlock 的入口点是 SYSCALL_DEFINE2(mlock),经过 do_mlock 到达核心函数 mlock_fixup。mlock_fixup 的原型为: c static int mlock_fixup(struct vm_area_struct *vma, struct vm_a…

2026/6/15 16:07:12

MuleSoft+LLM企业级AI编排:打通语义鸿沟与系统韧性

1. 项目概述:当企业级集成平台遇上大语言模型,不是叠加,而是重铸工作流 “AI Orchestration in Action: How MuleSoft and LLMs Fuel the Future of Enterprise AI”——这个标题里藏着一个正在发生的、静默却剧烈的范式转移。它说的不是“用…