Linux内核6.6 内存管理(9)page数据结构
当开启MMU之后,CPU访问内存的最小单位就是page, 那Linux是如何去描述这个页呢?
在Linux内核中,每个物理页面都会对应一个struct page结构体,内核用它详细记录物理页的状态,接下来会详细解释有哪些参数
首先看一下这样的管理成本
struct page 的大小与架构相关(通常几十到几百字节),假设一个物理页面大小为 4KB,struct page 占 64 字节:
对于 1GB 物理内存,总共有 1GB / 4KB = 262,144 个页面,对应 262,144 × 64B ≈ 16MB 的内存开销(约占总内存的 1.6%)。
对于 1TB 内存,开销约为 16MB × 1024 = 16GB(约占总内存的 1.6%)。
结论:内存占用与物理内存大小成线性比例,通常控制在总内存的 1%~5% 之间,属于可接受范围。
- flags
https://elixir.bootlin.com/linux/v6.6/source/include/linux/mm_types.h#L75
首先介绍一下flags, 种类大概有这些
https://elixir.bootlin.com/linux/v6.6/source/include/linux/page-flags.h#L100
enum pageflags { PG_locked, 页面上锁,内存管理其他模块不能访问这个页面,以免发生竞争 PG_writeback, 向块设备回写 PG_referenced, 页面活跃程度 PG_uptodate, 页面的数据已经从块设备成功读取 PG_dirty, 页面内容发生改变,被改写后没有和外存储器进行同步 PG_lru, 内核使用LRU链表来管理活跃和不活跃页面 PG_head, /* Must be in bit 6 */ PG_waiters, /* Page has waiters, check its waitqueue. Must be bit #7 and in the same byte as "PG_locked" */ PG_active, 页面活跃程度 PG_workingset, PG_error, PG_slab, 使用slab分配器 PG_owner_priv_1, /* Owner use. If pagecache, fs may use*/ PG_arch_1, PG_reserved, PG_private, /* If pagecache, has fs-private data */ PG_private_2, /* If pagecache, has fs aux data */ PG_mappedtodisk, /* Has blocks allocated on-disk */ PG_reclaim, /* To be reclaimed asap */ 表示这个页面马上要被回收 PG_swapbacked, /* Page is backed by RAM/swap */既有页面缓存功能 PG_unevictable, /* Page is "unevictable" */不可护手 #ifdef CONFIG_MMU PG_mlocked, /* Page is vma mlocked */对应的VMA属于mlocked #endif #ifdef CONFIG_ARCH_USES_PG_UNCACHED PG_uncached, /* Page has been mapped as uncached */ #endif #ifdef CONFIG_MEMORY_FAILURE PG_hwpoison, /* hardware poisoned page. Don't touch */ #endif #if defined(CONFIG_PAGE_IDLE_FLAG) && defined(CONFIG_64BIT) PG_young, PG_idle, #endif #ifdef CONFIG_ARCH_USES_PG_ARCH_X PG_arch_2, PG_arch_3, #endif __NR_PAGEFLAGS, PG_readahead = PG_reclaim, /* * Depending on the way an anonymous folio can be mapped into a page * table (e.g., single PMD/PUD/CONT of the head page vs. PTE-mapped * THP), PG_anon_exclusive may be set only for the head page or for * tail pages of an anonymous folio. For now, we only expect it to be * set on tail pages for PTE-mapped THP. */ PG_anon_exclusive = PG_mappedtodisk, /* Filesystems */ PG_checked = PG_owner_priv_1, /* SwapBacked */ PG_swapcache = PG_owner_priv_1, /* Swap page: swp_entry_t in private */ //表明页面处于交换缓存 /* Two page bits are conscripted by FS-Cache to maintain local caching * state. These bits are set on pages belonging to the netfs's inodes * when those inodes are being locally cached. */ PG_fscache = PG_private_2, /* page backed by cache */ /* XEN */ /* Pinned in Xen as a read-only pagetable page. */ PG_pinned = PG_owner_priv_1, /* Pinned as part of domain save (see xen_mm_pin_all()). */ PG_savepinned = PG_dirty, /* Has a grant mapping of another (foreign) domain's page. */ PG_foreign = PG_owner_priv_1, /* Remapped by swiotlb-xen. */ PG_xen_remapped = PG_owner_priv_1, /* non-lru isolated movable page */ PG_isolated = PG_reclaim, /* Only valid for buddy pages. Used to track pages that are reported */ PG_reported = PG_uptodate, #ifdef CONFIG_MEMORY_HOTPLUG /* For self-hosted memmap pages */ PG_vmemmap_self_hosted = PG_owner_priv_1, #endif /* * Flags only valid for compound pages. Stored in first tail page's * flags word. Cannot use the first 8 flags or any flag marked as * PF_ANY. */ /* At least one page in this folio has the hwpoison flag set */ PG_has_hwpoisoned = PG_error, PG_hugetlb = PG_active, PG_large_rmappable = PG_workingset, /* anon or file-backed */ };
2. _refconut
https://elixir.bootlin.com/linux/v6.6/source/include/linux/mm_types.h#L181
老版本kernel里面使用的是_count, 这里不用管。
它的作用:
记录有多少个使用者(虚拟页映射,内核数据结构引用)在使用该物理页
使用场景:
内存回收:当引用计数为0时,物理页可被安全回收
写时复制:多个虚拟页共享一个物理页时,通过_refcount来判断是否允许复制或者释放,比如父子进程fork时,并不会直接分配新的物理页,所以此时共享一个物理页,_refcount此时为2,但是需要复制物理页时,此时调整引用计数,那么父进程_refcount -1 = 1, 子进程_refcount变为1
禁止直接使用:
原子操作安全性/隐藏实现细节
3._mapcount
_refcount
记录所有使用者(包括内核),而 _mapcount
仅统计用户空间映射的引用
记录有多少个用户空间进程通过页表直接映射了该物理页。每次用户进程通过 mmap()
、fork()
等系统调用将物理页映射到虚拟地址空间时,_mapcount
递增。
字段 | 含义 | 统计范围 | 典型场景 |
| 物理页的总引用计数(所有使用者) | 内核 + 用户空间 | 写时复制(COW)、内核模块临时引用、页缓存共享 |
| 物理页被用户空间页表映射的次数 | 仅用户空间 | 内存回收(判断页是否可被交换)、页面迁移(避免迁移正在被用户访问的页) |
4.mapping
struct address_space
是 Linux 内核中用于管理文件映射和页面缓存的核心数据结构。每个文件(或匿名内存区域)都关联一个 address_space
,它负责:
- 维护文件内容与物理内存页的映射关系(即 page cache)。
- 管理文件的读写操作(通过
readpage
、writepage
等回调函数)。 - 处理内存页的换入换出(swap)。
5.virtual
通过 mapping
找到正确的物理页,通过 virtual
(虚拟地址)实际操作该物理页的数据。
作用:
允许内核代码直接访问物理页(通过虚拟地址)
|
| 建立物理页与文件 / 进程 的关联(即页缓存的逻辑映射) | - 文件映射(如 - 块设备缓存 - 页面回收(LRU 链表) |
|
| 记录物理页在内核空间的虚拟地址(即物理页的内核侧地址映射) | - 内核直接访问物理页(如
|
6.链表字段
https://elixir.bootlin.com/linux/v6.6/source/include/linux/mm_types.h#L90
union { struct list_head lru; //页缓存/匿名页的LRU管理,用于内存回收 struct { void *__filler; unsigned int mlock_count;//不可回收页,当>0时,页面处于不可回收状态 }; struct list_head buddy_list; //空闲页,加入伙伴系统的空闲页链表,用于内存分配 struct list_head pcp_list;//加入 CPU 本地页缓存链表,用于优化内存分配性能(减少锁竞争) };
7.page pool for net
网络专用,为高性能网络数据处理优化内存分配
https://elixir.bootlin.com/linux/v6.6/source/include/linux/mm_types.h#L119
struct { /* page_pool used by netstack */ unsigned long pp_magic;//验证页面是否由页面池分配,回收时会检查,确保回收只属于页面池的页面 struct page_pool *pp;//页面池指针 unsigned long _pp_mapping_pad;//用于内存对齐的填充字段。主要目的是确保结构体中的后续字段(如 dma_addr)在特定的内存边界上对齐,以提高内存访问效率。 unsigned long dma_addr;//存储用于直接内存访问(DMA)的物理地址 union {//32位 unsigned long dma_addr_upper; atomic_long_t pp_frag_count; //跟踪片段页的使用计数,比如一个物理页可以分为多个逻辑片段,如4KB可以拆成4个1KB片段 }; };#牛客在线求职答疑中心##牛客解忧铺##嵌入式##牛客创作赏金赛#