AI智能摘要
你好,根据您提供的文档内容,我总结如下: 内存管理是Linux内核中一个复杂的模块,涉及多种数据结构和逻辑。为了帮助开发者了解内存使用情况,内核在核心数据结构中提供了计数统计。初始化时,会进行一系列操作,包括设置架构、构建zonelist、初始化页分配器和内存管理模块等。 `/proc/zoneinfo` 是一个虚拟文件节点,用于展示内存管理区的详细统计信息。通过 `zoneinfo_show` 函数,可以遍历每个内存管理区,并打印相关信息。 `zoneinfo_show_print` 函数负责打印每个内存管理区的详细信息,包括: 1. 当前节点的内存统计信息,例如匿名页面、文件页面、脏页面、写回页面等数量。 2. 当前内存管理区的总信息,例如空闲页面数、最低/高/高水位线、覆盖的页面数、实际存在的页面数、受内核管理的页面数、CMA预留页面数等。 3. 当前内存管理区的详细页面信息,例如空闲页面数、非活跃/活跃的匿名/文件页面数、无法回收的页面数、待写回的页面数、mlock锁定的页面数、页表页面数、中转页面数、压缩页数、CMA空闲页面数等。 4. 当前内存管理区的pageset信息,即每个CPU内存分配器信息,包括可用的页面数、高水位线、批量分配大小等。 5. 其他信息,例如节点是否不可回收、节点的起始页帧号等。 这些信息可以帮助开发者了解内存使用情况,并进行相应的优化和调整。
此摘要由AI分析文章内容生成,仅供参考。

0. 前言

内存管理是一个相对复杂的内核模块,错综复杂的 数据结构 和管理逻辑。 Linux 内核为了帮助开发者从宏观上把握内存的使用情况,在几大核心数据结构中都有相应的计数统计,如物理页面使用情况、伙伴系统分配情况、内存管理区的页面使用情况、内存回收扫描回收情况、内存规整触发情况等等。

start_kernel() 中,会通过 setup_arch()build_all_zonelists()page_alloc_init()mm_init() 等函数对内存相关模块进行初始化,最后会通过 arch_call_rest_init() 对 Linux 系统做剩余的初始化工作,此函数中会调用 rest_init(),并在此调用 kernel_init() 启动init 线程,这里创建了本文将要剖析的虚拟文件节点 /proc/zoneinfo

1. zoneinfo_show

static int zoneinfo_show(struct seq_file *m, void *arg)
{
	pg_data_t *pgdat = (pg_data_t *)arg;
	walk_zones_in_node(m, pgdat, false, false, zoneinfo_show_print);
	return 0;
}

static void walk_zones_in_node(struct seq_file *m, pg_data_t *pgdat,
		bool assert_populated, bool nolock,
		void (*print)(struct seq_file *m, pg_data_t *, struct zone *))
{
	struct zone *zone;
	struct zone *node_zones = pgdat->node_zones;
	unsigned long flags;

    // 遍历每一个node节点
	for (zone = node_zones; zone - node_zones < MAX_NR_ZONES; ++zone) {
		if (assert_populated && !populated_zone(zone))
			continue;

		if (!nolock)
			spin_lock_irqsave(&zone->lock, flags);
        //输出每个节点的zone信息
		print(m, pgdat, zone);
		if (!nolock)
			spin_unlock_irqrestore(&zone->lock, flags);
	}
}

调用 zoneinfo_show() 时,会通过函数 walk_zones_in_node() 对每个zone 进行遍历,从第 0 个zone 开始,当参数 zone 为第 0 个 zone 且该zone 的 present_pages 不为0 时, is_zone_first_populated () 返回为 true,则会将该 node 的page 信息打印出来。

2. zoneinfo_show_print

static void zoneinfo_show_print(struct seq_file *m, pg_data_t *pgdat,
							struct zone *zone)
{
	int i;
	seq_printf(m, "Node %d, zone %8s", pgdat->node_id, zone->name);
	if (is_zone_first_populated(pgdat, zone)) {
		seq_printf(m, "\n  per-node stats");
		for (i = 0; i < NR_VM_NODE_STAT_ITEMS; i++) {
			unsigned long pages = node_page_state_pages(pgdat, i);

			if (vmstat_item_print_in_thp(i))
				pages /= HPAGE_PMD_NR;
			seq_printf(m, "\n      %-12s %lu", node_stat_name(i),
				   pages);
		}
	}
	seq_printf(m,
		   "\n  pages free     %lu"
		   "\n        min      %lu"
		   "\n        low      %lu"
		   "\n        high     %lu"
		   "\n        spanned  %lu"
		   "\n        present  %lu"
		   "\n        managed  %lu"
		   "\n        cma      %lu",
		   zone_page_state(zone, NR_FREE_PAGES),
		   min_wmark_pages(zone),
		   low_wmark_pages(zone),
		   high_wmark_pages(zone),
		   zone->spanned_pages,
		   zone->present_pages,
		   zone_managed_pages(zone),
		   zone_cma_pages(zone));

	seq_printf(m,
		   "\n        protection: (%ld",
		   zone->lowmem_reserve[0]);
	for (i = 1; i < ARRAY_SIZE(zone->lowmem_reserve); i++)
		seq_printf(m, ", %ld", zone->lowmem_reserve[i]);
	seq_putc(m, ')');

	/* If unpopulated, no other information is useful */
	if (!populated_zone(zone)) {
		seq_putc(m, '\n');
		return;
	}

	for (i = 0; i < NR_VM_ZONE_STAT_ITEMS; i++)
		seq_printf(m, "\n      %-12s %lu", zone_stat_name(i),
			   zone_page_state(zone, i));

#ifdef CONFIG_NUMA
	for (i = 0; i < NR_VM_NUMA_EVENT_ITEMS; i++)
		seq_printf(m, "\n      %-12s %lu", numa_stat_name(i),
			   zone_numa_event_state(zone, i));
#endif

	seq_printf(m, "\n  pagesets");
	for_each_online_cpu(i) {
		struct per_cpu_pages *pcp;
		struct per_cpu_zonestat __maybe_unused *pzstats;

		pcp = per_cpu_ptr(zone->per_cpu_pageset, i);
		seq_printf(m,
			   "\n    cpu: %i"
			   "\n              count: %i"
			   "\n              high:  %i"
			   "\n              batch: %i",
			   i,
			   pcp->count,
			   pcp->high,
			   pcp->batch);
#ifdef CONFIG_SMP
		pzstats = per_cpu_ptr(zone->per_cpu_zonestats, i);
		seq_printf(m, "\n  vm stats threshold: %d",
				pzstats->stat_threshold);
#endif
	}
	seq_printf(m,
		   "\n  node_unreclaimable:  %u"
		   "\n  start_pfn:           %lu",
		   pgdat->kswapd_failures >= MAX_RECLAIM_RETRIES,
		   zone->zone_start_pfn);
	seq_putc(m, '\n');
}

该函数大致分下面几个部分剖析:

  • 第一次调用时,输出当前 node 的内存统计信息,记录在 vm_node_stat 数组中,见 2.1 节;
  • 输出 zone 的总信息,见 2.2 节;
  • 输出 zone 的详细页面信息,记录在 zone->vm_stat 数组中,见 2.3 节;
  • 输出 zone 的 pageset 信息,即zone 在每个 CPU 内存分配器信息,记录在 zone->pageset 链表中,见 2.4 节;
  • 输出 zone 回收的 kswapd_failures 是否大于 MAX_RECLAIM_RETRIES(16) ,见 2.5节;
  • 输出 zone 的start_pfn,见 2.5 节;

2.1 输出当前node的内存统计信息

Node 0, zone   Normal
  per-node stats
      nr_inactive_anon 122141
      nr_active_anon 58839
      nr_inactive_file 189638
      nr_active_file 208381
      nr_unevictable 26959
      nr_slab_reclaimable 23751
      nr_slab_unreclaimable 66678
      nr_isolated_anon 0
      nr_isolated_file 0
      workingset_nodes 9493
      workingset_refault_anon 233015
      workingset_refault_file 737280
      workingset_activate_anon 65319
      workingset_activate_file 283723
      workingset_restore_anon 20181
      workingset_restore_file 92763
      workingset_nodereclaim 3456
      nr_anon_pages 167321
      nr_mapped    113653
      nr_file_pages 467931
      nr_dirty     12
      nr_writeback 0
      nr_writeback_temp 0
      nr_shmem     12442
      nr_shmem_hugepages 0
      nr_shmem_pmdmapped 0
      nr_file_hugepages 0
      nr_file_pmdmapped 0
      nr_anon_transparent_hugepages 0
      nr_vmscan_write 398073
      nr_vmscan_immediate_reclaim 8683
      nr_dirtied   701230
      nr_written   1083923
      nr_kernel_misc_reclaimable 2752
      nr_foll_pin_acquired 0
      nr_foll_pin_released 0
      nr_kernel_stack 63056
      nr_shadow_call_stack 15792

参数解释:

参数名称 含义
nr_inactive_anon 非活跃的匿名页面数(以页为单位)。匿名页面通常与进程的私有内存相关,这些页面可能成为回收目标。
nr_active_anon 活跃的匿名页面数(以页为单位)。活跃页面通常在频繁使用,优先保留,不会立即回收。
nr_inactive_file 非活跃的文件缓存页面数(以页为单位)。这些页面存储文件数据但使用频率低,可能成为回收目标。
nr_active_file 活跃的文件缓存页面数(以页为单位)。这些页面存储文件数据且使用频率高,不容易被回收。
nr_unevictable 无法驱逐的页面数,通常被锁定(如通过 mlock)。这些页面不能被回收。
nr_slab_reclaimable 可回收的 slab 缓存页面数(以页为单位)。这些缓存可以在内存不足时被释放。
nr_slab_unreclaimable 不可回收的 slab 缓存页面数(以页为单位)。这些缓存用于内核关键结构,不能被释放。
nr_isolated_anon 正在隔离的匿名页面数(以页为单位)。用于内存回收时暂时从匿名页面中隔离的页面数量。
nr_isolated_file 正在隔离的文件缓存页面数(以页为单位)。用于内存回收时暂时从文件缓存中隔离的页面数量。
workingset_nodes 工作集的节点数,表示访问过的内存节点数量,用于评估内存活跃性。
workingset_refault_anon 匿名页面在工作集中重新加载的次数。表示曾经被回收的匿名页面再次被访问的频率。
workingset_refault_file 文件缓存页面在工作集中重新加载的次数。表示曾经被回收的文件缓存页面再次被访问的频率。
workingset_activate_anon 被激活的匿名页面数,表明非活跃匿名页面被重新标记为活跃页面的次数。
workingset_activate_file 被激活的文件缓存页面数,表明非活跃文件页面被重新标记为活跃页面的次数。
workingset_restore_anon 恢复的匿名页面数,表示曾被回收的匿名页面被重新使用的次数。
workingset_restore_file 恢复的文件缓存页面数,表示曾被回收的文件页面被重新使用的次数。
workingset_nodereclaim 节点回收失败的次数,表示由于 NUMA 策略或内存不足而无法完成页面迁移的情况。
nr_anon_pages 匿名页面的总数,包含活跃和非活跃的匿名页面。
nr_mapped 映射到用户空间的页面数,通常是由 mmap 映射的页面。
nr_file_pages 文件缓存页面的总数,包含活跃和非活跃的文件页面。
nr_dirty 脏页面的数量,这些页面修改过但尚未写回磁盘。
nr_writeback 正在写回的页面数,这些页面正从内存同步到磁盘。
nr_writeback_temp 临时写回页面数,通常用于短期写回操作。
nr_shmem 共享内存页面数,用于进程间共享的匿名内存。
nr_shmem_hugepages 共享内存中使用的大页数量(以页为单位)。
nr_shmem_pmdmapped 共享内存中通过 PMD 映射的大页数量(以页为单位)。
nr_file_hugepages 文件缓存中使用的大页数量(以页为单位)。
nr_file_pmdmapped 文件缓存中通过 PMD 映射的大页数量(以页为单位)。
nr_anon_transparent_hugepages 匿名内存中透明大页的数量。
nr_vmscan_write 内存扫描期间写回的页面数,通常在内存回收时触发。
nr_vmscan_immediate_reclaim 内存扫描期间立即回收的页面数。
nr_dirtied 被标记为脏的页面总数(历史累计值)。
nr_written 写回磁盘的页面总数(历史累计值)。
nr_kernel_misc_reclaimable 内核中其他可回收的页面数。
nr_foll_pin_acquired 通过 get_user_pages() 获取的页面数。
nr_foll_pin_released 通过 put_page() 释放的页面数。
nr_kernel_stack 用于内核堆栈的页面数。
nr_shadow_call_stack 用于影子调用堆栈的页面数(通常与硬件支持的功能相关)。

2.2 输出zone的总信息

pages free     30498
        min      2048
        low      6047
        high     6559
        spanned  8381632
        present  930287
        managed  894377
        cma      38912
        protection: (0, 0, 0)

参数解释:

字段名称 含义
pages free 30498 当前分区中空闲的页面数(以页为单位)。表示可以直接分配的内存大小,30498页约为30498×4KB = 119.1MB。
min 2048 区域的最低水位线(以页为单位)。当pages free低于此值时,系统会触发紧急内存回收。
low 6047 区域的低水位线(以页为单位)。当pages free低于此值时,系统会优先触发后台内存回收。
high 6559 区域的高水位线(以页为单位)。当pages free高于此值时,系统会停止内存回收。
spanned 8381632 当前区域所覆盖的页面数(以页为单位),包括物理内存和可能存在的地址空洞。8381632 页约为8381632×4KB= 33,526,528KB = 31.9GB。
present 930287 此分区中实际存在的物理页面数(以页为单位),即物理内存中属于该分区的页面总数。930287 页约为930287×4KB=3,721,148KB =3.55GB。
managed 894377 此分区中受内核管理的页面数(以页为单位),即可供分配或回收的内存。894377 页约为894377×4KB=3,577,508 KB=3.41GB。
cma 38912 为连续内存分配器(CMA)预留的页面数(以页为单位)。38912 页约为38912×4KB=155,648KB=152MB。
protection (0, 0, 0) 保护值数组,用于描述区域间的内存保护机制。此处值为 (0, 0, 0),表明没有额外的内存保护设置。

2.3 输出 zone 的详细页面信息

nr_free_pages 30498
      nr_zone_inactive_anon 122141
      nr_zone_active_anon 58839
      nr_zone_inactive_file 189638
      nr_zone_active_file 208381
      nr_zone_unevictable 26959
      nr_zone_write_pending 12
      nr_mlock     16525
      nr_page_table_pages 22903
      nr_bounce    0
      nr_zspages   53155
      nr_free_cma  0
参数名称 含义
nr_free_pages 30498 当前区域中空闲的页面数,表示可以直接分配的页面数量。30498 页约为30498×4KB=121,992KB=119.1MB。
nr_zone_inactive_anon 122141 区域内非活跃的匿名页面数(以页为单位)。匿名页面通常与进程的私有内存相关,这些页面可能会被回收。
nr_zone_active_anon 58839 区域内活跃的匿名页面数(以页为单位)。活跃的匿名页面通常在使用中,不容易被回收。
nr_zone_inactive_file 189638 区域内非活跃的文件缓存页面数(以页为单位)。这些页面存储文件数据但使用频率低,可能成为回收目标。
nr_zone_active_file 208381 区域内活跃的文件缓存页面数(以页为单位)。这些页面存储文件数据且使用频率高,不容易被回收。
nr_zone_unevictable 26959 区域内无法回收的页面数(以页为单位)。通常是通过mlock锁定的内存或某些内核专用页面。
nr_zone_write_pending 12 区域内待写回的页面数(以页为单位)。这些页面已被标记为脏页,等待写回磁盘。
nr_mlock 16525 mlock系统调用锁定的页面数(以页为单位)。这些页面被固定在内存中,防止被回收。
nr_page_table_pages 22903 用于存储页表的页面数(以页为单位)。页表记录了虚拟地址和物理地址的映射关系,通常随进程数量增加而增加。
nr_bounce 0 用于 I/O 操作的中转页面数(以页为单位)。这些页面用于支持不直接访问高地址的 DMA 设备,此处为 0 表明未使用中转页面。
nr_zspages 53155 压缩页的总数(以页为单位)。压缩页是zswapzram等机制压缩后的页面数,能够节省内存空间。
nr_free_cma 0 连续内存分配器(CMA)中空闲的页面数(以页为单位)。此处为 0 表示预留的 CMA 内存已经全部分配或尚未启用。

2.4 输出 zone 的 pageset 信息

pagesets
    cpu: 0
              count: 87
              high:  378
              batch: 63
  vm stats threshold: 48
    cpu: 1
              count: 112
              high:  378
              batch: 63
  vm stats threshold: 48
    cpu: 2
              count: 355
              high:  378
              batch: 63
  vm stats threshold: 48
    cpu: 3
              count: 49
              high:  378
              batch: 63
  vm stats threshold: 48
    cpu: 4
              count: 0
              high:  378
              batch: 63
  vm stats threshold: 48
    cpu: 5
              count: 24
              high:  378
              batch: 63
  vm stats threshold: 48
    cpu: 6
              count: 353
              high:  378
              batch: 63
  vm stats threshold: 48
    cpu: 7
              count: 54
              high:  378
              batch: 63
  vm stats threshold: 48

参数解释:

参数名称 含义
cpu 表示具体的 CPU 编号,pagesets为每个 CPU 维护独立的页面分配状态。
count 表示当前 CPU 页面分配缓存中可用的页面数(以页为单位)。用于快速分配页面,减少全局锁的开销。
high 页面缓存的高水位线,超过该值时,内核会将多余的页面返还到全局空闲页面池。
batch 页面批量分配的大小,页面缓存中每次从全局池中获取或释放的页面数。
vm stats threshold 表示更新虚拟内存统计数据的阈值,当页面的变化量超过该值时,会触发统计更新(优化性能)。

2.5 其余信息

node_unreclaimable:  0
start_pfn:           525888

参数解释:

参数名称 含义
node_unreclaimable 0 当前节点是否不可回收的标志位。值为 0 表示当前节点中的页面可以被回收,没有被标记为不可回收。
start_pfn 525888 当前节点的起始页帧号(PFN, Page Frame Number)。用于指示该节点的物理内存起始地址,单位为页(4KB)。