内存管理-5-物理内存数据结构-3-struct page

基于msm-5.4


一、struct page简介

物理页帧,系统中的每个物理页面都有一个与之关联的 struct page,用于跟踪我们当前正在使用该页面的用途。请注意,我们无法跟踪哪些任务正在使用页面,但如果它是页面缓存页面,rmap 结构可以告诉我们谁在映射它。

如果您使用 alloc_pages() 分配页面,则可以将 struct page 中的部分空间用于自己的目的。主联合中的五个字(40B)可用,但第一个字的位 0 除外,必须保持清0。许多用户使用此字来存储指向保证对齐的对象的指针。如果您使用与 page->mapping
相同的存储,则必须在释放页面之前将其恢复为 NULL。

如果您的页面不会映射到用户空间,您也可以使用 mapcount 联合中的四个字节,但必须在释放它之前调用 page_mapcount_reset()。

如果要使用 refcount 字段,则必须以其他 CPU 临时增加然后减少 refcount 的方式使用它,而不会导致问题。从 alloc_pages() 接收页面时,refcount 将为正数。

如果分配 order > 0 的页面,则可以使用每个子页面中的某些字段,但之后可能需要恢复它们的值。

SLUB 使用 cmpxchg_double() 原子更新其空闲列表和计数器。这要求空闲列表和计数器相邻且双字8字节对齐。 我们将所有结构页面对齐到双字边界,
并确保“空闲列表”在结构内对齐。

page结构里面有很多union类型,因为page可以用来表示不同类型的物理内存。
page cache: 页缓存,磁盘在去读写的时候在内存中会有一个缓存,叫页缓存。平时申请的匿名页,也会使用page cache类型表示。
page_pool:
slab:
Tail pages:
Second tail page:
Page table:

系统在初始化的时候,对于每一个物理页帧,都会定义一个 struct page 结构去表示它。比如有一千个页帧,就要定义一千个page结构体,这些page结构体也会存放在内存中,使用 vmemmap 指针指向这个区域。此时物理页帧号pfn和page结构之间就存在了一一对应的关系,因为两者都是线性排列的。

#define __pfn_to_page(pfn)    (vmemmap + (pfn))                  //返回指针
#define __page_to_pfn(page)    (unsigned long)((page) - vmemmap)  //参数page是指针

//注:pfn = paddr >> PAGE_SHIFT


1.1 struct page 在内存管理中扮演的角色:

struct page 是物理内存页的元数据载体,其核心作用是为内核提供统一管理物理内存页的基础结构。每个物理页帧(Page Frame)对应一个 struct page 实例,通过它内核可以追踪页的状态、属性及关联信息。

struct page 记录页的物理属性(如是否空闲、脏页、锁定状态等); 系统所有 struct page 组成全局数组 mem_map, 通过页帧号(PFN)可直接索引到对应页的元数据。它在内存回收、LRU链表、反向映射、页缓存与文件映射。


二、成员介绍

//include/linux/mm_types.h
struct page {
    unsigned long flags;
    /* 此union结构是40B */
    union {

        /* Page cache and anonymous pages 40B */
        struct {
            struct list_head lru;
            struct address_space *mapping;
            pgoff_t index;
            unsigned long private;
        };

        /* page_pool used by netstack 8B */
        struct {
            dma_addr_t dma_addr;
        };

        /* slab, slob and slub 40B */
        struct {
            union {
                struct list_head slab_list;
                /* Partial pages */
                struct {    
                    struct page *next;
                    int pages;
                    int pobjects;
                };
            };
            struct kmem_cache *slab_cache;
            void *freelist;
            union {
                void *s_mem;
                unsigned long counters;
                struct {            
                    unsigned inuse:16;
                    unsigned objects:15;
                    unsigned frozen:1;
                };
            };
        };

        /* Tail pages of compound page 16B */
        struct {
            unsigned long compound_head;

            /* First tail page only */
            unsigned char compound_dtor;
            unsigned char compound_order;
            atomic_t compound_mapcount;
        };

        /* Second tail page of compound page 32B */
        struct {
            unsigned long _compound_pad_1;
            unsigned long _compound_pad_2;
            struct list_head deferred_list;
        };

        /* Page table pages 40B */
        struct {
            unsigned long _pt_pad_1;
            pgtable_t pmd_huge_pte;
            unsigned long _pt_pad_2;
            union {
                struct mm_struct *pt_mm;
                atomic_t pt_frag_refcount;
            };
            spinlock_t *ptl;
        };

        /* ZONE_DEVICE pages 16B */
        struct {
            struct dev_pagemap *pgmap;
            void *zone_device_data;
        };

        /* 16B */
        struct rcu_head rcu_head;
    };

    /* 4B */
    union {        
        atomic_t _mapcount;
        unsigned int page_type;
        unsigned int active;
        int units;
    };

    atomic_t _refcount;
    struct mem_cgroup *mem_cgroup; //CONFIG_MEMCG
    int _last_cpupid;
} __aligned(2 * sizeof(unsigned long));

__init_single_page() 中在初始化一个page结构之前,先将其清0.

成员介绍:

1. 固定存在的成员

flags:

set_page_zone()中表明bit63表示此page属于哪个zone,由 set_page_links() 可知,当前配置下,node_id 和 section_id 都没保存在这个flags中。

页标志位集合。用于记录这个页当前状态,例如是否脏页、是否在 LRU、是否是 slab 页、是否是复合页、是否是 swapcache、是否被锁等,后续根据它判断可以使用联合提中的哪些成员。flags 的高位还常编码 zone/node/section、最近 CPU/PID、KASAN tag 等元信息(是否在 flags 中取决于配置)。

字段解释总表(按页面类型):

---------------------------------------------------------------------------------
页面类型    flags标志       可用字段                        禁用字段
---------------------------------------------------------------------------------
文件/匿名页 PG_lru          lru, mapping, index, private    slab_cache, freelist
Slab对象    PG_slab         slab_list, slab_cache, freelist mapping, index
巨页        PG_compound     compound_head, compound_order   大部分字段
设备页      PG_hwpoison     pgmap, zone_device_data         常规字段
页表页      PG_table        pt_mm, ptl                      联合体大多数
交换缓存    PG_swapcache    swap_offset, mapping            常规LRU字段
---------------------------------------------------------------------------------

比如 PageLRU(PG_lru) 判断是否是文件页、PageSlab(PG_slab) 判断是否是Slab页、 PageCompound(PG_head) 判断是否是复合页。

需要先检查类型,再访问对应字段,比如:

if (PageSlab(page)) {
    // 访问 slab 字段
    cache = page->slab_cache;
} else if (PageLRU(page)) {
    // 访问 LRU 字段
    mapping = page->mapping;
}

判断是否是页表页,使用的是 PageTable(page->page_type)。

更多见《内存管理-5-page->flags

 

_refcount

引用计数。决定该页是否还能被释放。大于 0 表示页仍在使用,变为 0 表示可回收到伙伴系统。

 

联合体(mapcount / page_type / active / units)

_mapcount: 用户页表映射计数(从 -1 编码起)。 是映射计数,反映物理页被映射到多少个虚拟内存区域。初始值是 -1, 加上1以后才是真实的映射计数,建议使用内联函数 page_mapcount() 获取页的映射计数。
page_type: 非可映射页的类型编码。
active: SLAB 场景字段。
units: SLOB 场景字段。


2. 大联合体(5个long复用区)

根据“页用途”解释为不同子结构。

(1) 作为页缓存/匿名页时(最常见)

lru

挂到 LRU 或其他链表。

mapping

指向 address_space(文件页) 或相关映射对象。

利用指针总是4的整数倍这个特性,成员 mapping 的最低两位用来作为页映射标志,最低位 PAGE_MAPPING_ANON 表示匿名页。如果物理页是匿名页,page.mapping = (struct anon_vma 的地址 | PAGE_MAPPING ANON)。如果物理页是文件页 page.mapping 指向结构体 address_space。

index

在 mapping 中的页偏移(按 PAGE_SIZE)。

页面的pageblock的迁移类型的缓存值,在将页面放入 pcplist 时使用。在大多数情况下,用于在从 pcplist 释放时避免页面块迁移类型查找. 使用见 get_pcppage_migratetype();

它是在映射里面的偏移,单位是页。如果是匿名映射,那么 index 是物理页对应的虚拟页在虚拟内存区域中的页偏移,如果是文件映射那么 index 是物理页存储的数据在文件中的页偏移。

private

私有字段,典型用途:
a. PagePrivate 时挂 buffer_heads
b. PageSwapCache 时存 swap entry offset
c. PageBuddy 时存伙伴阶数信息


(2) 作为网络 page_pool 页

dma_addr:网卡/网络栈复用的 DMA 地址信息.


(3) 作为 slab/slub/sblob 页

slab_list 或 partial 链接信息.
slab_cache: 所属 kmem_cache.
freelist: 空闲对象链表头.
counters inuse/objects/frozen: 对象计数与冻结状态.


(4) 作为复合页(compound page)尾页首个 tail 页

compound_head(低 bit 标记 tail)
compound_dtor
compound_order
compound_mapcount


(5) 作为复合页(compound page)尾页第二个 tail 页

deferred_list(例如延迟拆分相关链表)


(6) 作为页表页时

pmd_huge_pte: THP 相关
pt_mmpt_frag_refcount(架构相关)
ptl(页表锁,可能是指针或内嵌锁,取决于是否 split ptlock)


(7) 作为 ZONE_DEVICE 页

pgmap: 设备页映射描述
zone_device_data: 设备私有数据,并且会复用 mapping/index/private 保存迁移或 DAX 相关上下文。
rcu_head: 可用于 RCU 延迟释放路径。


3. 可配置成员

mem_cgroup(CONFIG_MEMCG): 用于记录该页归属的内存 cgroup。

virtual(WANT_PAGE_VIRTUAL: 主要用于 highmem 架构缓存内核虚拟地址。arm64 通常不用这个字段。

_last_cpupid(LAST_CPUPID_NOT_IN_PAGE_FLAGS): 用于NUMA balancing 的最近 CPU/PID 追踪。

 

三、辅助函数

1. 获取page对应的zone、node、section

//include/linux/mm.h

static inline struct zone *page_zone(const struct page *page)
{
    return &NODE_DATA(page_to_nid(page))->node_zones[page_zonenum(page)];
}
static inline pg_data_t *page_pgdat(const struct page *page)
{
    return NODE_DATA(page_to_nid(page));
}
static inline unsigned long page_to_section(const struct page *page)
{
    return (page->flags >> SECTIONS_PGSHIFT) & SECTIONS_MASK; //64 0
}


四、其它相关

1. __mm_zero_struct_page() 中有对 struct page 的大小进行检查,必须是 56, 64, 72, 80 这几个大小,否则在编译时报错。

2. page有一组debug trace

trace_page_ref_{set/mod/mod_and_test/mod_and_return/mod_unless/freeze/unfreeze}, 使用见 debug_page_ref.c

 

posted on 2026-04-02 10:41  Hello-World3  阅读(19)  评论(0)    收藏  举报

导航