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片段
};
};
#牛客在线求职答疑中心##牛客解忧铺##嵌入式##牛客创作赏金赛#秋招之旅结束大半,目前也拿到了一些公司的offer,各种方向的: 芯片公司:兆芯,联发科,大普微等 产品公司:小米,联想,影石,虹软,诺瓦等 汽车公司:长安,博世,经纬恒润等 国央企研究所:32所,52所,712,星网,航空工业上电所 接下来会分享一些面试经验和学习经验