内存管理-10-内存log-2-分配失败日志

基于 kernel-6.1

一、简介

1. 概述

在内核中,当物理内存分配失败的时候,会调用 warn_alloc() 进行打印,默认每 10 秒打印一条,也可以使用 __GFP_NOWARN 标志来关闭这个打印,部分轻量级分配通常会带这个标志。

2. warn_alloc 调用路径

vmemmap_alloc_block //mm/sparse-vmemmap.c
    warn_alloc(gfp_mask & ~__GFP_NOWARN, NULL, "vmemmap alloc failure: order:%u", order);

__vmalloc_area_node //mm/vmalloc.c
    warn_alloc(gfp_mask, NULL, "vmalloc error: size %lu, failed to allocated page array size %lu",
        nr_small_pages * PAGE_SIZE, array_size);

__alloc_pages_slowpath //mm/page_alloc.c 
    warn_alloc(gfp_mask, ac->nodemask, "page allocation failure: order:%u", order);

可以根据失败打印的字符串,确认是哪个路径下的分配失败。

3. __show_mem 调用路径

打印 "Mem-Info" 的 __show_mem() 除了 warn_alloc() 会调用外,还有其它调用路径:

warn_alloc //page_alloc.c
    warn_alloc_show_mem //page_alloc.c
        __show_mem //lib/show_mem.c

oom_kill_process //oom_kill.c
check_panic_on_oom //oom_kill.c
out_of_memory //oom_kill.c
    dump_header //oom_kill.c
        __show_mem //lib/show_mem.c

        system_heap_do_vmap //dma-buf/mtk_system_heap.c
    panic //kernel/panic.c
        panic_print_sys_info //kernel/panic.c
        show_mem_info //fork.c MTK特有,需使能 CONFIG_MTK_DEBUG_FORK_OOM
        panic_show_mem //initramfs.c
echo m > /proc/sysrq-trigger //sysrq.c 调试
    op sysrq_showmem_op.handler //sysrq.c
        sysrq_handle_showmem //sysrq.c (0, NULL)
        fn_show_mem //tty/vt/keyboard.c
            show_mem //linux/mm.h
                __show_mem //lib/show_mem.c


二、代码实现

1. warn_alloc()

void warn_alloc(gfp_t gfp_mask, nodemask_t *nodemask, const char *fmt, ...) //page_alloc.c
{
    struct va_format vaf;
    va_list args;
    /* 限流,10秒最多打印1条, 这个“指标”不要被"非问题"轻易占用 */
    static DEFINE_RATELIMIT_STATE(nopage_rs, 10*HZ, 1);

    /*
     * 满足下列任一条件时静默退出,不打印告警:
     * (1) 调用方分配掩码中设置了 __GFP_NOWARN;
     * (2) 超过限速阈值(10s/1次);
     * (3) 分配标志包含 __GFP_DMA 但系统没有 managed DMA zone:此时 DMA 分配失败是配置性原因,无诊断价值.
     * 默认不使能 CONFIG_ZONE_DMA has_managed_dma()恒返回false, 即带了 __GFP_DMA 就不会打印。
     */
    if ((gfp_mask & __GFP_NOWARN) || !__ratelimit(&nopage_rs) || ((gfp_mask & __GFP_DMA) && !has_managed_dma()))
        return;

    va_start(args, fmt);
    vaf.fmt = fmt;
    vaf.va = &args;
    /* %pGg 格式的打印,可以将分配掩码转换为字符串 */
    pr_warn("%s: %pV, mode:%#x(%pGg), nodemask=%*pbl",
            current->comm, &vaf, gfp_mask, &gfp_mask, nodemask_pr_args(nodemask));
    va_end(args);

    /* 打印mem cpuset的信息, 有助于判断告警是否由 cpuset 策略限制导致而非真正内存耗尽,嵌入式可忽略 */
    cpuset_print_current_mems_allowed();
    pr_cont("\n");
    /* 打印调用栈 */
    dump_stack();
    /* 主要内存信息的打印交给这个函数 */
    warn_alloc_show_mem(gfp_mask, nodemask);
}


2.1 cpuset_print_current_mems_allowed()

默认使能 CONFIG_CPUSETS 这个函数才不为空。

void cpuset_print_current_mems_allowed(void) //cpuset.c
{
    struct cgroup *cgrp;

    rcu_read_lock();

    cgrp = task_cs(current)->css.cgroup;
    /* NUMA中的多个内存节点是通过cpuset控制的,只不过嵌入式设备不用,这里也给你打印出来了 */
    pr_cont(",cpuset=");
    pr_cont_cgroup_name(cgrp);
    /* 嵌入式只有一个node节点,恒等于0 */
    pr_cont(",mems_allowed=%*pbl", nodemask_pr_args(&current->mems_allowed));

    rcu_read_unlock();
}


2.2 warn_alloc_show_mem()

/*
filter 默认值的含义:SHOW_MEM_FILTER_NODES 置位, 表示只显示当前进程 cpuset/mempolicy 许可节
点的统计,减少 NUMA 多节点噪音。

两处清零 filter 的逻辑,对应两组"节点限制不适用"的场景:
-----------------------------------------------------------------------------
条件                     原因
-----------------------------------------------------------------------------
tsk_is_oom_victim        OOM 受害者即将退出,已获准越节点访问保留内存。需要!__GFP_NOMEMALLOC
PF_MEMALLOC              回收上下文(如 kswapd),水位和节点限制均已豁免, 需要!__GFP_NOMEMALLOC
PF_EXITING               退出路径,同样享有豁免, 需要!__GFP_NOMEMALLOC
!in_task()               中断/softirq回调中,无 cpuset 上下文
!__GFP_DIRECT_RECLAIM    NOWAIT/ATOMIC 路径,不受 cpuset 约束
-----------------------------------------------------------------------------

warn_alloc_show_mem: (gfp_mask, NULL)
*/
static void warn_alloc_show_mem(gfp_t gfp_mask, nodemask_t *nodemask)
{
    /* 默认启用节点过滤:只显示当前任务 cpuset/mempolicy 允许节点的统计。嵌入式只有一个节点,不用管 */
    unsigned int filter = SHOW_MEM_FILTER_NODES; //0x0001u

    /*
     * 本文档记录了特定上下文中分配操作所享有的例外情况,即允许在当前进程(current)的允许节点集之外进行分配。
     *
     * 规则一:某些上下文允许在 cpuset 限制之外分配内存,此时节点过滤会误导诊断,应当关闭,显示全局内存状态。
     *
     * 具体豁免条件(同时满足"没有设置 __GFP_NOMEMALLOC"才判断):
     *    1. tsk_is_oom_victim(current):当前任务已被 OOM killer 选中,即将退出,内核允许它访问保留内存,
     *    不受 cpuset 节点约束。
     *    2. PF_MEMALLOC:当前任务处于内存回收上下文(如 kswapd),允许越过正常水位使用保留内存,节点限制同样不适用。
     *    3. PF_EXITING:当前任务正在退出,也给予类似豁免。
     *
     * 注意: __GFP_NOMEMALLOC 表示调用者明确禁止使用保留内存,此时不应给予上述豁免,保持默认的节点过滤。
     */
    if (!(gfp_mask & __GFP_NOMEMALLOC))
        if (tsk_is_oom_victim(current) || (current->flags & (PF_MEMALLOC | PF_EXITING)))
            filter &= ~SHOW_MEM_FILTER_NODES; //0x0001u
    /*
     * 规则二:以下两种情况下节点过滤同样无意义,应关闭:
     * 1. !in_task():当前不在进程上下文,在中断/softirq回调中,没有有效的 cpuset,节点限制的概念不适用。
     * 2. !(gfp_mask & __GFP_DIRECT_RECLAIM):分配方不允许直接回收(如 GFP_ATOMIC/GFP_NOWAIT),通常也不受
     *    cpuset 节点约束,打印全局信息有助于排查是否所有节点都已耗尽。
     *
     * in_task(): 不在执行NMI、硬中断、软中断回调,就认为是进程上下文,包括关硬中断关软中断也认为是在进程上下文 
     */
    if (!in_task() || !(gfp_mask & __GFP_DIRECT_RECLAIM))
        filter &= ~SHOW_MEM_FILTER_NODES;

    /*
     * gfp_zone(gfp_mask) 让后续只展示与本次分配相关的 zone 层次(如只到 Normal,不到 HighMem)
     * gfp中的zone分配标识与zone的对应关系见 GFP_ZONE_TABLE。
     * 对于 mode:0x8000c0a(GFP_NOIO|__GFP_HIGHMEM|__GFP_MOVABLE|__GFP_CMA) gfp_zone() 返回zone=ZONE_MOVABLE
     */
    __show_mem(filter, nodemask, gfp_zone(gfp_mask));
}


2.2.1 __show_mem()

/*
 * 参数:
 * nodemask: 嵌入式上通常是NULL
 * max_zone_idx: 打印 free area 时允许展示到的最高 zone 下标.
 *
 * 输出分两部分:
 * 1) __show_free_areas():逐 zone/逐 order 的可分配形态信息;
 * 2) 本函数汇总:RAM 总页数、高端/MovableOnly 页、reserved 页、CMA/hwpoison 信息。
 *
 * warn_alloc_show_mem: (filter=SHOW_MEM_FILTER_NODES, nodemask=NULL, max_zone_idx=ZONE_MOVABLE)
 */
void __show_mem(unsigned int filter, nodemask_t *nodemask, int max_zone_idx)
{
    pg_data_t *pgdat;
    /*
     * total:    各在线节点 populated zone 的 present_pages 总和
     * reserved: present_pages - managed_pages 的总和,近似“不可由常规伙伴系统分配”的页
     * highmem:  高端内存或 MovableOnly 相关页统计(由 is_highmem_idx 判定)
     */
    unsigned long total = 0, reserved = 0, highmem = 0;

    printk("Mem-Info:\n");
    /* 主要信息打印函数,各 zone 的水位与空闲页分布、 各 order/migratetype 空闲块 等信息 */
    __show_free_areas(filter, nodemask, max_zone_idx);

    /*
     * 再做跨节点汇总,给出全局总量视角。仅统计在线节点,并跳过未 populated 的空 zone。
     * 嵌入式设备只有一个节点,只执行一次。
     */
    for_each_online_pgdat(pgdat) {
        int zoneid;
        /* AOS/SOS中默认配置了4个zone, 分别为 DMA32, NORMAL, MOVEABLE, DEVICE, 但只有NORMAL中有物理内存 */
        for (zoneid = 0; zoneid < MAX_NR_ZONES; zoneid++) {
            struct zone *zone = &pgdat->node_zones[zoneid];
            /* 排除 zone->present_pages 为 0 的 zone */
            if (!populated_zone(zone))
                continue;

            /* 各在线节点 populated zone 的 present_pages 总和 */
            total += zone->present_pages;
            /* present_pages - managed_pages 的总和,近似“不可由常规伙伴系统分配”的页 */
            reserved += zone->present_pages - zone_managed_pages(zone);
            /* 默认不使能 CONFIG_HIGHMEM(主要为32位机器而生的), 恒返回0 */
            if (is_highmem_idx(zoneid))
                highmem += zone->present_pages;
        }
    }

    /*
     * 实测会比 meminfo 中的 "MemTotal" 字段的值大一些。这里是 present_pages, 而后者是 managed_pages, 即:
     * "pages RAM" = "MemTotal" + "pages reserved" 
     */
    printk("%lu pages RAM\n", total);
    /* 默认不使能 CONFIG_HIGHMEM,固定为0 */
    printk("%lu pages HighMem/MovableOnly\n", highmem);
    printk("%lu pages reserved\n", reserved);
#ifdef CONFIG_CMA
    /* 系统中配置的所有CMA节点的总大小 */
    printk("%lu pages cma reserved\n", totalcma_pages);
#endif
    /* 这个是硬件内存错误恢复机制(ECC/RAS 场景)。出现坏页时,可把页打上 HWPoison(PG_hwpoison),隔离并避免再次分配使用 */
#ifdef CONFIG_MEMORY_FAILURE
    printk("%lu pages hwpoisoned\n", atomic_long_read(&num_poisoned_pages));
#endif
    /* 这里还给了一个hook,支持用户定制dump更多信息 */
    trace_android_vh_show_mem(filter, nodemask);
}


2.2.1.1 __show_free_areas()

#define K(x) ((x) << (PAGE_SHIFT-10))

/*
 * filter: 前面是否赋值 SHOW_MEM_FILTER_NODES
 * 例如: (filter=SHOW_MEM_FILTER_NODES, nodemask=NULL, max_zone_idx=ZONE_MOVABLE) 
 */
void __show_free_areas(unsigned int filter, nodemask_t *nodemask, int max_zone_idx)
{
    unsigned long free_pcp = 0;
    int cpu, nid;
    struct zone *zone;
    pg_data_t *pgdat;

    /* 遍历所有 zone->present_pages 不为0的zone */
    for_each_populated_zone(zone) {
        /* 最高只遍历到 max_zone_idx 位置, max_zone_idx 与内存分配掩码的对应关系见 GFP_ZONE_TABLE */
        if (zone_idx(zone) > max_zone_idx)
            continue;
        /* 若返回真就跳过此zone, 嵌入式恒返回false,可忽略(cpuset中也只可以echo 0 > mems) */
        if (show_mem_node_skip(filter, zone_to_nid(zone), nodemask))
            continue;

        /* 计算所有zone在所有cpu上的pcp页个数和 */
        for_each_online_cpu(cpu)
            free_pcp += per_cpu_ptr(zone->per_cpu_pageset, cpu)->count;
    }

    /*
     * [1] 这里打印的单位是 page. 这行打印的是所有node的所有zone的统计之,但是嵌入式只有一个node,
     * 所以和下面"Node 0"的数据是一样的。TODO: 再写一个BK解释每个小项的统计  
     */
    printk("active_anon:%lu inactive_anon:%lu isolated_anon:%lu\n"
        " active_file:%lu inactive_file:%lu isolated_file:%lu\n"
        " unevictable:%lu dirty:%lu writeback:%lu\n"
        " slab_reclaimable:%lu slab_unreclaimable:%lu\n"
        " mapped:%lu shmem:%lu pagetables:%lu\n"
        " sec_pagetables:%lu bounce:%lu\n"
        " kernel_misc_reclaimable:%lu\n"
        " free:%lu free_pcp:%lu free_cma:%lu\n",
        global_node_page_state(NR_ACTIVE_ANON),
        global_node_page_state(NR_INACTIVE_ANON),
        global_node_page_state(NR_ISOLATED_ANON),
        global_node_page_state(NR_ACTIVE_FILE),
        global_node_page_state(NR_INACTIVE_FILE),
        global_node_page_state(NR_ISOLATED_FILE),
        global_node_page_state(NR_UNEVICTABLE),
        global_node_page_state(NR_FILE_DIRTY),
        global_node_page_state(NR_WRITEBACK),
        global_node_page_state_pages(NR_SLAB_RECLAIMABLE_B),
        global_node_page_state_pages(NR_SLAB_UNRECLAIMABLE_B),
        global_node_page_state(NR_FILE_MAPPED),
        global_node_page_state(NR_SHMEM),
        global_node_page_state(NR_PAGETABLE),
        global_node_page_state(NR_SECONDARY_PAGETABLE),
        global_zone_page_state(NR_BOUNCE),
        global_node_page_state(NR_KERNEL_MISC_RECLAIMABLE),
        global_zone_page_state(NR_FREE_PAGES),
        free_pcp,
        global_zone_page_state(NR_FREE_CMA_PAGES));

    /* [2] 这个是逐个打印每个node的信息,嵌入式只有一个node */
    for_each_online_pgdat(pgdat) {
        /* 跳过无关的node,嵌入式只有一个node,恒不成立,忽略 */
        if (show_mem_node_skip(filter, pgdat->node_id, nodemask))
            continue;
        /* 此node中从 0--max_zone_idx 只要有一个zone存在物理页(即 managed_pages!=0) 则返回真 */
        if (!node_has_managed_zones(pgdat, max_zone_idx))
            continue;

        printk("Node %d"
            " active_anon:%lukB"
            " inactive_anon:%lukB"
            " active_file:%lukB"
            " inactive_file:%lukB"
            " unevictable:%lukB"
            " isolated(anon):%lukB"
            " isolated(file):%lukB"
            " mapped:%lukB"
            " dirty:%lukB"
            " writeback:%lukB"
            " shmem:%lukB"
            /* 比上面多出下面这7个成员 */
#ifdef CONFIG_TRANSPARENT_HUGEPAGE
            " shmem_thp: %lukB"
            " shmem_pmdmapped: %lukB"
            " anon_thp: %lukB"
#endif
            " writeback_tmp:%lukB"
            " kernel_stack:%lukB"
#ifdef CONFIG_SHADOW_CALL_STACK
            " shadow_call_stack:%lukB"
#endif
            " pagetables:%lukB"
            " sec_pagetables:%lukB"
            " all_unreclaimable? %s"
            "\n",
            pgdat->node_id,
            K(node_page_state(pgdat, NR_ACTIVE_ANON)),
            K(node_page_state(pgdat, NR_INACTIVE_ANON)),
            K(node_page_state(pgdat, NR_ACTIVE_FILE)),
            K(node_page_state(pgdat, NR_INACTIVE_FILE)),
            K(node_page_state(pgdat, NR_UNEVICTABLE)),
            K(node_page_state(pgdat, NR_ISOLATED_ANON)),
            K(node_page_state(pgdat, NR_ISOLATED_FILE)),
            K(node_page_state(pgdat, NR_FILE_MAPPED)),
            K(node_page_state(pgdat, NR_FILE_DIRTY)),
            K(node_page_state(pgdat, NR_WRITEBACK)),
            K(node_page_state(pgdat, NR_SHMEM)),
#ifdef CONFIG_TRANSPARENT_HUGEPAGE
            K(node_page_state(pgdat, NR_SHMEM_THPS)),
            K(node_page_state(pgdat, NR_SHMEM_PMDMAPPED)),
            K(node_page_state(pgdat, NR_ANON_THPS)),
#endif
            K(node_page_state(pgdat, NR_WRITEBACK_TEMP)),
            node_page_state(pgdat, NR_KERNEL_STACK_KB),
#ifdef CONFIG_SHADOW_CALL_STACK
            node_page_state(pgdat, NR_KERNEL_SCS_KB),
#endif
            K(node_page_state(pgdat, NR_PAGETABLE)),
            K(node_page_state(pgdat, NR_SECONDARY_PAGETABLE)),
            pgdat->kswapd_failures >= MAX_RECLAIM_RETRIES ? "yes" : "no");
    }


    /* [3] 编译所有node的所有存在物理页的zone,即再以 zone 为单位打印一次 */
    for_each_populated_zone(zone) {
        int i;
        /* 跳过 max_zone_idx 不允许的zone, 即不打印与此次分配无关的zone的信息 */
        if (zone_idx(zone) > max_zone_idx)
            continue;
        /* 跳过无关的node,嵌入式只有一个node,恒不成立,忽略 */
        if (show_mem_node_skip(filter, zone_to_nid(zone), nodemask))
            continue;

        free_pcp = 0;
        /* 这里是此zone所有pcp页之和 */
        for_each_online_cpu(cpu)
            free_pcp += per_cpu_ptr(zone->per_cpu_pageset, cpu)->count;

        /* 没有使能 CONFIG_NUMA 是空实现 */
        show_node(zone);
        printk(KERN_CONT
            "%s"
            " free:%lukB"
            " boost:%lukB"
            " min:%lukB"
            " low:%lukB"
            " high:%lukB"
            " reserved_highatomic:%luKB"
            " active_anon:%lukB"
            " inactive_anon:%lukB"
            " active_file:%lukB"
            " inactive_file:%lukB"
            " unevictable:%lukB"
            " writepending:%lukB"
            " present:%lukB"
            " managed:%lukB"
            " mlocked:%lukB"
            " bounce:%lukB"
            " free_pcp:%lukB"
            " local_pcp:%ukB"
            " free_cma:%lukB"
            "\n",
            zone->name,
            K(zone_page_state(zone, NR_FREE_PAGES)),
            K(zone->watermark_boost),
            K(min_wmark_pages(zone)), //已经包含了boost的部分
            K(low_wmark_pages(zone)),
            K(high_wmark_pages(zone)),
            K(zone->nr_reserved_highatomic),
            K(zone_page_state(zone, NR_ZONE_ACTIVE_ANON)),
            K(zone_page_state(zone, NR_ZONE_INACTIVE_ANON)),
            K(zone_page_state(zone, NR_ZONE_ACTIVE_FILE)),
            K(zone_page_state(zone, NR_ZONE_INACTIVE_FILE)),
            K(zone_page_state(zone, NR_ZONE_UNEVICTABLE)),
            K(zone_page_state(zone, NR_ZONE_WRITE_PENDING)),
            K(zone->present_pages),
            K(zone_managed_pages(zone)),
            K(zone_page_state(zone, NR_MLOCK)),
            K(zone_page_state(zone, NR_BOUNCE)),
            K(free_pcp),
            K(this_cpu_read(zone->per_cpu_pageset->count)),
            K(zone_page_state(zone, NR_FREE_CMA_PAGES)));
        printk("lowmem_reserve[]:");
        for (i = 0; i < MAX_NR_ZONES; i++)
            printk(KERN_CONT " %ld", zone->lowmem_reserve[i]);
        printk(KERN_CONT "\n");
    }

    /* 然后打印此次分配相关zone的空闲链表信息 */
    for_each_populated_zone(zone) {
        unsigned int order;
        unsigned long nr[MAX_ORDER], flags, total = 0;
        unsigned char types[MAX_ORDER];
        /* 也是跳过无关zone */
        if (zone_idx(zone) > max_zone_idx)
            continue;
        /* 过滤非必要zone节点 */
        if (show_mem_node_skip(filter, zone_to_nid(zone), nodemask))
            continue;
        /* 没有使能 CONFIG_NUMA 是空实现 */
        show_node(zone);
        printk(KERN_CONT "%s: ", zone->name);
        /* 持 zone->lock 后遍历此zone的空闲链表 */
        spin_lock_irqsave(&zone->lock, flags);
        for (order = 0; order < MAX_ORDER; order++) { //10
            struct free_area *area = &zone->free_area[order];
            int type;
            /* 记录当前页块空闲页块个数和空闲页个数 */
            nr[order] = area->nr_free;
            total += nr[order] << order;
            /* 计算此order中有哪些迁移类型的页块, 只要存在一个就标记上对应的标志(pcp的也记录在free_area中了) */
            types[order] = 0;
            for (type = 0; type < MIGRATE_TYPES; type++) {
                if (!free_area_empty(area, type))
                    types[order] |= 1 << type;
            }
        }
        spin_unlock_irqrestore(&zone->lock, flags);
        /* 打印空闲页块个数,和每个空闲页块的大小,单位kB */
        for (order = 0; order < MAX_ORDER; order++) { //10
            printk(KERN_CONT "%lu*%lukB ", nr[order], K(1UL) << order);
            /* 打印此order中空闲页块中存在的迁移类型 */
            if (nr[order])
                show_migration_types(types[order]);
        }
        /* 打印此zone空闲链表中页块的总大小,单位kB */
        printk(KERN_CONT "= %lukB\n", K(total));
    }

    for_each_online_node(nid) {
        /* 过滤掉非必要的node节点 */
        if (show_mem_node_skip(filter, nid, nodemask))
            continue;
        /* 默认不使能 CONFIG_HUGETLB_PAGE, 是空实现 */
        hugetlb_show_meminfo_node(nid);
    }

    /*
     * 打印页缓存页的个数。一次打印中:
     * "active_file:614883" + "inactive_file:142571" < "771334 total pagecache pages"
     * 前两个只是LRU中的,后一个是所有的,差别主要是后者包含 shmem 和 swap cache 页。
     */
    printk("%ld total pagecache pages\n", global_node_page_state(NR_FILE_PAGES));

    /* 打印swap的大小和可用大小 */
    show_swap_cache_info();
}


2.2.1.1.1 show_mem_node_skip()

/*
 * 根据是否将 SHOW_MEM_FILTER_NODES 传递给 show_free_areas(),确定是否应显示该节点。
 * 前面 warn_alloc_show_mem() 是否赋值 flags 或包含 SHOW_MEM_FILTER_NODES, 嵌入式 nodemask 固定传 NULL
 */
static bool show_mem_node_skip(unsigned int flags, int nid, nodemask_t *nodemask)
{
    /* 若不包含此标志则不跳过,若包含则继续往下判断 */
    if (!(flags & SHOW_MEM_FILTER_NODES))
        return false;

    /*
     * 无节点掩码 —— 也称为隐式内存 NUMA 策略。无需关注同步参数 `read_mems_allowed_begin`,
     * 因为这里不需要非常精确。
     * 若使能cpuset是 cpuset_current_mems_allowed == current->mems_allowed, cat /proc/pid/status
     * 看 Mems_allowed 成员。
     */
    if (!nodemask)
        nodemask = &cpuset_current_mems_allowed;

    /* 测试 bit0 是不是在 current->mems_allowed 中,默认是在的,嵌入式恒返回false表示不过滤节点 */
    return !node_isset(nid, *nodemask);
}


2.2.1.1.2 show_migration_types()

/* type 里面的值是 1<< migratetype 的位或 */
static void show_migration_types(unsigned char type) //page_alloc.c
{
    static const char types[MIGRATE_TYPES] = {
        [MIGRATE_UNMOVABLE]    = 'U',
        [MIGRATE_MOVABLE]    = 'M',
        [MIGRATE_RECLAIMABLE]    = 'E',
        [MIGRATE_HIGHATOMIC]    = 'H',
#ifdef CONFIG_CMA //1
        [MIGRATE_CMA]        = 'C',
#endif
#ifdef CONFIG_MEMORY_ISOLATION //1
        [MIGRATE_ISOLATE]    = 'I',
#endif
    };
    char tmp[MIGRATE_TYPES + 1];
    char *p = tmp;
    int i;

    /* 只拷贝 type 参数中存在的类型 */
    for (i = 0; i < MIGRATE_TYPES; i++) {
        if (type & (1 << i))
            *p++ = types[i];
    }

    *p = '\0';
    /* 打印迁移类型字符串,如 (UMECH) */
    printk(KERN_CONT "(%s) ", tmp);
}

对照着cat /proc/pagetypeinfo 和 echo m > /proc/sysrq-trigger 来看比较直观,如:

# echo m > /proc/sysrq-trigger; dmesg -c; cat /proc/pagetypeinfo

[ 2352.826996] Normal: 4830*4kB (UMEH) 860*8kB (UMEH) 1105*16kB (UMEH) 259*32kB (UMEH) 256*64kB (UMEH) 352*128kB (UMEH) 194*256kB (UMEH) 72*512kB (UMEH) 15*1024kB (UMEH) 3*2048kB (UME) 0*4096kB = 221640kB


Page block order: 10
Pages per block:  1024

Free pages count per migrate type at order       0      1      2      3      4      5      6      7      8      9     10
Node    0, zone   Normal, type    Unmovable      1    186    915    172    233    336    186     55      1      1      0
Node    0, zone   Normal, type      Movable   3992    437     76     19      5      6      2     14     11      1      0
Node    0, zone   Normal, type  Reclaimable    457    143      3     31      1      5      3      1      1      1      0
Node    0, zone   Normal, type          CMA      0      0      0      0      0      0      0      0      0      0      0
Node    0, zone   Normal, type   HighAtomic    154     81     48     37     17      5      3      2      2      0      0
Node    0, zone   Normal, type      Isolate      0      0      0      0      0      0      0      0      0      0      0

Number of blocks type     Unmovable      Movable  Reclaimable          CMA   HighAtomic      Isolate
Node 0, zone   Normal          928         2098           64           80            6            0


2.2.1.1.3 hugetlb_show_meminfo_node()

默认不使能 CONFIG_HUGETLB_PAGE, 是空实现。若使能了会有如下打印:

void hugetlb_show_meminfo_node(int nid) //hugetlb.c
{
    struct hstate *h;

    if (!hugepages_supported())
        return;

    for_each_hstate(h)
        printk("Node %d hugepages_total=%u hugepages_free=%u hugepages_surp=%u hugepages_size=%lukB\n",
            nid,
            h->nr_huge_pages_node[nid],
            h->free_huge_pages_node[nid],
            h->surplus_huge_pages_node[nid],
            huge_page_size(h) / SZ_1K);
}


2.2.1.1.4 show_swap_cache_info()

void show_swap_cache_info(void) //swap_state.c
{
    /* 打印的是 NR_SWAPCACHE */
    printk("%lu pages in swap cache\n", total_swapcache_pages());
    /* 打印的是全局变量 nr_swap_pages 的值,和 meminfo 中的 "SwapFree:" 是同一个值,表示还可压缩的逻辑大小 */
    printk("Free swap  = %ldkB\n", get_nr_swap_pages() << (PAGE_SHIFT - 10));
    /* 这里的值,和 meminfo 中的 “SwapTotal:” 和 /proc/swaps 中的 “Size” 打印的是同一个值,表示能压缩的最大逻辑大小。*/
    printk("Total swap = %lukB\n", total_swap_pages << (PAGE_SHIFT - 10));
}


三、打印案例详解

这是通过 sysrq 触发的打印,关THP

# echo m > /proc/sysrq-trigger; dmesg -c
[52565.768037] sysrq: Show Memory
[52565.768047] Mem-Info:
/* __show_free_areas(), 单位是页,全局打印,free_pcp 是所有zone在所有online cpu上的pcp页数之和 */
[52565.768050] active_anon:676729 inactive_anon:267362 isolated_anon:0
 active_file:335268 inactive_file:519702 isolated_file:0
 unevictable:3023 dirty:691 writeback:0
 slab_reclaimable:49159 slab_unreclaimable:81681
 mapped:407327 shmem:7902 pagetables:39659
 sec_pagetables:0 bounce:0
 kernel_misc_reclaimable:38372
 free:66354 free_pcp:14792 free_cma:0
/* __show_free_areas(), 每node打印(只打印此次分配相关zone所在node),单位是kB */
[52565.768055] Node 0 active_anon:2706916kB inactive_anon:1069448kB active_file:1341072kB inactive_file:2078808kB unevictable:12092kB isolated(anon):0kB isolated(file):0kB mapped:1629308kB dirty:2764kB \
writeback:0kB shmem:31608kB shmem_thp: 0kB shmem_pmdmapped: 0kB anon_thp: 0kB writeback_tmp:0kB kernel_stack:89452kB shadow_call_stack:0kB pagetables:158636kB sec_pagetables:0kB all_unreclaimable? no /* __show_free_areas(), 每zone打印(只打印此次分配相关zone),单位是kB */ [52565.768060] Normal free:265416kB boost:0kB min:14196kB low:131964kB high:148788kB reserved_highatomic:36864KB active_anon:2706916kB inactive_anon:1069448kB active_file:1341072kB inactive_file:2078808kB \
unevictable:12092kB writepending:2764kB present:13005824kB managed:12697448kB mlocked:12092kB bounce:0kB free_pcp:59168kB local_pcp:16180kB free_cma:0kB [
52565.768065] lowmem_reserve[]: 0 0 0 0 /* __show_free_areas(), 每个zone的空闲链表状态(只打印此次分配相关zone) */ [52565.768069] Normal: 8335*4kB (UMEH) 3128*8kB (UMEH) 1039*16kB (UMEH) 563*32kB (UMEH) 466*64kB (UMEH) 308*128kB (UMEH) 244*256kB (UMEH) 71*512kB (UMEH) 4*1024kB (UH) 0*2048kB 0*4096kB = 265164kB /* __show_free_areas() */ [52565.768081] 863879 total pagecache pages /* show_swap_cache_info(), */ [52565.768082] 186 pages in swap cache [52565.768084] Free swap = 6768272kB [52565.768086] Total swap = 8888208kB /* __show_mem(), 否则是所有zone的 zone->present_pages 之和 */ [52565.768088] 3251456 pages RAM /* __show_mem(), 默认 CONFIG_HIGHMEM 关闭是0,否则是 ZONE_HIGHMEM 的 zone->present_pages 之和 */ [52565.768089] 0 pages HighMem/MovableOnly /* __show_mem(), 是所有zone的 zone->present_pages - zone->managed_pages 差值的总和,含义近似"不可由常规伙伴系统分配"的页*/ [52565.768090] 77094 pages reserved /* __show_mem(), 打印的是 totalcma_pages 是系统总的CMA内存量,等于 /proc/meminfo 中的 CmaTotal 字段 */ [52565.768092] 81920 pages cma reserved

可以看到以全局为单位打印一次,再以 node 为单位打印一次,再以 zone 为单位打印一次。


1. "active_anon:" 开头的全员打印这行详解:

这行统计的是全局的信息。但是 free_pcp 特殊,它是从 0--max_zone_idx 这些zones上的所有PCP的页数和,不是所有zone的。这行的单位都是页。

(1) 匿名页(anon)相关

active_anon: 676729 活跃匿名页,即近期被访问过的匿名内存(堆/栈/mmap 私有映射)。单位是页,约 676729 × 4KB ≈ 2.6GB。若偏高说明用户态程序常驻内存较多。

inactive_anon: 267362 不活跃匿名页,最近一段时间没被访问,是 kswapd 回收/换出的首选候选。约 267362 × 4KB ≈ 1GB。

isolated_anon: 0 正在被内存压缩/迁移隔离的匿名页。为 0 表示当前没有正在进行的匿名页迁移

(2) 文件页(file)相关

active_file: 335268 活跃文件页(页缓存),近期被访问过,较难被回收。约 335268 × 4KB ≈ 1.3GB。

inactive_file: 519702 不活跃文件页,较长时间未访问,内存压力时优先回收,是内存可用量的重要来源。约 519702 × 4KB ≈ 2GB。这是当前最大的单块可回收资源。

isolated_file: 0 正在被迁移/压缩隔离的文件页,为 0 表示无进行中操作。

(3) 不可换出页

unevictable: 3023 不可被回收或换出的页,来自 mlock()、共享内存锁定或特殊映射等。约 3023 × 4KB ≈ 12MB,正常范围。

(4) 脏页与回写

dirty: 691 已修改但尚未落盘的脏页,约 691 × 4KB ≈ 2.8MB,比较小,说明 IO 不繁忙。

writeback: 0 正在写回磁盘中的页为 0,说明此刻无回写 IO 进行中。

(5) Slab 内核对象缓存

slab_reclaimable: 49159 可回收的 slab 内存(如 dentry、inode 缓存),内存压力时可被回收。约 49159 × 4KB ≈ 192MB。

slab_unreclaimable: 81681 不可回收的 slab(如内核 kmalloc 长期持有的对象),只能等释放后才归还。约 81681 × 4KB ≈ 319MB,这部分是内存压力时无能为力的固定开销。

(6) 其他核心字段

mapped: 407327 当前被至少一个进程映射(建立了页表项)的页。包含文件页和匿名页,反映 RSS 的来源总量。约 407327 × 4KB ≈ 1.6GB。

shmem: 7902 共享内存页(tmpfs/shmem),约 7902 × 4KB ≈ 31MB,包括 /dev/shm、匿名共享映射等。

pagetables: 39659 各进程页表占用的内核内存(不含 secondary),约 39659 × 4KB ≈ 155MB。进程多或地址空间大时偏高,通常无法压缩。

sec_pagetables: 0 辅助页表(如某些架构需要的 shadow page tables),此处为 0。

bounce: 0 bounce buffer,用于 DMA 无法访问高端内存时的中转缓冲页,为 0 说明当前无此场景。

kernel_misc_reclaimable: 38372 其他可回收的内核内存,约 38372 × 4KB ≈ 150MB。来自注册了 shrinker 的其他内核子系统(如 BPF JIT、IO buffer 等)。
但是注册了 shrinker ≠ 内存记在 kernel_misc_reclaimable 里,还需要调用 mod_node_page_state(pgdat, NR_KERNEL_MISC_RECLAIMABLE, count) 记录才行,比如 binder 中的 shrinker 就没有调用。

(7) 关键分配字段

free: 66354 buddy 分配器中当前真实空闲页总量,约 66354 × 4KB ≈ 259MB。这是最直接反映"现在能分配多少页"的数字,但需减去 PCP 和保留部分才是真正可用量。

free_pcp: 14792 所有 zone 的所有 online 的 CPU 的 per-cpu pagelist 中缓存的页,约 14792 × 4KB ≈ 58MB。这些页名义上"free",但实际停留在 CPU 本地缓存中,尚未回到 buddy 主链表####。free 字段已包含这些页####,但它们可能无法立刻满足大块高阶分配。

free_cma: 0 CMA 池当前空闲页为 0。这是本快照里最值得关注的异常信号:CMA 配置了总量(之前看到 327680kB = 320MB),但此刻完全耗尽(含 buddy 里 MIGRATE_CMA 链表和 NR_FREE_CMA_PAGES 统计)。任何带 __GFP_CMA 的分配此时必须依赖迁移才能成功,失败风险显著升高。


2. "Node X" 开头的以node为单位打印这行详解

这个打印逐个 node 的统计信息,嵌入式只有一个node,一般只打印 Node 0 的。下面只列出与首行有差异的字段:

(1) THP相关字段

shmem_thp: shmem 使用的透明大页总量。

shmem_pmdmapped: shmem 中以 PMD 大页映射方式映射的量(映射形态统计)。

anon_thp: 匿名内存中透明大页占用量。

(2) 其它字段

writeback_tmp: 临时回写相关页计数(通常很小或 0)。

kernel_stack: 内核线程栈占用总量。

shadow_call_stack: SCS 占用(未启用/未使用时常为 0)。

all_unreclaimable? no: 当前节点并非"全部不可回收",回收器仍认为有可回收空间。执行的是 pgdat->kswapd_failures >= MAX_RECLAIM_RETRIES(16) ? "yes" : "no", 异步回收失败次数还没超过限制就认为还可回收。


3. "<ZoneName> free:"开头的以zone为单位打印这行详解

Normal zone->name, 这是 ZONE_NORMAL 的统计,不是全系统总和。

free: 该 zone 当前空闲页总量(含 buddy 与 PCP 上的空闲页)。

boost: watermark 提升值(回收压力大时可临时抬高水位)。

min/low/high: zone 水位线。分配和回收都会参考:低于 min:通常进入强回收/更严格限制。低于 low:触发 kswapd 回收。达到 high:回收压力相对缓解。

reserved_highatomic: 为高原子分配预留的页,避免原子上下文彻底饿死。

active_anon/inactive_anon/active_file/inactive_file/unevictable: 该 zone 内对应 LRU 状态统计,与上面 Node 行同义,但作用域是 zone。

writepending: 待写回页,这里对应你看到的脏写压力,常与 dirty 关联观察。

present: 该 zone 物理存在页总量,包含并非都可分配的部分。

managed: buddy 直接管理的页总量,比 present 更接近“可用于页分配器管理”的基数。

mlocked: 被 mlock 锁定页。

bounce: bounce buffer 相关页,现代 64 位平台常为 0。

free_pcp: 空闲页中在 PCP(每 CPU 页缓存)里的部分。这些页也是 free,但短时间内不等同于“全局立即可用于任意请求”。

local_pcp: 当前 CPU 本地 PCP 计数。

free_cma: CMA 区域中当前空闲页量,只对带 __GFP_CMA 或 CMA 分配路径更直接可见

lowmem_reserve[]: 按目标 zone 索引划分的保留页阈值数组,有几个 zone 就有几个字段。核心作用是在水位判断里加一道门槛,避免高 zone 的分配把低 zone 吃光。计算逻辑详见 setup_per_zone_lowmem_reserve()。当 free_pages <= min + lowmem_reserve[highest_zoneidx] 就判定不可分配。
lowmem_reserve[k] 的含义是:当本次分配的最高可用 zone 是第 k 级时,当前 zone 里至少要额外保留多少页不能动。

 

四、其它打印案例

1. 分配失败打印-开THP

  1556.312235  1000 12876 12876 W warn_alloc: 3 callbacks suppressed //10s/次的限流
  1556.312238  1000 12876 12876 W EGLThread 5446: page allocation failure: order:0, mode:0x8000c0a(GFP_NOIO|__GFP_HIGHMEM|__GFP_MOVABLE|__GFP_CMA), nodemask=(null),cpuset=top-app,mems_allowed=0
  1556.312266  1000 12876 12876 W         : CPU: 4 PID: 12876 Comm: EGLThread 5446 Tainted: G S      W  OE      6.1.115-android14-11-g302306753d21 #1
  1556.312269  1000 12876 12876 W Hardware name: MT9898 (DT)
  1556.312272  1000 12876 12876 W Call trace:  
  1556.312273  1000 12876 12876 W         : dump_backtrace+0xf4/0x118
  1556.312280  1000 12876 12876 W         : show_stack+0x18/0x24
  1556.312282  1000 12876 12876 W         : dump_stack_lvl+0x60/0x7c
  1556.312292  1000 12876 12876 W         : dump_stack+0x18/0x3c
  1556.312294  1000 12876 12876 W         : warn_alloc+0xf4/0x160
  1556.312299  1000 12876 12876 W         : __alloc_pages_slowpath+0x1090/0x11bc
  1556.312302  1000 12876 12876 W         : __alloc_pages+0x1f4/0x22c
  1556.312305  1000 12876 12876 W         : alloc_zspage+0x9c/0x49c [zsmalloc]
  1556.312315  1000 12876 12876 W         : zs_malloc+0x12c/0x248 [zsmalloc]
  1556.312322  1000 12876 12876 W         : zram_bvec_rw+0x25c/0x86c [zram]
  1556.312329  1000 12876 12876 W         : zram_rw_page+0xac/0x170 [zram]
  1556.312336  1000 12876 12876 W         : bdev_write_page+0x84/0xd8
  1556.312345  1000 12876 12876 W         : __swap_writepage+0x60/0x570
  1556.312353  1000 12876 12876 W         : swap_writepage+0x50/0xa8
  1556.312356  1000 12876 12876 W         : shrink_folio_list+0x9f0/0x12a4
  1556.312362  1000 12876 12876 W         : evict_folios+0x1568/0x18ac
  1556.312367  1000 12876 12876 W         : try_to_shrink_lruvec+0x24c/0x2d0
  1556.312369  1000 12876 12876 W         : shrink_one+0xb0/0x1f8
  1556.312372  1000 12876 12876 W         : shrink_node+0xd58/0x10e4
  1556.312375  1000 12876 12876 W         : do_try_to_free_pages+0x240/0x5b4
  1556.312377  1000 12876 12876 W         : try_to_free_pages+0x2dc/0x50c
  1556.312379  1000 12876 12876 W         : __alloc_pages_slowpath+0x5b4/0x11bc
  1556.312382  1000 12876 12876 W         : __alloc_pages+0x1f4/0x22c
  1556.312384  1000 12876 12876 W         : __folio_alloc+0x1c/0x4c
  1556.312390  1000 12876 12876 W         : alloc_zeroed_user_highpage_movable+0x38/0x48
  1556.312398  1000 12876 12876 W         : handle_mm_fault+0x79c/0x12c0
  1556.312408  1000 12876 12876 W         : do_page_fault+0x1fc/0x4ac
  1556.312416  1000 12876 12876 W         : do_translation_fault+0x38/0x54
  1556.312419  1000 12876 12876 W         : do_mem_abort+0x58/0x118
  1556.312422  1000 12876 12876 W         : el0_da+0x44/0xac
  1556.312425  1000 12876 12876 W         : el0t_64_sync_handler+0x98/0xb4
  1556.312427  1000 12876 12876 W         : el0t_64_sync+0x1a4/0x1a8
  1556.312430  1000 12876 12876 W Mem-Info:  
  1556.312433  1000 12876 12876 W active_anon: 685745 inactive_anon:117999 isolated_anon:0
  1556.312433  1000 12876 12876 W active_file: 230776 inactive_file:96303 isolated_file:0
  1556.312433  1000 12876 12876 W unevictable: 4395 dirty:1396 writeback:0
  1556.312433  1000 12876 12876 W slab_reclaimable: 46296 slab_unreclaimable:87345
  1556.312433  1000 12876 12876 W mapped  : 166814 shmem:23435 pagetables:43709
  1556.312433  1000 12876 12876 W sec_pagetables: 0 bounce:0
  1556.312433  1000 12876 12876 W kernel_misc_reclaimable: 46202
  1556.312433  1000 12876 12876 W free    : 111198 free_pcp:6781 free_cma:63
  1556.312438  1000 12876 12876 W         : Node 0 active_anon:2742980kB inactive_anon:471996kB active_file:923104kB inactive_file:385212kB unevictable:17580kB isolated(anon):0kB isolated(file):0kB \
mapped:667256kB dirty:5584kB writeback:0kB shmem:93740kB shmem_thp: 0kB shmem_pmdmapped: 0kB anon_thp: 868352kB writeback_tmp:0kB kernel_stack:104820kB shadow_call_stack:0kB pagetables:174836kB \
sec_pagetables:0kB all_unreclaimable? no 1556.312443 1000 12876 12876 W Normal free: 445368kB boost:0kB min:73728kB low:200376kB high:218472kB reserved_highatomic:118784KB active_anon:2742980kB inactive_anon:471996kB active_file:923104kB \
inactive_file:385212kB unevictable:17580kB writepending:5584kB present:13005824kB managed:12697452kB mlocked:17580kB bounce:0kB free_pcp:26420kB local_pcp:0kB free_cma:252kB
1556.312448 1000 12876 12876 W lowmem_reserve[]: 0 0 0 0 1556.312451 1000 12876 12876 W Normal : 20099*4kB (UMECHI) 2514*8kB (UMEH) 996*16kB (UMEH) 1938*32kB (UMEH) 851*64kB (UMEH) 810*128kB (UMEH) 304*256kB (UMEH) 53*512kB (UMEH) 4*1024kB (H) 0*2048kB \
0*4096kB = 445660kB 1556.312463 1000 12876 12876 W : 350995 total pagecache pages 1556.312465 1000 12876 12876 W : 942 pages in swap cache 1556.312466 1000 12876 12876 W : Free swap = 3059820kB 1556.312468 1000 12876 12876 W : Total swap = 8888212kB 1556.312469 1000 12876 12876 W : 3251456 pages RAM 1556.312471 1000 12876 12876 W : 0 pages HighMem/MovableOnly 1556.312472 1000 12876 12876 W : 77093 pages reserved 1556.312473 1000 12876 12876 W : 4096 pages cma reserved


2. 多zone有物理内存打印

例如有 5 个zone,且 DMA 和 Normal 两个zone中都有内存的:

# echo m > /proc/sysrq-trigger; dmesg -c
[71224.102222] sysrq: Show Memory
[71224.102236] Mem-Info:
[71224.102238] active_anon:8066 inactive_anon:12498 isolated_anon:0
                active_file:30165 inactive_file:25109 isolated_file:0
                unevictable:0 dirty:54 writeback:0
                slab_reclaimable:12038 slab_unreclaimable:20883
                mapped:11326 shmem:2849 pagetables:1508
                sec_pagetables:0 bounce:0
                kernel_misc_reclaimable:0
                free:80325 free_pcp:2954 free_cma:50193
[71224.102242] Node 0 active_anon:32264kB inactive_anon:49992kB active_file:120660kB inactive_file:100436kB unevictable:0kB isolated(anon):0kB isolated(file):0kB mapped:45304kB dirty:216kB writeback:0kB \
shmem:11396kB writeback_tmp:0kB kernel_stack:9312kB pagetables:6032kB sec_pagetables:0kB all_unreclaimable? no [71224.102247] DMA free:60592kB boost:0kB min:26768kB low:33460kB high:40152kB reserved_highatomic:0KB active_anon:8420kB inactive_anon:6872kB active_file:71272kB inactive_file:64392kB unevictable:0kB \
writepending:48kB present:319488kB managed:301776kB mlocked:0kB bounce:0kB free_pcp:4272kB local_pcp:3888kB free_cma:14764kB [
71224.102251] lowmem_reserve[]: 0 0 585 585 585 [71224.102254] Normal free:260708kB boost:0kB min:53228kB low:66532kB high:79836kB reserved_highatomic:8192KB active_anon:23844kB inactive_anon:43120kB active_file:49388kB inactive_file:36044kB \
unevictable:0kB writepending:168kB present:624640kB managed:600024kB mlocked:0kB bounce:0kB free_pcp:7544kB local_pcp:6244kB free_cma:186008kB [
71224.102258] lowmem_reserve[]: 0 0 0 0 0 [71224.102260] DMA: 846*4kB (MC) 231*8kB (UMEC) 128*16kB (UMEC) 162*32kB (UMEC) 122*64kB (UMEC) 69*128kB (UMEC) 29*256kB (UMEC) 11*512kB (UEC) 6*1024kB (UMEC) 0*2048kB 3*4096kB (C) = 60592kB [71224.102277] Normal: 187*4kB (UMCH) 843*8kB (UMECH) 380*16kB (UMCH) 245*32kB (UMECH) 219*64kB (UMECH) 86*128kB (MECH) 33*256kB (UMEC) 14*512kB (UME) 4*1024kB (UME) 1*2048kB (C) 47*4096kB (MEC) = 260708kB [71224.102289] 58149 total pagecache pages [71224.102290] 24 pages in swap cache [71224.102291] Free swap = 368636kB [71224.102292] Total swap = 450556kB [71224.102293] 236032 pages RAM [71224.102294] 0 pages HighMem/MovableOnly [71224.102295] 10582 pages reserved [71224.102295] 69632 pages cma reserved

 

五、相关补充

1. active_anon 补充解释

active_anon 只统计用户态匿名页,内核占用的内存通常不会计入其中,原因在于内核内存的来源和管理方式与用户态完全不同。

(1) 匿名页是指"没有文件后端支撑"的页,即不对应磁盘上任何文件的页。典型的例子是:malloc、mmap(MAP_ANONYMOUS)、栈内存。这些都是用户态进程的行为。

(2) 内核自己的内存不走这条路。内核通过 kmalloc、vmalloc、alloc_pages 等接口分配内存。这些内存会被计入别的字段,比如:slab_reclaimable/slab_unreclaimable 记录内核 slab 缓存; pagetables 记录进程页表页(也是内核代表进程管理的); kernel_misc_reclaimable 记录其他可回收内核内存; vmalloc 区不计入 LRU,也不出现在 anon 统计里。

(3) qctive/inactive 的 anon/file 分类是 LRU 维度的统计, NR_ACTIVE_ANON/NR_INACTIVE_ANON 来自 LRU 链表。内核自己的内存一般不挂在 LRU 上,因此不进这个计数。只有用户态进程的匿名页(以及少部分内核代表用户态管理的匿名映射,如 tmpfs/shmem 部分)才会上 LRU。

(4) 一个小补充: shmem(tmpfs、共享匿名内存)在内部实现上是"文件页",即使在用户看来像匿名内存,它们实际上会走 active_file/inactive_file 的统计,不是 anon。shmem 字段单独列出来,反映的是这部分共享内存的当前消耗。

总结来说,active_anon 说"用户态程序"是准确的,因为内核自己的内存不走 LRU-anon 这条路,它们有专属的统计字段(slab、pagetables、kernel_misc_reclaimable 等)。


2. vmalloc 区的统计

vmalloc 映射的是内核虚拟地址,不是物理地址,其对应的物理页不挂到用户 LRU anon/file 链表,所以不计入 active_anon、inactive_anon、active_file、inactive_file 中。分配失败的报错也同正常的物理页的分配。

vmalloc 对应的物理内存占用会体现在meminfo中的全局物理页消耗(MemFree 等)和 VmallocUsed(虚拟映射用量)里。


3. "total pagecache pages"中的值会大于"active_file" + "inactive_file"的原因

这三者打印的分别是 NR_FILE_PAGES、NR_INACTIVE_FILE、NR_ACTIVE_FILE。active_file/inactive_file 是 LRU 里的 file 活跃/非活跃链表计数,只覆盖 file LRU 两条链,是用户空间文件页,不等于所有 file pages。

差值主要来源:
(1) shmem/tmpfs 页面会计入 NR_FILE_PAGES,但通常走 anon LRU,不进 active_file/inactive_file,详见 shmem_add_to_page_cache().
(2) swap cache 也会计入 NR_FILE_PAGES,但不在 active_file/inactive_file 这两个值里直接体现,详见 add_to_swap_cache().

"total pagecache pages" 可不是 "active_file" + "inactive_file" 的同义词。它更接近整个 NR_FILE_PAGES 总量,包含了 file LRU 之外的部分(尤其是 shmem 和 swap cache)。

 

 


六、总结

1. 打印也值打印 0--max_zone_idx 这些 zone 的统计信息,因为只能从这些 zone 中分配,若失败也不打印无关 zone 的信息。

2. active_anon/inactive_anon 和 active_file/inactive_file 都只是LRU链表中的统计,主要是用户态占用的页,内核态的页通常不放在LRU中。

3. "free:260708kB" 是来自 NR_FREE_PAGES, 中包含 "reserved_highatomic:8192KB"中的空闲页部分(并非所有) 和 "free_cma:186008kB" 的全部, 不包含 "lowmem_reserve[]"。对于可使用高原子预留和CMA内存的分配,不用从 free 中减去它们与 min 比较,否则需要减去。代码见 zone_watermark_ok() --> __zone_watermark_ok() --> __zone_watermark_unusable_free().

 

posted on 2026-07-20 14:29  Hello-World3  阅读(5)  评论(0)    收藏  举报

导航