内存管理-5-物理内存管理-1-相关结构体

基于msm-5.4

概述:

物理内存组织关系如下图。非服务器的设备,一般只有一个内存node节点,使用 pglst_data 结构描述;一个内存节点下有较多分区,使用 zone 结构描述;每个分区中有不同大小的空闲内存块,通过 free_area 结构进行描述;物理内存管理的最小单位使用 page 结构进行描述。

 物理内存管理主要分两部分,一是权限管理,另外就是将其分成大小相同的内存块,叫做物理页(Frame Page),使用 struct page 表示。分区 struct zone 表示。

 

一、struct pglist_data

1. 简介

表示一个node里面的内存资源。在 NUMA 机器上,每个 NUMA 节点都会有一个 pg_data_t 来描述其内存布局。在 UMA 机器上,只有一个 pglist_data 来描述整个内存。 内存统计信息和页面替换数据结构按区域(zone)进行维护。

初始化函数:zone_sizes_init() 其调用路径:

start_kernel //main.c
    setup_arch //setup.c
        bootmem_init //init.c
            zone_sizes_init
            memblock_dump_all //dump所有的memory和reserved内存区间

2. 成员介绍

复制代码
//include/linux/mmzone.h
typedef struct pglist_data {
    struct zone node_zones[MAX_NR_ZONES];
    struct zonelist node_zonelists[MAX_ZONELISTS];
    int nr_zones;
    spinlock_t node_size_lock;
    unsigned long node_start_pfn;
    unsigned long node_present_pages;
    unsigned long node_spanned_pages;
    int node_id;
    wait_queue_head_t kswapd_wait;
    wait_queue_head_t pfmemalloc_wait;
    struct task_struct *kswapd;
    struct task_struct *mkswapd[MAX_KSWAPD_THREADS]; //16
    int kswapd_order;
    enum zone_type kswapd_classzone_idx;
    int kswapd_failures;

    int kcompactd_max_order;
    enum zone_type kcompactd_classzone_idx;
    wait_queue_head_t kcompactd_wait;
    struct task_struct *kcompactd;

    unsigned long        totalreserve_pages;
    ZONE_PADDING(_pad1_) //cache line aligned,size=0
    spinlock_t        lru_lock;
    struct deferred_split deferred_split_queue;
    struct lruvec        lruvec;
    unsigned long        flags;

    ZONE_PADDING(_pad2_) //cache line aligned,size=0
    struct per_cpu_nodestat __percpu *per_cpu_nodestats;
    atomic_long_t        vm_stat[NR_VM_NODE_STAT_ITEMS];
} pg_data_t;
复制代码

node_zones[]: 此node的zone描述,在 calculate_node_totalpages() 中初始化。

node_zonelists[]: 里面只有一个元素,在 build_zonelists() 中进行初始化。

node_start_pfn: 物理内存的最小页帧号, 在 free_area_init_node() 中赋值。

node_present_pages: node物理页的总个数,不包括hole, 在 calculate_node_totalpages() 中赋值。

node_spanned_pages: node跨越物理页面范围的总个数,包括空洞,在 calculate_node_totalpages() 中赋值。

node_id: 嵌入式中只有一个node, 这个node_id是0,在 free_area_init_node() 中赋值。

mkswapd[]: 显示最大可以有16个kswapd线程,被 mem_hotplug_begin/end() 保护。

kswapd_failures: 'reclaimed == 0'运行次数。

totalreserve_pages: 每个节点保留的页面数,不可用于用户空间分配。

_pad1_: 页面回收使用的写密集型字段

lruvec: 页面回收扫描器经常访问的字段

per_cpu_nodestats: 每node的vmstats,在 free_area_init_core() 中指向全局 boot_nodestats, 定义在 page_alloc.c 中, 是个数组,有 NR_VM_NODE_STAT_ITEMS 个元素,每个CPU都有这么多个元素

vm_stat: 统计信息

 

二、struct zone

1. 简介

描述一个内存区。内存可以按照不同的用途与类型划分为不同的区域,zone结构表示内存区域,系统存在的zone见 enum zone_type 成员。

ZONE_DMA: 表示这片区域用于DMA,一般是给x86使用的,64位上将其取消掉了,arm系统上也没有使用这个zone,因为划分了这个区域若不使能的话它就会一直空着,造成内存浪费。arm使用另外一机制,叫做cma,它能充分利用不浪费一片内存。

使用哪些zone区域是可以配置的。

水位是每内存区的。

2. 成员介绍

复制代码
//include/linux/mmzone.h
struct zone {
    /* Read-mostly fields */
    unsigned long _watermark[NR_WMARK];
    unsigned long watermark_boost;

    unsigned long nr_reserved_highatomic;
    long lowmem_reserve[MAX_NR_ZONES]; //2
    struct pglist_data    *zone_pgdat;
    struct per_cpu_pageset __percpu *pageset;
    bool            cma_alloc; //CONFIG_CMA
    unsigned long        *pageblock_flags; //CONFIG_SPARSEMEM
    unsigned long        zone_start_pfn;

    atomic_long_t        managed_pages;
    unsigned long        spanned_pages;
    unsigned long        present_pages;

    const char        *name;
    unsigned long        nr_isolate_pageblock; //CONFIG_MEMORY_ISOLATION
    seqlock_t        span_seqlock; //CONFIG_MEMORY_HOTPLUG
    int initialized;
    ZONE_PADDING(_pad1_) //cache aligned, size=0, Write-intensive
    struct free_area    free_area[MAX_ORDER];
    unsigned long        flags;
    spinlock_t        lock;
    ZONE_PADDING(_pad2_) //cache aligned, size=0, Write-intensive
    unsigned long percpu_drift_mark;
#if defined CONFIG_COMPACTION || defined CONFIG_CMA //both-enabled
    unsigned long        compact_cached_free_pfn;
    unsigned long        compact_cached_migrate_pfn[2];
    unsigned long        compact_init_migrate_pfn;
    unsigned long        compact_init_free_pfn;
#endif
#ifdef CONFIG_COMPACTION //enabled
    unsigned int        compact_considered;
    unsigned int        compact_defer_shift;
    int                    compact_order_failed;
#endif
#if defined CONFIG_COMPACTION || defined CONFIG_CMA //both-enabled
    bool            compact_blockskip_flush;
#endif
    bool            contiguous;
    ZONE_PADDING(_pad3_) //cache aligned, size=0, Write-intensive
    /* Zone statistics */
    atomic_long_t        vm_stat[NR_VM_ZONE_STAT_ITEMS];
    atomic_long_t        vm_numa_stat[NR_VM_NUMA_STAT_ITEMS];
} ____cacheline_internodealigned_in_smp;
复制代码

_watermark[]: 当前zone的水位,使用 min/low/high_wmark_pages(zone) 宏访问,此宏会考虑boost水位。超出水位后会触发内存回收等机制。

zone_pgdat: zone_init_internals() 中进行初始化,指向 NODE_DATA(nid), 即 &contig_page_data

pageset: zone_pcp_init() 中进行初始化,指向全局 boot_pageset 变量,定义在page_alloc.c中。

lowmem_reserve[]: 我们不知道要分配的内存是否可释放或/以及最终是否会被释放,因此为了避免完全浪费几GB的RAM,我们必须保留一些较低区域内存(否则,尽管较高区域有大量可释放的 RAM,我们仍有在较低区域运行 OOM 的风险)。如果 sysctl_lowmem_reserve_ratio sysctl 发生变化,则在运行时重新计算此数组。它相当于一个备胎,分配内存的时候可以指定参数来决定去哪个zone里面去分,若目标zone中已经没有内存了,它会去其它zone里面去找,若其它zone中也没有内存了,就会启用 lowmem_reserve[] 中预留的内存。

pageblock_flags: pageblock_nr_pages 块的标志,使用的是ifndef,默认没有这个参数,实际使用的是 mem_section->usage中 的这个参数,请参阅 pageblock-flags.h,在 SPARSEMEM 中,此映射存储在 struct mem_section 中。

zone_start_pfn:  此zone管理的物理内存的起始页帧号,在 init_currently_empty_zone() 中赋值,ZONE_NORMAL的是物理内存的起始页帧号,ZONE_MOVEABLE的是0,zone_start_pfn == zone_start_paddr >> PAGE_SHIFT

spanned_pa​​ges: 是此zone所跨越的总页面数,包括空洞(holes),在 calculate_node_totalpages() 中赋值,其计算方式为:spanned_pa​​ges = zone_end_pfn - zone_start_pfn;

present_pages: 是此zone中实际存在的物理页面数量,在 calculate_node_totalpages() 中赋值,其计算方式为:present_pages = spanned_pa​​ges - 空洞页面

managed_pa​​ges: 此zone实际管理的页面数,zone_init_internals() 中赋值为 present_pages - memmap使用的页面。三者的关系是: spanned_pages > present_pages > managed_pages.

name: 此zone的名字,zone_init_internals() 中让其指向全局 zone_names[zone_idx].

nr_isolate_pageblock: 孤立页块的数量。它用于解决由于检索页块的迁移类型不当而导致的空闲页计数不正确的问题。受 zone->lock 保护。

initialized: zone初始化后在 init_currently_empty_zone() 中将其设置为1。

free_area[]: 不同大小的可用区域,MAX_ORDER定义为11,即0-10,一次能从buddy系统中分配出来的最大连续内存块的大小是4MB. 下标 i 中存放的是页数为2^i个页面的分组。每个数组元素里面的每一个迁移类型都有一个链表。

flags: zone的标志位。

lock: 主要保护free_area[]

percpu_drift_mark: 当可用页面低于此值时,读取可用页面数量时会采取额外步骤,以避免每个 CPU 计数器漂移导致水位被突破。

compact_cached_free_pfn: 指示pfn无压缩扫描仪器应从何处开始。

compact_cached_migrate_pfn[]: pfn异步和同步压缩迁移扫描器应开始的位置。

compact_defer_shift/compact_considered: 如果压缩失败,则在重试之前会跳过 1<<compact_defer_shift 进行压缩。自上次失败以来尝试的次数会通过 compact_considered 进行跟踪。

compact_blockskip_flush: 当应清除 PG_migrate_skip 位时,设置为 true

vm_stat/vm_numa_stat: zone的统计信息。

free_area[]: 当前zone空闲可用的物理页帧。free_area.free_list: 随着物理页的申请与释放,page在这些链表上动态移动。

 

三、struct free_area

1. 简介

描述固定大小的空余内存块,zone中不同大小的空闲内存块使用数组进行描述。

2. 成员介绍

//include/linux/mmzone.h
struct free_area {
    struct list_head    free_list[MIGRATE_TYPES];
    unsigned long        nr_free;
};

free_list[]: 一个链表数组,同一个zone中的同一大小同一迁移属性的内存块挂在同一个链表上。

nr_free: 链表中元素的个数。

 

四、struct page

1. 简介

物理页帧,系统中的每个物理页面都有一个与之关联的 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

 

2. 成员介绍

复制代码
//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.

成员介绍:

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

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

 

3. 辅助函数

(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
}
复制代码

 

4. 其它相关

(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   Hello-World3  阅读(68)  评论(0编辑  收藏  举报

相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!

导航

< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5
点击右上角即可分享
微信分享提示