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% 之间,属于可接受范围。

  1. 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 递增。

字段

含义

统计范围

典型场景

_refcount

物理页的总引用计数(所有使用者)

内核 + 用户空间

写时复制(COW)、内核模块临时引用、页缓存共享

_mapcount

物理页被用户空间页表映射的次数

仅用户空间

内存回收(判断页是否可被交换)、页面迁移(避免迁移正在被用户访问的页)

4.mapping

struct address_space 是 Linux 内核中用于管理文件映射页面缓存的核心数据结构。每个文件(或匿名内存区域)都关联一个 address_space,它负责:

  • 维护文件内容与物理内存页的映射关系(即 page cache)。
  • 管理文件的读写操作(通过 readpagewritepage 等回调函数)。
  • 处理内存页的换入换出(swap)。

5.virtual

通过 mapping 找到正确的物理页,通过 virtual (虚拟地址)实际操作该物理页的数据。

作用:

允许内核代码直接访问物理页(通过虚拟地址)

mapping

struct address_space*

建立物理页文件 / 进程

的关联(即页缓存的逻辑映射)

- 文件映射(如mmap

- 块设备缓存

- 页面回收(LRU 链表)

virtual

void*

记录物理页在内核空间的虚拟地址(即物理页的内核侧地址映射)

- 内核直接访问物理页(如

kmalloc分配的内存)

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片段
			};
		};

#牛客在线求职答疑中心##牛客解忧铺##嵌入式##牛客创作赏金赛#
全部评论

相关推荐

06-27 18:53
门头沟学院 Java
这样才知道自己不适合搞代码,考公去咯
只爱喝白开水:我也发现不适合搞代码,打算转非技术方向了
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务