内存管理-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(¤t->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) 收藏 举报
浙公网安备 33010602011771号