Linux内存从0到1学习笔记(九,内存优化调试之一 - kswapd0)

Posted 高桐@BILL

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux内存从0到1学习笔记(九,内存优化调试之一 - kswapd0)相关的知识,希望对你有一定的参考价值。

写在前面

        Linux内核中有一个非常重要的内核线程kswapd,它负责在内存不足时回收页面。kswapd内核线程初始化时会为系统中每个内存结点创建一个名为“kswapd%"内核线程。对于UMA架构下常常是kswapd0线程。

        因此我们可以把kswapd0看作是系统的虚拟内存管理程序,如果物理内存不够用,系统就会唤醒 kswapd0 进程。但是需要特别注意的是,由于kswapd0分配磁盘交换空间作缓存,因此会占用大量的CPU资源。

术语

swap

指的是一个交换分区或文件。负责在内存不足时,将部分内存上的数据交换到swap空间上,以便让系统不会因内存不够用而导致oom或者更致命的情况出现。所以,当内存使用存在压力,开始触发内存回收的行为时,就可能会使用swap空间。

在Linux上可以使用swapon -s命令查看当前系统上正在使用的交换空间有哪些,以及相关信息:

 kswapd

Linux内核中有一个非常重要的内核线程kswapd,它负责在内存不足时回收页面。

watermark_scale_factor
======================

This factor controls the aggressiveness of kswapd. It defines the
amount of memory left in a node/system before kswapd is woken up and
how much memory needs to be free before kswapd goes back to sleep.

The unit is in fractions of 10,000. The default value of 10 means the
distances between watermarks are 0.1% of the available memory in the
node/system. The maximum value is 3000, or 30% of memory.

A high rate of threads entering direct reclaim (allocstall) or kswapd
going to sleep prematurely (kswapd_low_wmark_hit_quickly) can indicate
that the number of free pages kswapd maintains for latency reasons is
too small for the allocation bursts occurring in the system. This knob
can then be used to tune kswapd aggressiveness accordingly.

一、页面回收机制调用路径

首先,我们来看下页面回收机制的主要调用路径(图片来自《奔跑吧,Linux内核!》,如下:

每个内存节点通过一个pg_data_t数据结构来描述物理内存的布局。kswapd传递的参数就是pg_data_t数据结构。

二、kswapd内存回收

2.1 内存水位标记 watermark)

在分配路径上,如果低水位(ALLOC_WMARK_LOW)的情况下无法成功分配内存,那么会通过wakeup_kswaped()函数唤醒kswapd内核线程来回收页面,以便释放一些内存。

2.2 kswapd唤醒路径

alloc_page --> __alloc_pages_nodemask() --> __alloc_pages_slowpath() --> wake_all_kswapds() --> wakeup_kswapd()

在kswapd的分配路径上的唤醒函数wakeup_kswapd()把kswapd_max_order和classzone_idx作为参数传递给kswapd内核线程。在分配路径上,如果低水位(ALLOC_WMARK_LOW)的情况下无法成功分配内存,那么会通过wakeup_kswaped()函数唤醒kswapd内核线程来回收页面,以便释放一些内存。

linux_mainline-5.17.0/mm/vmscan.c
4533  /*
4534   * A zone is low on free memory or too fragmented for high-order memory.  If
4535   * kswapd should reclaim (direct reclaim is deferred), wake it up for the zone's
4536   * pgdat.  It will wake up kcompactd after reclaiming memory.  If kswapd reclaim
4537   * has failed or is not needed, still wake up kcompactd if only compaction is
4538   * needed.
4539   */
4540  void wakeup_kswapd(struct zone *zone, gfp_t gfp_flags, int order,
4541  		   enum zone_type highest_zoneidx)
4542  
4543  	pg_data_t *pgdat;
4544  	enum zone_type curr_idx;
4545  
4546  	if (!managed_zone(zone))
4547  		return;
4548  
4549  	if (!cpuset_zone_allowed(zone, gfp_flags))
4550  		return;
4551  
4552  	pgdat = zone->zone_pgdat;
4553  	curr_idx = READ_ONCE(pgdat->kswapd_highest_zoneidx);
4554  
4555  	if (curr_idx == MAX_NR_ZONES || curr_idx < highest_zoneidx)
4556  		WRITE_ONCE(pgdat->kswapd_highest_zoneidx, highest_zoneidx);
4557  
4558  	if (READ_ONCE(pgdat->kswapd_order) < order)
4559  		WRITE_ONCE(pgdat->kswapd_order, order);
4560  
4561  	if (!waitqueue_active(&pgdat->kswapd_wait))
4562  		return;
4563  
4564  	/* Hopeless node, leave it to direct reclaim if possible */
4565  	if (pgdat->kswapd_failures >= MAX_RECLAIM_RETRIES ||
4566  	    (pgdat_balanced(pgdat, order, highest_zoneidx) &&
4567  	     !pgdat_watermark_boosted(pgdat, highest_zoneidx))) 
4568  		/*
4569  		 * There may be plenty of free memory available, but it's too
4570  		 * fragmented for high-order allocations.  Wake up kcompactd
4571  		 * and rely on compaction_suitable() to determine if it's
4572  		 * needed.  If it fails, it will defer subsequent attempts to
4573  		 * ratelimit its work.
4574  		 */
4575  		if (!(gfp_flags & __GFP_DIRECT_RECLAIM))
4576  			wakeup_kcompactd(pgdat, order, highest_zoneidx);
4577  		return;
4578  	
4579  
4580  	trace_mm_vmscan_wakeup_kswapd(pgdat->node_id, highest_zoneidx, order,
4581  				      gfp_flags);
4582  	wake_up_interruptible(&pgdat->kswapd_wait);
4583  

在系统中可以从/proc/zoneinfo文件中查看当前系统的相关的信息和使用情况。

2.3 /proc/zoneinfo

我们可以通过“cat /proc/zoninfo”来查看每个Node中不同zone区域的统计信息,如下:

bill@bill-VirtualBox:~$ cat /proc/zoneinfo
Node 0, zone      DMA
  per-node stats
      nr_inactive_anon 4694
      nr_active_anon 114895
      nr_inactive_file 141277
      nr_active_file 66066
      nr_unevictable 0
      nr_slab_reclaimable 11617
      nr_slab_unreclaimable 10038
      nr_isolated_anon 0
      nr_isolated_file 0
      workingset_nodes 0
      workingset_refault 0
      workingset_activate 0
      workingset_restore 0
      workingset_nodereclaim 0
      nr_anon_pages 115306
      nr_mapped    60868
      nr_file_pages 211629
      nr_dirty     234
      nr_writeback 0
      nr_writeback_temp 0
      nr_shmem     4915
      nr_shmem_hugepages 0
      nr_shmem_pmdmapped 0
      nr_file_hugepages 0
      nr_file_pmdmapped 0
      nr_anon_transparent_hugepages 0
      nr_unstable  0
      nr_vmscan_write 0
      nr_vmscan_immediate_reclaim 0
      nr_dirtied   18242
      nr_written   4311
      nr_kernel_misc_reclaimable 0
  pages free     3977
        min      67
        low      83
        high     99
        spanned  4095
        present  3998
        managed  3977
        protection: (0, 3426, 3867, 3867, 3867)
      nr_free_pages 3977
      nr_zone_inactive_anon 0
      nr_zone_active_anon 0
      nr_zone_inactive_file 0
      nr_zone_active_file 0
      nr_zone_unevictable 0
      nr_zone_write_pending 0
      nr_mlock     0
      nr_page_table_pages 0
      nr_kernel_stack 0
      nr_bounce    0
      nr_zspages   0
      nr_free_cma  0
      numa_hit     0
      numa_miss    0
      numa_foreign 0
      numa_interleave 0
      numa_local   0
      numa_other   0
  pagesets
    cpu: 0
              count: 0
              high:  0
              batch: 1
  vm stats threshold: 2
  node_unreclaimable:  0
  start_pfn:           1
Node 0, zone    DMA32
  pages free     634266
        min      14908
        low      18635
        high     22362
        spanned  1044480
        present  913392
        managed  889680
        protection: (0, 0, 441, 441, 441)
      nr_free_pages 634266
      nr_zone_inactive_anon 4660
      nr_zone_active_anon 100019
      nr_zone_inactive_file 100862
      nr_zone_active_file 35798
      nr_zone_unevictable 0
      nr_zone_write_pending 74
      nr_mlock     0
      nr_page_table_pages 5231
      nr_kernel_stack 4208
      nr_bounce    0
      nr_zspages   0
      nr_free_cma  0
      numa_hit     468245
      numa_miss    0
      numa_foreign 0
      numa_interleave 0
      numa_local   468245
      numa_other   0
  pagesets
    cpu: 0
              count: 70
              high:  378
              batch: 63
  vm stats threshold: 12
  node_unreclaimable:  0
  start_pfn:           4096
Node 0, zone   Normal
  pages free     2372
        min      1919
        low      2398
        high     2877
        spanned  131072
        present  131072
        managed  112936
        protection: (0, 0, 0, 0, 0)
      nr_free_pages 2372
      nr_zone_inactive_anon 34
      nr_zone_active_anon 14876
      nr_zone_inactive_file 40415
      nr_zone_active_file 30268
      nr_zone_unevictable 0
      nr_zone_write_pending 160
      nr_mlock     0
      nr_page_table_pages 1176
      nr_kernel_stack 2192
      nr_bounce    0
      nr_zspages   0
      nr_free_cma  0
      numa_hit     252639
      numa_miss    0
      numa_foreign 0
      numa_interleave 32495
      numa_local   252639
      numa_other   0
  pagesets
    cpu: 0
              count: 49
              high:  186
              batch: 31
  vm stats threshold: 6
  node_unreclaimable:  0
  start_pfn:           1048576
Node 0, zone  Movable
  pages free     0
        min      0
        low      0
        high     0
        spanned  0
        present  0
        managed  0
        protection: (0, 0, 0, 0, 0)
Node 0, zone   Device
  pages free     0
        min      0
        low      0
        high     0
        spanned  0
        present  0
        managed  0
        protection: (0, 0, 0, 0, 0)
 

以normal为例进行解读,如下:

Node 0, zone   Normal
  pages free     2372
        min      1919
        low      2398
        high     2877
        spanned  131072     ==>  spanned_pages
        present  131072     ==>  present_pages
        managed  112936     ==>  managed_pages
        protection: (0, 0, 0, 0, 0)
......

page_low: 当空闲页面的数量达到page_low所标定的数量的时候,kswapd线程将被唤醒,并开始释放回收页面。这个值默认是page_min的2倍。

page_min: 当空闲页面的数量达到page_min所标定的数量的时候, 分配页面的动作和kswapd线程同步运行

page_high: 当空闲页面的数量达到page_high所标定的数量的时候, kswapd线程将重新休眠,通常这个数值是page_min的3倍

spanned_pages:  代表的是这个zone中所有的页,包含空洞,计算公式是:   zone_end_pfn - zone_start_pfn 
present_pages: 代表的是这个zone中可用的所有物理页,计算公式是:spanned_pages-hole_pages
managed_pages: 代表的是通过buddy管理的所有可用的页,计算公式是:present_pages - reserved_pages,三者的关系是spanned_pages > present_pages > managed_pages

如上Normal zone有三个水位:min, low和high(单位为page大小),标志着当前zone中内存分配压力。当系统中可用内存紧张时,内核kswapd将会被被唤醒,并依据对应水位执行相应的回收操作。

kswapd和这3个参数的互动关系如下图:

总结下来就是,当系统剩余内存低于watermark[low]的时候,内核的kswapd开始起作用,进行内存回收。直到剩余内存达到watermark[high]的时候停止。如果内存消耗导致剩余内存达到了或超过了watermark[min]时,就会触发直接回收(direct reclaim)。

三、/proc/sys/vm/swappiness

/proc/sys/vm/swappiness的值如下,

linux_mainline-5.17.0/Documentation/admin-guide/sysctl/vm.rst
swappiness
==========

This control is used to define the rough relative IO cost of swapping
and filesystem paging, as a value between 0 and 200. At 100, the VM
assumes equal IO cost and will thus apply memory pressure to the page
cache and swap-backed pages equally; lower values signify more
expensive swap IO, higher values indicates cheaper.

Keep in mind that filesystem IO patterns under memory pressure tend to
be more efficient than swap's random IO. An optimal value will require
experimentation and will also be workload-dependent.

The default value is 60.

For in-memory swap, like zram or zswap, as well as hybrid setups that
have swap on faster devices than the filesystem, values beyond 100 can
be considered. For example, if the random IO against the swap device
is on average 2x faster than IO from the filesystem, swappiness should
be 133 (x + 2x = 200, 2x = 133.33).

At 0, the kernel will not initiate swap until the amount of free and
file-backed pages is less than the high watermark in a zone.

这个值范围定义在0~200。如果这个值设置为100表示内存发生回收时,从swap交换内存和从cache回收内存的优先级一样。

如果值为0,则kernel不会初始化swap,直到当前的剩余内存和文件映射内存的总和低于high的水位值。

最后

我的理解总结下来就是,当系统剩余内存低于watermark[low]的时候会触发kswapd,kswapd会根据swappiness的值来确认是否优先进行内存页的换出。需要注意的是,zoneinfo中,不同的zone有不同的水位值,我们在从meminfo计算剩余内存的时候,free的值是总的剩余内存,需要减去其他zone的剩余内存后再和当前对应zone的水位进行比较,以确认是否会触发kswapd进行页换出。

以上是关于Linux内存从0到1学习笔记(九,内存优化调试之一 - kswapd0)的主要内容,如果未能解决你的问题,请参考以下文章

Linux内存从0到1学习笔记(九,内存优化调试之一 - kswapd0)

Linux内存从0到1学习笔记(9.6,内存优化调试之page_owner拆解)

Linux内存从0到1学习笔记(9.5,内存优化调试之记录pid到page_owner)

Linux内存从0到1学习笔记(9.10 内存优化调试之panic_on_oom介绍

Linux内存从0到1学习笔记(9.7 内存优化调试之page_owner内存分配堆栈详解)---更新中

Linux内存从0到1学习笔记(11.2 内存优化方案之内存压缩zram)