尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

tlb mmu_gather 延迟释放

tlb mmu_gather 延迟释放 mmu_gather是 Linux 内核内存管理中一个用于批量处理 TLB 刷新和物理页释放的核心机制。它的名字直译是“MMU 聚集”核心思想就是“攒着一起做”以避免频繁的 TLB 刷新和页面释放带来的开销。核心目标保证正确的操作顺序在进程退出或munmap时内核需要解除页表映射、刷新 TLB并释放物理页。这三步必须严格按顺序执行解除页表映射unhook page刷新 TLBTLB invalidate释放物理页free page绝对不能在刷新 TLB 之前释放物理页否则 CPU 可能通过旧的 TLB 条目访问已被回收的内存导致数据损坏或安全问题。mmu_gather就是为了保证这个顺序并把原本零散的“刷新-释放”操作批量聚集起来一次完成。核心数据结构struct mmu_gather是这一机制的主控结构体定义在include/asm-generic/tlb.h中。它记录了当前操作的所有关键信息mm正在操作哪个进程的虚拟内存。start/end本次操作涉及的虚拟地址范围。fullmm是否在操作整个地址空间如进程退出此时可以走“快速模式”优化。need_flush_all架构相关的标志。例如 x86-PAE 修改顶层页表条目后需要设置此标志来强制完整刷新 TLB。cleared_ptes/pmds/puds/p4ds记录在哪些页表层级上清除了条目。batch/active/local用于管理待释放页面的批次链表。local是内嵌在mmu_gather结构体中的“本地批次”如果页面太多会通过tlb_next_batch()分配新的批次页来扩容。工作流程收集、刷新、释放mmu_gather的使用遵循一套标准的 API 流程开始收集调用tlb_gather_mmu()初始化一个mmu_gather实例开始“攒”操作。收集页面在解除页表映射时不是立即释放物理页而是通过tlb_remove_page()等函数把struct page指针加入待释放队列。这些页面被批量存储在mmu_gather_batch结构中。执行刷新与释放当收集到一定数量或者整个操作结束时调用tlb_flush_mmu()或tlb_finish_mmu()。此时会先执行 TLB 刷新确保所有 CPU 都不再使用旧的映射然后才批量释放队列中积累的所有物理页。这种“先刷新后释放”的顺序是mmu_gather设计的基石它把多次独立的 TLB 刷新和页面释放合并为少量的大批量操作显著提升了内存回收的效率。延迟释放mmu_gather的延迟释放机制核心思路是把“释放”这个动作拆成两步先把要释放的页面“记账”到一个队列里等 TLB 刷新完成后再统一“结账”。延迟释放的关键批量链表mmu_gather内部维护一个批次链表struct mmu_gather_batch每个批次是一块能容纳多个struct page *指针的数组struct mmu_gather_batch { struct mmu_gather_batch *next; // 指向下一个批次 unsigned int nr; // 当前批次已存放的页面数 unsigned int max; // 当前批次的最大容量 struct encoded_page *pages[]; // 柔性数组存放页面指针 };mmu_gather结构体本身内嵌了一个本地批次local如果页面数量不多直接存在这里不需要额外分配内存。收集过程只记账不释放当内核需要释放一个页面时不会直接调用free_pages()而是调用tlb_remove_page()或tlb_remove_ptdesc()把struct page指针追加到批次数组中static inline void tlb_remove_page(struct mmu_gather *tlb, struct page *page) { tlb_remove_page_size(tlb, page, PAGE_SIZE); }在tlb_remove_page_size()内部会调用tlb_remove_page_ptdesc()最终通过tlb_remove_page_size()的底层逻辑将页面指针编码后存入批次的pages[]数组并递增nr。如果当前批次满了会调用tlb_next_batch()分配一个新的批次通过next指针链接到链表尾部继续存放。释放过程先刷新后结账当收集阶段结束进程退出、munmap完成等会调用tlb_finish_mmu()。它的核心流程是执行 TLB 刷新调用tlb_flush_mmu()通过flush_tlb_mm_range()等架构相关函数让所有 CPU 的 TLB 中相关条目失效。这一步必须在释放物理页之前完成。遍历批次链表逐页释放TLB 刷新完成后调用tlb_flush_mmu_free()遍历批次链表对每个页面调用free_pages_and_swap_cache()或__free_pages()进行真正的释放。回收批次内存如果某个批次不是内嵌的本地批次释放完页面后这个批次本身占用的内存页也会被释放。为什么必须延迟延迟释放的核心原因是顺序约束如果先释放物理页再刷新 TLB那么在 TLB 刷新完成前的窗口期内CPU 可能通过旧的 TLB 条目访问到已经被回收、可能被重新分配的物理页导致数据损坏或安全漏洞。通过mmu_gather把释放操作“挂起”确保 TLB 刷新一定发生在物理页释放之前从而保证了内存回收的正确性和安全性。性能收益除了正确性延迟释放还带来了显著的性能提升批量 TLB 刷新原本每次解除映射都要刷新一次 TLB现在可以合并为一次大范围刷新减少INVLPG或 CR3 重载的次数。批量页面释放原本逐页调用free_pages()现在可以批量处理减少伙伴系统的锁竞争和调用开销。减少缓存污染批处理减少了中间数据结构的访问频率对 CPU 缓存更友好。
返回列表