Notes
main
main
  • Introduction
  • linuxKernel
    • tips
    • make_help
    • old linux
      • compile_linux0.11
      • TestEnvironment
      • load_setup
      • get_hard_data
    • list
    • plist
    • fifo
    • idr
    • xarray
    • rbtree
    • maple_tree
    • syscall
    • bitmap
    • page
    • page_flags
    • page_size
    • page mapcount
    • page refcount
    • folio
    • slub
      • proc_slabinfo
      • slub_theory
      • kmalloc_kfree
      • kmem_cache
      • slab_alloc
      • slab_free
      • proc_meminfo_SReclaimable_SReclaimable
    • vmalloc
    • brk
    • mmap
    • mremap
    • mprotect
    • madvise
    • read
    • write
    • shmem
    • huge_page
    • page_fault
    • rmap
    • lru
    • multi-gen-LRU
    • page_reclaim
    • page_cache
    • page_table
    • rcu
    • kvm
    • aarch64_boot
    • tracing_system
    • cache_coherence_and_memory_consistency
    • cpu_speculates
    • mmap_lock
    • per-vma_lock
    • cgroup
    • symbol
    • campact
    • page_ext
    • mempool
    • kernelstack
    • filesystem
    • io_stack
    • workingset
    • ioremap
    • sched_period
  • linuxDebug
    • openocd_openjtag
    • i2c_tools
    • objdump
    • addr2line
    • gdb_useage
    • debug_linux_kernel_via_gdb
    • debug_linux_module_via_gdb
    • early_boot
    • sequentially_execute
    • dynamic_debug
    • research_linuxKernel_by_patch
    • tracefs
    • ebpf
    • bpftrace
    • perf
    • flame_graph
    • crash
    • ASAN_HWASAN_MTE_check_mem_bug
    • page_owner
    • vmtouch
    • fio
    • benchmark
  • linuxSystem
    • common
      • system_version
      • procfs
      • proc_sys_vm
      • cmd_ps
      • makefile
      • file_descriptor
      • psi
      • ulimit
      • top
      • delay_accounting
    • ubuntu
      • custom_kernel
      • get_cmd_src
      • record_ssh_info
      • log
      • run_custom_script
      • repo
      • cockpit
      • nfs
      • tftp
      • misc
    • fedora
      • system_upgrade
      • custom_kernel
      • lvextend
      • yt-dlp
      • jellyfin
  • linuxDriver
    • i2c_peripherals_driver
    • spi_peripherals_driver
    • gpio_subsystem
    • IRQ_driver
    • blockIO_unblockIO_async
    • linux_own_driver
    • misc_device
    • input_device
    • timer
    • atomic_spinlock_semaphore_mutex
    • lcd
    • touch_screen
    • debugfs
    • v4l2
    • mmap
  • hardware
    • paging_mmu_pt
    • iommu
  • process_thread_scheduler
    • scheduler01
    • scheduler02
    • scheduler03
    • scheduler04
    • scheduler05
    • scheduler06
  • memory_management
    • mm1
    • mm2
    • mm3
    • mm4
    • mm5
  • input_output_filesystem
    • io_fs_01
    • io_fs_02
    • io_fs_03
    • io_fs_04
  • lock_and_lockup_detector
    • general_lock
    • hung_task
    • softLockup_hardLockup
    • crash_experiment
  • MIT_6.S081
    • 6.S081_Operating_System_Engineering
    • Schedule.md
    • Class
      • Overview
      • Administrivia
    • Labs
      • Tools
      • Guidance
      • startup
      • syscall
      • page_table
      • Calling_Convention
      • traps
    • xv6
      • xv6
    • References.md
  • qemu
    • qemu_buildroot
    • qemu_busybox.md
    • Serial.md
    • demo_mini2440
      • 0_compilation_error_summary
      • 1_compilation_steps
      • 2_operation_mode
      • 3_transplant_tools_libraries
      • 4_tools_use
      • reference_website
  • tools
    • getKernelSourceCodeList
    • nat
    • shell
    • translating
    • YouCompleteMe
    • cscope
    • global
    • vscode
    • vim
    • binary
    • markdown
    • draw
    • git
    • tig
    • tmux
    • mail_client
    • download_patchset_from_LKML
    • minicom
    • clash
  • other
    • interview
    • interview_c_base
    • know_dontknow
    • Stop-Ask-Questions-The-Stupid-Ways
    • How-To-Ask-Questions-The-Smart-Way
    • docker
    • buildroot
    • rv32_to_rv64
Powered by GitBook
On this page
  • 简介
  • 策略场景
  • 解析 compact_zone()

Was this helpful?

  1. linuxKernel

campact

简介

在出现内存碎片问题时,可以尝试进行内存规整(compact),移动可移动的页面,腾出更多连续空闲内存。

将一个页移动到另一个页的过程叫作页迁移(page migrate),事实上,页迁移是内存管理的独立逻辑, 内核封装了单独接口 migrate_pages()。其中一个应用场景就是 compact,其他类似场景还有 NUMA Balance、Memory hotplug 和 CMA等。

策略场景

内存规整的范围?何时进行内存规整?

对于内存规整范围,内核通常选择以 zone 为单位进行规整,核心接口 compact_zone()。

何时触发,属于触发策略和场景问题,内核当前有四种策略场景,这些场景最终都会通过 compact_zone() 进行内存规整,如下:

  1. direct compact

  2. kcompactd

  3. /proc/sys/vm/compact_memory

  4. /proc/sys/vm/compaction_proactiveness

如下所示,内存规整是基于内存迁移实现的功能,内核根据不同策略触发内存规整,用于缓解内存外部碎片问题, 可以分层分析内存规整。

direct compact        kcompactd         compact_memory       compaction_proactiveness
      |                   |                   |                          |
      +-------------------+-------------------+--------------------------+
      |
      v           +---> migrate scanner
compact_zone() ---+
      |           +---> free scanner
      v
migrate_pages()

direct compact

__alloc_pages_slowpath()
    __alloc_pages_direct_compact()

在 page 分配器进入慢速分配路径时,并且内存低于 min 水位线,会触发 direct compact 策略。

kcompactd

内核在启动过程中会调用 kcompactd_init(),为每个 node 启动一个内核线程 kcompactd ,并且 kcompactd 线程会运行在与 node 相对应的 CPU 核上,在合适的时机 kcompactd 将会被唤醒进行内存规整。

本节主要从二个方面说明,分别是 唤醒条件、运行条件。

唤醒条件,内存规整模块向内核提供 wakeup_kcompactd() 用于唤醒 node 对应的 kcompactd 线程, 唤醒 kcompactd 与 kswapd 是强相关,如下场景会被唤醒:

  1. 内存分配失败时,会先唤醒 kswapd, wakeup_kswapd() 将先判断当前内存无法分配的原因,如果 不是低内存导致,此时内存回收可能已经无法解决此问题,所以将会提前调用 wakeup_kcompactd() 唤醒 kcompacted 线程进行内存规整。

  2. kswapd 线程在每次内存回收完毕后,将会调⽤ kswapd_try_to_sleep() 尝试休眠,随后调用 wakeup_kcompactd() 尝试进行内存规整。

运行条件,当 kcompactd 线程运行时,将会调用 compactd_do_work() 遍历当前 node 的所有 zone 进行合法性判断,若符合条件,则调用 compact_zone() 针对 zone 进⾏内存规整。

/proc/sys/vm/compact_memory

通过对 compact_memory 节点进行写 1,触发当前所有 node 以及所有 zone 进行内存规整。

/proc/devices/system/node/node<id>/compact 只有存在 NUMA 系统上,可以只触发某一个 node 进行内存规整。

解析 compact_zone()

compact_zone() 针对单个 zone 内存区进行内存规整,这是内存规整的最小单元。 通过 migrate scanner 从低地址到高地址寻找迁移页, 通过 free scanner 从高地址到低地址寻找空闲页, 最终将迁移页迁移至空闲页,完成内存规整,如下所示:

low                                                high
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|X|X| |X| |X| |X| | |X| | ... | |X| | |X|X|X|X| | |X|X|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+-----------+-----------+     +-----------+-----------+
  pageblock   pageblock         pageblock   pageblock

migrate scanner                          free scanner
-------------->                          <------------

migrate page list                        free page list
   +-+-+-+-+                               +-+-+-+-+
   |X|X|X|X|                               | | | | |
   +-+-+-+-+                               +-+-+-+-+
       |                                       |
       |                                       |
       |                                       |
       |           +---------------+           |
       +---------->| migrate pages |<----------+
                   +---------------+

更细节一些来说,内存规整开始后,先通过 migrate scanner 扫描,并且扫描单位为一个 pageblock, 将当前 pageblock 中可迁移的页隔离后放入到待迁移的链表。随后调用 free scanner 扫描, free scanner 依然以 pageblock为步长,但不再限制扫描一个pageblock,其扫描的目标是 找到大于等于当前迁移页数量的空闲页。上述扫描过程将会产生迁移页和空闲页,用于后续内存迁移,这样 就完成了内存规整的一轮操作,内存规整并非一次性扫描 zone,然后再迁移,而是以这种一步一步的方式 进行迁移,这能平摊内存规整对性能带来的风险,并且每轮处理后都有机会判断当前内存规整是否可以退出。

再次回到 compact_zone(),核心代码逻辑如下:

  1. compact_finished() 用于判断当前内存规整是否结束

  2. isolate_migratepages() 是 migrate scanner 实现,用于查找需要移动的页

  3. isolate_freepages() 是 free scanner 实现,用于查找空闲页

  4. migrate_pages() 是页迁移函数,将上述两个 scanner 扫描出来的页进行迁移处理,完成内存规整

总而言之,内存规整的核心逻辑在于 migrate scanner 和 free scanner 运作原理。

migrate scanner

主要是 isolate_migratepages(),本质就是将 page 从 LRU 链表中删除。

isolate_migratepages()
      isolate_migratepages_block() break;
            lruvec_del_folio()
                  list_del(page)
            list_add(page, cc->migratepages)

以 pageblock 为步长,检查是否有合适的 pageblock?

如果当前 pageblock 不合适,继续查找下一个 pageblock,但是这里最多查找 32个 pageblock 后, 就需要主动 schedule 出去,睡眠一段时间再继续。

如果有合适的 pageblock,调用一次 isolate_migratepages_block(),查找合适的迁移页。 如果找到,将此迁移页从 LRU 链表中删除,同时加入到 migrate page list。 完成一轮 pageblock 查找后,直接结束查找迁移页。

free scanner

主要是 isolate_freepages(),本质就是将 page 从 page 分配器中取出,不再参与系统内存分配, 仅用于内存规整迁移。

isolate_freepages()
      isolate_freepages_block()
            __isolate_free_page()
                  list_del(page)
            list_add_tail(page, cc->freepages)
      if (cc->nr_freepages >= cc->nr_migratepages) break;

以 pageblock 为步长,调用一次 isolate_freepages_block(),查找合适的空闲页, 如果找到,将此空闲页从 page 分配器中删除,同时加入到 free page list。

完成一轮 pageblock 查找后,判断空闲页数是否大于等于迁移页数?true,结束查找空闲页。 false,继续查找下一个 pageblock,但是这里最多查找 32个 pageblock 后,就需要主动 schedule 出去,睡眠一段时间再继续。

PrevioussymbolNextpage_ext

Last updated 1 year ago

Was this helpful?