Slub Memory Allocator -13- (slabinfo)

<kernel v5.0>

/proc/slabinfo

$ cat /proc/slabinfo
slabinfo - version: 2.1
# name            <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab> : tunables <limit> <batchcount> <sharedfactor> : slabdata <active_slabs> <num_slabs> <sharedavail>
nf_conntrack_1      1035   1035    344   23    2 : tunables    0    0    0 : slabdata     45     45      0
ext4_groupinfo_4k   2112   2112    168   24    1 : tunables    0    0    0 : slabdata     88     88      0
ip6-frags              0      0    248   16    1 : tunables    0    0    0 : slabdata      0      0      0
ip6_dst_cache        126    126    384   21    2 : tunables    0    0    0 : slabdata      6      6      0
RAWv6                104    104   1216   26    8 : tunables    0    0    0 : slabdata      4      4      0
UDPLITEv6              0      0   1216   26    8 : tunables    0    0    0 : slabdata      0      0      0
UDPv6                156    156   1216   26    8 : tunables    0    0    0 : slabdata      6      6      0
tw_sock_TCPv6          0      0    272   30    2 : tunables    0    0    0 : slabdata      0      0      0
request_sock_TCPv6     0      0    328   24    2 : tunables    0    0    0 : slabdata      0      0      0
TCPv6                 56     56   2304   14    8 : tunables    0    0    0 : slabdata      4      4      0
cfq_io_cq            216    216    112   36    1 : tunables    0    0    0 : slabdata      6      6      0
bsg_cmd                0      0    312   26    2 : tunables    0    0    0 : slabdata      0      0      0
xfs_icr                0      0    144   28    1 : tunables    0    0    0 : slabdata      0      0      0
xfs_ili                0      0    152   26    1 : tunables    0    0    0 : slabdata      0      0      0
xfs_inode              0      0   1216   26    8 : tunables    0    0    0 : slabdata      0      0      0
xfs_efd_item           0      0    400   20    2 : tunables    0    0    0 : slabdata      0      0      0
xfs_log_item_desc    128    128     32  128    1 : tunables    0    0    0 : slabdata      1      1      0
xfs_da_state           0      0    480   17    2 : tunables    0    0    0 : slabdata      0      0      0
xfs_btree_cur          0      0    208   19    1 : tunables    0    0    0 : slabdata      0      0      0
xfs_log_ticket         0      0    184   22    1 : tunables    0    0    0 : slabdata      0      0      0

각 슬랩 캐시에 대해 다음과 같은 정보가 출력된다.

  • name
    • 슬랩 이름
    • s->name
  •  <active_objs>
    • 사용 중인 object 수 (per-cpu에 있는 free object 조차 사용 중인 object 수에 포함된다)
    • 모든 노드의 n->total_objects – (per 노드 partial 리스트에 있는 슬랩 페이지의 page->objects – page->inuse 합계)
  • <num_objs>
    • 할당된 슬랩 페이지의 모든 object 수(free + inuse)
    • 모든 노드의 n->total_objects 합계
  • <objsize>
    • 슬랩 object 사이즈 (메타 정보 포함)
    • s->size
  • <objperslab>
    • 각 슬랩 페이지에 들어가는 object 수
    • s->oo에 기록된 object 수
  • <pagesperslab>
    • 각 슬랩 페이지에 들어가는 페이지 수
    • 2 ^ s->oo에 기록된 order
  • <limit>
    • 캐시될 최대 object 수 – slub에서 미사용 중(항상 0으로 출력)
  • <batchcount>
    • 한 번에 refill 가능한 object 수 – slub에서 미사용 중(항상 0으로 출력)
  • <sharedfactor>
    • slub에서 미사용 중(항상 0으로 출력)
  • <active_slabs>
    • 사용 중인 슬랩 수 = 전체 슬랩 수와 동일
    • 모든 노드의 n->nr_slabs 합계
  • <num_slabs>
    • 전체 슬랩 수 = 사용 중인 슬랩 수와 동일
    • 모든 노드의 n->nr_slabs 합계
  • <sharedavail>
    • slub에서 미사용 중(항상 0으로 출력)

 


/sys/kernel/slab 디렉토리

슬랩 캐시들은 /sys/kernel/slab 디렉토리에서 관리하며 다음과 같이 분류한다.

  • 병합 불가능한 슬랩 캐시로 슬랩 캐시명을 사용한 디렉토리가 생성된다.
    • 예) TCP 디렉토리
  • 병합 가능한 슬랩 캐시
    • alias 캐시가 가리키는 유니크한 이름으로 자동 생성된 슬랩 캐시로”:”문자열로 시작하는 디렉토리가 생성된다.
    • 예) :A-0001088 디렉토리
  • 병합된 alias 슬랩 캐시로 오리지널 캐시를 가리키는 링크 파일을 생성한다.
    • 예) lrwxrwxrwx 1 root root 0 Nov 19 15:42 UDP -> :A-0001088

 

병합 가능한 슬랩 캐시명의 규칙은 다음과 같다.

  • format
    • “:” 문자 + [[d][a][F][A]-] + 유니크한 7자리 숫자

 

각 옵션 문자에 대한 의미는 다음과 같다.

  • d
    • DMA 사용 슬랩 캐시
    • SLAB_CACHE_DMA 플래그 사용
  • D
    • DMA32 사용 슬랩 캐시
    • SLAB_CAHE_DMA32 플래그 사용 (커널 v5.1-rc3에서 추가)
  • a
    • reclaimable 슬랩 캐시
    • SLAB_RECLAIM_ACCOUNT 플래그 사용
  • F
    • consistency 체크 허용한 슬랩 캐시
    • SLAB_CONSISTENCY_CHECKS 플래그 사용
  • A
    • memcg 통제 허용한 슬랩 캐시
    • SLAB_ACCOUNT 플래그 사용
  • t

 

다음과 같이 병합된 슬랩 캐시들은 병합 가능한 오리지널 캐시 디렉토리를 가리킨다.

$ ls /sys/kernel/slab -la
...
lrwxrwxrwx   1 root root 0 Nov 19 15:32 PING -> :A-0000960
drwxr-xr-x   2 root root 0 Nov 19 15:32 RAW
drwxr-xr-x   2 root root 0 Nov 19 15:32 TCP
lrwxrwxrwx   1 root root 0 Nov 19 15:32 UDP -> :A-0001088
lrwxrwxrwx   1 root root 0 Nov 19 15:32 UDP-Lite -> :A-0001088
lrwxrwxrwx   1 root root 0 Nov 19 15:32 UNIX -> :A-0001024
lrwxrwxrwx   1 root root 0 Nov 19 15:32 aio_kiocb -> :0000192
...

 

다음은 전체 슬랩 캐시를 보여준다.

$ ls /sys/kernel/slab
:0000024    :a-0000256                files_cache              nfs_inode_cache
:0000032    :a-0000360                filp                     nfs_page
:0000040    PING                      fs_cache                 nfs_read_data
:0000048    RAW                       fsnotify_mark            nfs_write_data
:0000056    TCP                       fsnotify_mark_connector  nsproxy
:0000064    UDP                       hugetlbfs_inode_cache    numa_policy
:0000080    UDP-Lite                  iint_cache               p9_req_t
:0000088    UNIX                      inet_peer_cache          pde_opener
:0000104    aio_kiocb                 inode_cache              pid
:0000128    anon_vma                  inotify_inode_mark       pid_namespace
:0000192    anon_vma_chain            iommu_iova               pool_workqueue
:0000208    asd_sas_event             ip4-frags                posix_timers_cache
:0000216    audit_buffer              ip_dst_cache             proc_dir_entry
:0000240    audit_tree_mark           ip_fib_alias             proc_inode_cache
:0000256    bdev_cache                ip_fib_trie              radix_tree_node
:0000320    bio-0                     isp1760_qh               request_queue
:0000344    bio-1                     isp1760_qtd              request_sock_TCP
:0000384    bio_integrity_payload     isp1760_urb_listitem     rpc_buffers
:0000448    biovec-128                jbd2_inode               rpc_inode_cache
:0000464    biovec-16                 jbd2_journal_handle      rpc_tasks
:0000512    biovec-64                 jbd2_journal_head        sas_task
:0000640    biovec-max                jbd2_revoke_record_s     scsi_data_buffer
:0000704    blkdev_ioc                jbd2_revoke_table_s      sd_ext_cdb
:0000768    buffer_head               jbd2_transaction_s       seq_file
:0000896    configfs_dir_cache        kernfs_node_cache        sgpool-128
:0001024    cred_jar                  key_jar                  sgpool-16
:0001088    debug_objects_cache       khugepaged_mm_slot       sgpool-32
:0001984    dentry                    kioctx                   sgpool-64
:0002048    dio                       kmalloc-128              sgpool-8
:0002112    dmaengine-unmap-128       kmalloc-1k               shared_policy_node
:0004096    dmaengine-unmap-16        kmalloc-256              shmem_inode_cache
:A-0000032  dmaengine-unmap-2         kmalloc-2k               sighand_cache
:A-0000040  dmaengine-unmap-256       kmalloc-4k               signal_cache
:A-0000064  dnotify_mark              kmalloc-512              sigqueue
:A-0000072  dnotify_struct            kmalloc-8k               skbuff_ext_cache
:A-0000080  dquot                     kmalloc-rcl-128          skbuff_fclone_cache
:A-0000128  eventpoll_epi             kmalloc-rcl-1k           skbuff_head_cache
:A-0000192  eventpoll_pwq             kmalloc-rcl-256          sock_inode_cache
:A-0000256  ext2_inode_cache          kmalloc-rcl-2k           squashfs_inode_cache
:A-0000704  ext4_allocation_context   kmalloc-rcl-4k           task_delay_info
:A-0000960  ext4_extent_status        kmalloc-rcl-512          task_group
:A-0001024  ext4_free_data            kmalloc-rcl-8k           task_struct
:A-0001088  ext4_groupinfo_4k         kmem_cache               taskstats
:A-0005120  ext4_inode_cache          kmem_cache_node          tcp_bind_bucket
:a-0000016  ext4_io_end               ksm_mm_slot              tw_sock_TCP
:a-0000024  ext4_pending_reservation  ksm_rmap_item            uid_cache
:a-0000032  ext4_prealloc_space       ksm_stable_node          user_namespace
:a-0000040  ext4_system_zone          mbcache                  uts_namespace
:a-0000048  fanotify_event_info       mm_struct                v9fs_inode_cache
:a-0000056  fanotify_perm_event_info  mnt_cache                vm_area_struct
:a-0000064  fasync_cache              mqueue_inode_cache       xfrm_dst_cache
:a-0000072  fat_cache                 names_cache              xfrm_state
:a-0000104  fat_inode_cache           net_namespace
:a-0000128  file_lock_cache           nfs_commit_data
:a-0000144  file_lock_ctx             nfs_direct_cache

 

슬랩 캐시 속성들

$ ls /sys/kernel/slab/TCP
aliases      destroy_by_rcu  objects_partial  red_zone                  slabs_cpu_partial
align        free_calls      objs_per_slab    remote_node_defrag_ratio  store_user
alloc_calls  hwcache_align   order            sanity_checks             total_objects
cpu_partial  min_partial     partial          shrink                    trace
cpu_slabs    object_size     poison           slab_size                 usersize
ctor         objects         reclaim_account  slabs                     validate
  • aliases
    • 병합된 alias 캐시 수
  • align
    • 슬랩 object에 사용할 align 값
    • s->align
  • alloc_calls
    • alloc  유저 (owner) 트래킹을 사용하여 슬랩 캐시의 할당 내역을 출력한다.
    • 예) 1 0xffff000008b8a068 age=2609 pid=3481
  • cache_dma
    • DMA 존을 사용한 슬랩 캐시인지 여부를 보여준다. (1=dma 존 사용 슬랩 캐시, 0=normal 존 사용 슬랩 캐시)
    • SLAB_CACHE_DMA 플래그 사용 유무
  •  cpu_partial
    • per-cpu에서 관리될 최대 슬랩 object 수
    • 사이즈(s->size)에 따라 디폴트 값으로 2, 6, 13, 30 중 하나로 지정된다.
    • 디버그를 사용하는 경우에는 per-cpu 관리를 사용하지 않아 0이 지정된다.
  • cpu_slabs
    • per-cpu용으로 관리되고 있는 슬랩 페이지 수를 합산하여 보여준다. (c->page + c->partial 페이지 수)
    • 노드별로 N[nid]=<per-cpu 슬랩 페이지 수>를 추가로 표기한다.
    • 예) 21 N0=21
  • ctor
    • 생성자가 있는 슬랩 캐시의 생성자 함수명을 보여준다.
    • 예) init_once+0x0/0x78
  • destroy_by_rcu
    • rcu를 사용한 슬랩 object 삭제 기법을 사용하는지 여부를 보여준다.
    • lock-less 접근을 통해 빠르게 삭제하는 rcu 방법의 사용 유무 (1=사용, 0=미사용)
  • free_calls
    • 예) 2 <not-available> age=4295439025 pid=0
  • hwcache_align
    • L1 하드웨어 캐시 라인에 정렬 유무를 보여준다. (1=사용, 0=미사용)
    • SLAB_HWCACHE_ALIGN 플래그 사용 유무
  • min_partial
    • 노드별 partial 리스트에서 유지할 최소 슬랩 페이지 수를 보여준다.
    • 디폴트(s->min_partial) 값은 사이즈에 비례한 5~10 범위의 값을 사용하며 이는 노드별로 적용된다.
    • /proc/sys/kernel/
  • object_size
    • 메타 데이터를 제외한 슬랩 object 사이즈를 보여준다.
    • s->object_size
  • objects
    • 전체 사용중인 슬랩 object 수를 보여준다. (주의: per-cpu에서 관리되는 free object들도 사용 중인 상태로 카운팅된다.)
    • 노드별로 N[nid]=<슬랩 object 수>를 추가로 표기한다.
    • 예) 1288 N0=1288
  • objects_partial
    •  노드별 partial 리스트에서 사용중인 슬랩 object 수를 보여준다.
    • 노드별로 N[nid]=<슬랩 object 수>를 추가로 표기한다.
    • 예) 2 N0=2
  • objs_per_slab
    • 슬랩 페이지에 사용될 object 수를 보여준다.
    • s->oo에 기록된 order 페이지에 포함될 object 수이다.
  • order
    • 슬랩 페이지 할당에 사용될 order 값이다. (s->oo)
    • 이 값은 슬랩 캐시를 생성 시 사이즈에 따라 적절히 산출되었다.
    • 메모리 부족 상황에서 슬랩 페이지를 할당하는 경우 위의 order가 아닌 최소 order (s->min)값으로 슬랩 페이지를 할당하기도 한다.
  • partial
    • 노드 partial 리스트에서 관리되고 있는 슬랩 페이지의 수를 합산하여 보여준다.
      • n->nr_partial 합산
    • 노드별로 N[nid]=<per-cpu 슬랩 object 수>를 추가로 표기한다.
    • 예) 1 N0=1
  • poison
    • poison 디버그 사용 유무를 보여준다. (1=사용, 0=미사용)
    • SLAB_POISON 플래그 사용 유무
    • “slab_debug=FP,<슬랩캐시명>”
  • reclaim_account
    • reclaimable 슬랩 캐시인지 여부를 보여준다. (1=reclaimable 캐시, 0=일반 unreclaimable 캐시)
    • shrinker를 지원하는 슬랩 캐시들을 만들 때 SLAB_RECLAIM_ACCOUNT 플래그를 사용하여 슬랩 캐시를 생성한다.
  • red_zone
    • red-zone 디버그 사용 유무를 보여준다. (1=사용, 0=미사용)
    • SLAB_RED_ZONE 플래그 사용 유무
    • “slab_debug=FZ,<슬랩캐시명>”
  • remote_node_defrag_ratio
    • 로컬 노드의 슬랩 페이지가 부족한 상황인 경우 이 값으로 지정한 백분율만큼 리모트 노드에서 메모리를 허용한다.
    • 디폴트 값은 100이고 0~100까지 허용되며, 0을 사용하는 경우 리모트 노드의 슬랩 페이지를 사용하지 못하게 한다.
  • sanity_checks
    • sanity 체크 디버그 사용 유무를 보여준다. (1=사용, 0=미사용)
    • SLAB_CONSISTENCY_CHECKS 플래그 사용 유무
    • “slab_debug=F,<슬랩캐시명>”
  • shrink
    • reclaimable 슬랩 캐시의 메모리 회수를 수행한다.
    • 예) echo 1 > /sys/kernel/slab/ext4_inode_cache/shrink
  • slab_size
    • 메타 데이터를 포함한 슬랩 object 사이즈를 보여준다.
    • s->size
  • slabs
    • 전체 슬랩 페이지 수를 보여준다.
    • 노드별로 N[nid]=<슬랩 페이지 수>를 추가로 표기한다.
    • 예) 28 N0=28
  • slabs_cpu_partial
    •  per-cpu partial 리스트가 관리하고 있는 free 슬랩 object 수 및 슬랩 페이지 수를 보여준다. (s->page 제외)
    • cpu별로 C[cpu] = <cpu partial free 슬랩 object 수>(<cpu partial 슬랩 페이지 수>)를 추가로 표기한다.
    • 예) 28(28) C0=6(6) C1=3(3) C2=18(18) C3=1(1)
  • store_user
    • 유저 트래킹 디버그 사용 유무를 보여준다. (1=사용, 0=미사용)
    • SLAB_STORE_USER 플래그 사용 유무
    • “slab_debug=FU,<슬랩캐시명>”
  • total_objects
    • 전체 슬랩 object 수를 보여준다.
    • 노드별로 N[nid]=<슬랩 object 수>를 추가로 표기한다.
    • 예) 1288 N0=1288
  • trace
    • 트레이스 디버그 사용 유무를 보여준다. (1=사용, 0=미사용)
    • SLAB_TRACE 플래그 사용 유무
    • “slab_debug=T,<슬랩캐시명>”
  • usersize
    • copy to/from user에 사용할 유저 사이즈를 보여준다.
    • s->usersize
  • validate
    • 슬랩 캐시의 유효성 검사를 수행한다. (강제 디버그 체크)
    • 예) echo 1 > /sys/kernel/slab/anon_vma/validate

 


slabinfo 유틸리티

디버깅 툴 빌드

$ gcc -o slabinfo tools/vm/slabinfo.c

 

사용법

$ sudo ./slabinfo -h
slabinfo 4/15/2011. (c) 2007 sgi/(c) 2011 Linux Foundation.

slabinfo [-ahnpvtsz] [-d debugopts] [slab-regexp]
-a|--aliases           Show aliases
-A|--activity          Most active slabs first
-d<options>|--debug=<options> Set/Clear Debug options
-D|--display-active    Switch line format to activity
-e|--empty             Show empty slabs
-f|--first-alias       Show first alias
-h|--help              Show usage information
-i|--inverted          Inverted list
-l|--slabs             Show slabs
-n|--numa              Show NUMA information
-o|--ops		Show kmem_cache_ops
-s|--shrink            Shrink slabs
-r|--report		Detailed report on single slabs
-S|--Size              Sort by size
-t|--tracking          Show alloc/free information
-T|--Totals            Show summary information
-v|--validate          Validate slabs
-z|--zero              Include empty slabs
-1|--1ref              Single reference

Valid debug options (FZPUT may be combined)
a / A          Switch on all debug options (=FZUP)
-              Switch off all debug options
f / F          Sanity Checks (SLAB_DEBUG_FREE)
z / Z          Redzoning
p / P          Poisoning
u / U          Tracking
t / T          Tracing

 

슬랩 캐시 리스트

$ sudo ./slabinfo
Name                   Objects Objsize    Space Slabs/Part/Cpu  O/S O %Fr %Ef Flg
:at-0000016                256      16     4.0K          0/0/1  256 0   0 100 *a
:at-0000032               3968      32   126.9K         22/0/9  128 0   0 100 *a
:at-0000040                408      40    16.3K          0/0/4  102 0   0  99 *a
:at-0000064              32128      64     2.0M       454/0/48   64 0   0 100 *a
:at-0000104                156     104    16.3K          0/0/4   39 0   0  99 *a
:t-0000024                 680      24    16.3K          0/0/4  170 0   0  99 *
:t-0000032                9472      32   303.1K        12/0/62  128 0   0 100 *
:t-0000040                 612      40    24.5K          0/0/6  102 0   0  99 *
:t-0000064               12483      64   802.8K       128/1/68   64 0   0  99 *
:t-0000088                2714      88   241.6K        15/0/44   46 0   0  98 *
:t-0000096                 168      96    16.3K          0/0/4   42 0   0  98 *
:t-0000104                6552     104   688.1K       158/0/10   39 0   0  99 *
:t-0000128                2240     128   286.7K        15/0/55   32 0   0 100 *
:t-0000192                4305     192   839.6K       152/0/53   21 0   0  98 *
:t-0000256                 192     256    49.1K          3/0/9   16 0   0 100 *
:t-0000320                 954     320   335.8K        12/5/29   25 1  12  90 *A
:t-0000384                  84     384    32.7K          0/0/4   21 1   0  98 *A
:t-0000512                 720     512   368.6K        26/0/19   16 1   0 100 *
:t-0000640                  50     640    32.7K          1/0/1   25 2   0  97 *A
:t-0000960                 187     936   180.2K         1/0/10   17 2   0  97 *A
:t-0001024                 176    1024   180.2K          4/0/7   16 2   0 100 *
:t-0002048                 176    2048   360.4K          2/0/9   16 3   0 100 *
:t-0004032                 153    4032   655.3K         5/2/15    8 3  10  94 *
:t-0004096                  64    4096   262.1K          0/0/8    8 3   0 100 *
anon_vma                  2124     104   241.6K        10/0/49   36 0   0  91
bdev_cache                  72     848    65.5K          0/0/4   18 2   0  93 Aa
biovec-128                  84    1536   131.0K          0/0/4   21 3   0  98 A
biovec-256                  10    3072    32.7K          0/0/1   10 3   0  93 A
biovec-64                   84     768    65.5K          0/0/4   21 2   0  98 A
blkdev_queue                34    1824    65.5K          0/0/2   17 3   0  94
blkdev_requests            204     232    49.1K         0/0/12   17 0   0  96
dentry                   20500     200     4.1M      1012/0/13   20 0   0  97 a
ext4_groupinfo_4k          253     172    45.0K         10/0/1   23 0   0  96 a
ext4_inode_cache         10686    1232    13.4M       400/0/11   26 3   0  97 a
fat_cache                  170      20     4.0K          0/0/1  170 0   0  83 a
fat_inode_cache             60     776    49.1K          1/0/2   20 2   0  94 a
file_lock_cache            100     160    16.3K          0/0/4   25 0   0  97
fscache_cookie_jar          32     124     4.0K          0/0/1   32 0   0  96
ftrace_event_file          595      48    28.6K          6/0/1   85 0   0  99
idr_layer_cache            270    1068   294.9K          5/0/4   30 3   0  97
inode_cache               5589     584     3.3M       191/0/16   27 2   0  96 a
jbd2_journal_handle        292      56    16.3K          0/0/4   73 0   0  99 a
jbd2_transaction_s         189     176    36.8K          0/0/9   21 0   0  90 Aa
kmalloc-8192                24    8192   196.6K          1/0/5    4 3   0 100
kmem_cache                 128     116    16.3K          1/0/3   32 0   0  90 A
kmem_cache_node            128      68    16.3K          1/0/3   32 0   0  53 A
mm_struct                  112     536    65.5K          0/0/4   28 2   0  91 A
mqueue_inode_cache          18     840    16.3K          0/0/1   18 2   0  92 A
nfs_commit_data             18     448     8.1K          0/0/1   18 1   0  98 A
posix_timers_cache          18     216     4.0K          0/0/1   18 0   0  94
proc_inode_cache           546     616   344.0K         4/0/17   26 2   0  97 a
radix_tree_node           2106     304   663.5K        71/0/10   26 1   0  96 a
shmem_inode_cache          644     696   458.7K         20/0/8   23 2   0  97
sighand_cache              184    1372   262.1K          0/0/8   23 3   0  96 A
sigqueue                   112     144    16.3K          0/0/4   28 0   0  98
sock_inode_cache           100     616    65.5K          0/0/4   25 2   0  93 Aa
taskstats                   24     328     8.1K          0/0/1   24 1   0  96
TCP                         68    1816   131.0K          0/0/4   17 3   0  94 A
UDP                         64     960    65.5K          0/0/4   16 2   0  93 A
  • Name
    • 슬랩 명
  • Objects
    • 사용중인(in-use) object 수
  • Objsize
    • 메타 데이터를 제외한 object 사이즈 (s->obj_size)
  • Space
    • 전체 슬랩 페이지에 사용된 바이트 사이즈 (단위에 사용된 값들은 1024 단위가 아니라 1000단위이다.)
  • Slabs/Part/Cpu
    • Slabs
      • 전체 슬랩 페이지 수 – Cpu
        • = full 슬랩 페이지 수 + Part
    • Part
      • 노드별 partial 리스트에서 관리하는 슬랩 페이지 수
    • Cpu
      • cpu별 슬랩 페이지에서 관리하는 슬랩 페이지 수 (c->page + c->partial 페이지 수)
  • O/S
    • 슬랩 페이지당 object 수
    • s->objs_per_slab
  • O
    • order 값
  • %Fr
    • 노드 partial 리스트의 슬랩 페이지 비율
  • %Ef
    • 사용 중인 object 비율
  • Flg
    • 플래그 값은 다음과 같다.
      • *- alias 캐시
      • d – dma
      • A – L1 하드웨어 캐시 정렬(hwcache_align)
      • p – poison
      • a – reclaimable 슬랩 캐시
      • Z – red-zone
      • F – sanity check
      • U – 유저(owner) 트래킹
      • T – 트레이스

 

Loss 소팅 순서 (-L)

$ sudo ./slabinfo -L
Name                   Objects Objsize            Loss Slabs/Part/Cpu  O/S O %Fr %Ef Flg
task_struct                 92    3456          206.3K         11/9/5    9 3  56  60
kmalloc-512                624     512          147.4K        52/31/5   16 1  54  68
:A-0000192                2317     192          140.8K      112/58/31   21 0  40  75 *A
kernfs_node_cache        13230     128          112.8K        432/0/9   30 0   0  93
proc_inode_cache          1114     648           97.3K        96/18/4   12 1  18  88 a
dentry                    7417     192           70.9K      341/56/24   21 0  15  95 a

 

 

사용률 표시 (-D)

$ sudo ./slabinfo -D
Name                   Objects      Alloc       Free   %Fast Fallb O CmpX   UL
:0000024                   170          0          0   0   0     0 0    0    0
:0000040                   102          0          0   0   0     0 0    0    0
:0000048                    85          0          0   0   0     0 0    0    0
:0000056                    73          0          0   0   0     0 0    0    0
:0000064                    64          0          0   0   0     0 0    0    0
:0000080                    51          0          0   0   0     0 0    0    0
:0000128                    32          0          0   0   0     0 0    0    0
:0000192                    21          0          0   0   0     0 0    0    0
:0000256                   192          0          0   0   0     0 0    0    0
:0000448                    36          0          0   0   0     0 1    0    0
:0000896                    36          0          0   0   0     0 2    0    0
:0001024                    16          0          0   0   0     0 2    0    0
:0002048                    16          0          0   0   0     0 3    0    0
:0004096                    40          0          0   0   0     0 3    0    0
:a-0000032                 128          0          0   0   0     0 0    0    0
:a-0000048                  85          0          0   0   0     0 0    0    0
:a-0000056                  73          0          0   0   0     0 0    0    0
:A-0000064                1340          0          0   0   0     0 0    0    0
:A-0000072                 168          0          0   0   0     0 0    0    0
:A-0000080                 255          0          0   0   0     0 0    0    0
:a-0000104                1482          0          0   0   0     0 0    0    0
:A-0000128                 320          0          0   0   0     0 0    0    0
:A-0000192                2317          0          0   0   0     0 0    0    0
:a-0000256                  16          0          0   0   0     0 0    0    0
:A-0001088                 130          0          0   0   0     0 2    0    0
anon_vma                   654          0          0   0   0     0 0    0    0
bdev_cache                  19          0          0   0   0     0 2    0    0
blkdev_ioc                  39          0          0   0   0     0 0    0    0
configfs_dir_cache          46          0          0   0   0     0 0    0    0
...

 

alias 슬랩 캐시 리스트 (-A)

$ sudo ./slabinfo -a

:at-0000016  <- discard_entry f2fs_inode_entry jbd2_revoke_table_s inmem_page_entry free_nid f2fs_ino_entry sit_entry_set
:at-0000024  <- nat_entry nat_entry_set
:at-0000032  <- ext4_extent_status jbd2_revoke_record_s
:at-0000040  <- ext4_free_data ext4_io_end
:at-0000064  <- mmcblk0p6 jbd2_journal_head buffer_head
:at-0000104  <- ext4_allocation_context ext4_prealloc_space
:t-0000024   <- ip_fib_alias dnotify_struct jbd2_inode nsproxy scsi_data_buffer
:t-0000032   <- ftrace_event_field fanotify_event_info dmaengine-unmap-2 secpath_cache anon_vma_chain sd_ext_cdb ip_fib_trie tcp_bind_bucket ext4_system_zone
:t-0000040   <- eventpoll_pwq page->ptl
:t-0000064   <- nfs_page pid kmalloc-64 kiocb uid_cache fasync_cache file_lock_ctx cfq_io_cq
:t-0000088   <- flow_cache vm_area_struct
:t-0000096   <- dnotify_mark fsnotify_mark inotify_inode_mark
:t-0000104   <- task_delay_info kernfs_node_cache
:t-0000128   <- ip_mrt_cache blkdev_ioc sgpool-8 pid_namespace fs_cache inet_peer_cache kmalloc-128 ip_dst_cache eventpoll_epi cred_jar
:t-0000192   <- bio-0 key_jar skbuff_head_cache ip4-frags request_sock_TCP rpc_tasks kmalloc-192 biovec-16
:t-0000256   <- mnt_cache sgpool-16 pool_workqueue kmalloc-256 files_cache
:t-0000320   <- xfrm_dst_cache filp
:t-0000384   <- skbuff_fclone_cache dio
:t-0000512   <- sgpool-32 kmalloc-512
:t-0000640   <- nfs_write_data kioctx nfs_read_data
:t-0000960   <- RAW PING signal_cache
:t-0001024   <- UNIX kmalloc-1024 sgpool-64
:t-0002048   <- kmalloc-2048 sgpool-128 rpc_buffers
:t-0004032   <- task_struct net_namespace
:t-0004096   <- names_cache kmalloc-4096

 

슬랩 캐시 요약 (-T)

$ sudo ./slabinfo -T
Slabcache Totals
----------------
Slabcaches :  71      Aliases  : 118->49  Active:  59
Memory used:  32.5M   # Loss   : 741.4K   MRatio:     2%
# Objects  : 125.2K   # PartObj:      9   ORatio:     0%

Per Cache    Average         Min         Max       Total
---------------------------------------------------------
#Objects        2.1K          10       32.1K      125.2K
#Slabs            58           1        1.0K        3.4K
#PartSlab          0           0           2           2
%PartSlab         0%          0%         10%          0%
PartObjs           0           0           9           9
% PartObj         0%          0%          5%          0%
Memory        550.9K        4.0K       13.4M       32.5M
Used          538.3K        3.4K       13.1M       31.7M
Loss           12.5K           0      302.4K      741.4K

Per Object   Average         Min         Max
---------------------------------------------
Memory           255          16        8.1K
User             253          16        8.1K
Loss               1           0          64

 

슬랩 캐시 세부 정보 (-r)

$ slabinfo -r jake

Slabcache: jake             Aliases:  0 Order :  1 Objects: 2

Sizes (bytes)     Slabs              Debug                Memory
------------------------------------------------------------------------
Object :      30  Total  :       1   Sanity Checks : On   Total:    8192
SlabObj:     392  Full   :       0   Redzoning     : On   Used :      60
SlabSiz:    8192  Partial:       1   Poisoning     : On   Loss :    8132
Loss   :     362  CpuSlab:       0   Tracking      : On   Lalig:     724
Align  :      56  Objects:      20   Tracing       : On   Lpadd:     352

jake has no kmem_cache operations

jake: Kernel object allocation
-----------------------------------------------------------------------
      1 0xffff000008b8a068 age=1174850 pid=3481
      1 0xffff000008b8a078 age=1174837 pid=3481

jake: Kernel object freeing
------------------------------------------------------------------------
      2 <not-available> age=4296123146 pid=0

jake: No NUMA information available.
  • Object
    • 메타 데이터를 제외한 object 사이즈
  • SlabObj
    • 메타 데이터를 포함한 object 사이즈
  • SlabSiz
    • 1 개의 슬렙 페이지 사이즈
  • Loss
    • 1 개의 슬랩 페이지에서 사용할 수 없는 공간 사이즈(remain)
  • Align
    • align 단위
  • Total
    • 전체 슬랩 페이지 수
  • Full
    • 전체 object가 모두 사용 중인 슬랩 페이지 수
  • Partial
    • 노드별 슬랩 페이지 수
  • CpuSlab
    • per-cpu 슬랩 페이지 수
  • Objects
    • 1 개의 슬랩 페이지당 object 수
  • Sanity Checks
    • Sanity 체크 디버그 사용 여부
  • Redzoning
    • Red-zone 디버그 사용 여부
  • Poisoning
    • poison 디버그 사용 여부
  • Tracking
    • 유저 트래킹 디버그 사용 여부
  • Tracing
    • 트레이스 디버그 사용 여부
  • Total
    • 전체 슬렙 페이지에 사용 중인 메모리 사이즈
  • Used
    • 메타 데이터를 제외한 사용(in-use) 중인 슬랩 object가 점유한 메모리 사이즈
  • Loss
    • Toal – Used
  • Lalig
    • (메타 데이터를 포함한 object 사이즈 – 메타 데이터를 제외한 object 사이즈) * 사용 중인 object 수
    • (SlabObj – Object) * 사용 중인 object 수
  • Lpadd
    • 슬랩 페이지의 남는(remain) 영역을 모두 합산한 메모리 사이즈

 


slabtop 유틸리티

$ slabtop
 Active / Total Objects (% used)    : 514911 / 563284 (91.4%)
 Active / Total Slabs (% used)      : 30238 / 30238 (100.0%)
 Active / Total Caches (% used)     : 89 / 121 (73.6%)
 Active / Total Size (% used)       : 198611.59K / 205849.01K (96.5%)
 Minimum / Average / Maximum Object : 0.02K / 0.37K / 12.00K

  OBJS ACTIVE  USE OBJ SIZE  SLABS OBJ/SLAB CACHE SIZE NAME
114156 110759   0%    0.19K   5436       21     21744K dentry
 86895  84904   0%    1.04K  13611       30    435552K ext4_inode_cache
 56589  45405   0%    0.10K   1451       39      5804K buffer_head
 46512  19299   0%    0.04K    456      102      1824K ext4_extent_status
 44832  43854   0%    0.12K   1401       32      5604K kmalloc-128
 41300  39940   0%    0.57K   1475       28     23600K radix_tree_node
 25664  25664 100%    0.06K    401       64      1604K anon_vma_chain
 23070  22810   0%    0.13K    769       30      3076K kernfs_node_cache
 18774  18489   0%    0.57K    783       28     12528K inode_cache
 14344  14234   0%    0.18K    652       22      2608K vm_area_struct
 13110  13110 100%    0.09K    285       46      1140K anon_vma
  8816   8696   0%    0.25K    551       16      2204K filp
  4752   4057   0%    0.64K    198       24      3168K proc_inode_cache
  4386   4386 100%    0.04K     43      102       172K pde_opener
  3825   3825 100%    0.05K     45       85       180K ftrace_event_field
  3616   3616 100%    0.12K    113       32       452K pid
  3549   3549 100%    0.19K    169       21       676K cred_jar
  3125   3125 100%    0.62K    125       25      2000K squashfs_inode_cache
  2896   2877   0%    0.50K    181       16      1448K kmalloc-512
  2496   2496 100%    0.06K     39       64       156K dmaengine-unmap-2
  2408   2408 100%    0.14K     86       28       344K ext4_groupinfo_4k
  2304   2292   0%    1.00K    144       16      2304K kmalloc-1024
  2192   2140   0%    0.25K    137       16       548K kmalloc-256
  2075   2075 100%    0.62K     83       25      1328K sock_inode_cache
...

 

참고

Per-cpu -4- (atomic operations)

 

Per-cpu -4- (atomic operations)

this_cpu_cmpxchg_double()

include/linux/percpu-defs.h

#define this_cpu_cmpxchg_double(pcp1, pcp2, oval1, oval2, nval1, nval2) \
        __pcpu_double_call_return_bool(this_cpu_cmpxchg_double_, pcp1, pcp2, oval1, oval2, nval1, nval2)

per-cpu 값인 pcp(pcp1, pcp2) 값이 old 값(oval1, oval2)과 같은 경우 pcp에 new 값(nval1, nval2)을 atomic하게 대입한다.

  • cmpxchg_double() 함수와 다른 점
    • 다른 cpu와 경합할 필요가 없는 per-cpu 값을 교체하기 위해 더 빠른 atomic operation을 기대한다.
      • arm 아키텍처에서는 atomic opeation 동작을 하는 동안만  local irq를 막는다.
      • arm64 아키텍처에서는 atomic operation 동작을 하는 동안만 preemption을 막는다.

 

아래 그림은 this_cpu_cmpxchg_double() 함수가 처리되는 과정을 보여준다.

this_cpu_cmpxchg_double-1

 

__pcpu_double_call_return_bool()

include/linux/percpu-defs.h

/*
 * Special handling for cmpxchg_double.  cmpxchg_double is passed two
 * percpu variables.  The first has to be aligned to a double word
 * boundary and the second has to follow directly thereafter.
 * We enforce this on all architectures even if they don't support
 * a double cmpxchg instruction, since it's a cheap requirement, and it
 * avoids breaking the requirement for architectures with the instruction.
 */
#define __pcpu_double_call_return_bool(stem, pcp1, pcp2, ...)           \
({                                                                      \
        bool pdcrb_ret__;                                               \
        __verify_pcpu_ptr(&(pcp1));                                     \
        BUILD_BUG_ON(sizeof(pcp1) != sizeof(pcp2));                     \
        VM_BUG_ON((unsigned long)(&(pcp1)) % (2 * sizeof(pcp1)));       \
        VM_BUG_ON((unsigned long)(&(pcp2)) !=                           \
                  (unsigned long)(&(pcp1)) + sizeof(pcp1));             \
        switch(sizeof(pcp1)) {                                          \
        case 1: pdcrb_ret__ = stem##1(pcp1, pcp2, __VA_ARGS__); break;  \
        case 2: pdcrb_ret__ = stem##2(pcp1, pcp2, __VA_ARGS__); break;  \
        case 4: pdcrb_ret__ = stem##4(pcp1, pcp2, __VA_ARGS__); break;  \
        case 8: pdcrb_ret__ = stem##8(pcp1, pcp2, __VA_ARGS__); break;  \
        default:                                                        \
                __bad_size_call_parameter(); break;                     \
        }                                                               \
        pdcrb_ret__;                                                    \
})

pcp(pcp1, pcp2) 값이 old 값(oval1, oval2)과 같은 경우 pcp에 new 값(nval1, nval2)을 대입한다.

  • 데이터 길이에 따라 인수 stem1 ~ stem8을 호출한다.
    • 예) stem=this_cpu_cmpxchg_double_
      • this_cpu_cmpxchg_double_1, this_cpu_cmpxchg_double_2, this_cpu_cmpxchg_double_4, this_cpu_cmpxchg_double_8

 

this_cpu_cmpxchg_double_1()

include/asm-generic/percpu.h

#ifndef this_cpu_cmpxchg_double_1
#define this_cpu_cmpxchg_double_1(pcp1, pcp2, oval1, oval2, nval1, nval2) \
        this_cpu_generic_cmpxchg_double(pcp1, pcp2, oval1, oval2, nval1, nval2)
#endif
#ifndef this_cpu_cmpxchg_double_2
#define this_cpu_cmpxchg_double_2(pcp1, pcp2, oval1, oval2, nval1, nval2) \
        this_cpu_generic_cmpxchg_double(pcp1, pcp2, oval1, oval2, nval1, nval2)
#endif
#ifndef this_cpu_cmpxchg_double_4
#define this_cpu_cmpxchg_double_4(pcp1, pcp2, oval1, oval2, nval1, nval2) \
        this_cpu_generic_cmpxchg_double(pcp1, pcp2, oval1, oval2, nval1, nval2)
#endif
#ifndef this_cpu_cmpxchg_double_8
#define this_cpu_cmpxchg_double_8(pcp1, pcp2, oval1, oval2, nval1, nval2) \
        this_cpu_generic_cmpxchg_double(pcp1, pcp2, oval1, oval2, nval1, nval2)
#endif

pcp(pcp1, pcp2) 값이 old 값(oval1, oval2)과 같은 경우 pcp에 new 값(nval1, nval2)을 대입한다.

  • 32bit arm에서는 double word를 atomic하게 처리하는 연산이 없으므로 generic 함수를 호출한다.

 

this_cpu_generic_cmpxchg_double()

include/asm-generic/percpu.h

#define this_cpu_generic_cmpxchg_double(pcp1, pcp2, oval1, oval2, nval1, nval2) \
({                                                                      \
        int __ret;                                                      \
        unsigned long __flags;                                          \
        raw_local_irq_save(__flags);                                    \
        __ret = raw_cpu_generic_cmpxchg_double(pcp1, pcp2,              \
                        oval1, oval2, nval1, nval2);                    \
        raw_local_irq_restore(__flags);                                 \
        __ret;                                                          \
})

인터럽트를 잠시 비활성화 시킨 채로 pcp(pcp1, pcp2) 값이 old 값(oval1, oval2)과 같은 경우 pcp에 new 값(nval1, nval2)을 대입하고 다시 인터럽트를 원래대로 돌린다.

 

raw_cpu_cmpxchg_double()

include/linux/percpu-defs.h

#define raw_cpu_cmpxchg_double(pcp1, pcp2, oval1, oval2, nval1, nval2) \
        __pcpu_double_call_return_bool(raw_cpu_cmpxchg_double_, pcp1, pcp2, oval1, oval2, nval1, nval2)

pcp(pcp1, pcp2) 값이 old 값(oval1, oval2)과 같은 경우 pcp에 new 값(nval1, nval2)을 대입하는데 성공하면 true를 반환한다.

 

raw_cpu_cmpxchg_double_1()

include/asm-generic/percpu.h

#ifndef raw_cpu_cmpxchg_double_1
#define raw_cpu_cmpxchg_double_1(pcp1, pcp2, oval1, oval2, nval1, nval2) \
        raw_cpu_generic_cmpxchg_double(pcp1, pcp2, oval1, oval2, nval1, nval2)
#endif
#ifndef raw_cpu_cmpxchg_double_2
#define raw_cpu_cmpxchg_double_2(pcp1, pcp2, oval1, oval2, nval1, nval2) \
        raw_cpu_generic_cmpxchg_double(pcp1, pcp2, oval1, oval2, nval1, nval2)
#endif
#ifndef raw_cpu_cmpxchg_double_4
#define raw_cpu_cmpxchg_double_4(pcp1, pcp2, oval1, oval2, nval1, nval2) \
        raw_cpu_generic_cmpxchg_double(pcp1, pcp2, oval1, oval2, nval1, nval2)
#endif
#ifndef raw_cpu_cmpxchg_double_8
#define raw_cpu_cmpxchg_double_8(pcp1, pcp2, oval1, oval2, nval1, nval2) \
        raw_cpu_generic_cmpxchg_double(pcp1, pcp2, oval1, oval2, nval1, nval2)
#endif

pcp(pcp1, pcp2) 값이 old 값(oval1, oval2)과 같은 경우 pcp에 new 값(nval1, nval2)을 대입하는데 성공하면 true를 반환한다.

  • 32bit arm에서는 double word를 atomic하게 처리하는 연산이 없으므로 generic 함수를 호출한다.

 

raw_cpu_generic_cmpxchg_double()

include/asm-generic/percpu.h

#define raw_cpu_generic_cmpxchg_double(pcp1, pcp2, oval1, oval2, nval1, nval2) \
({                                                                      \
        int __ret = 0;                                                  \
        if (raw_cpu_read(pcp1) == (oval1) &&                            \
                         raw_cpu_read(pcp2)  == (oval2)) {              \
                raw_cpu_write(pcp1, nval1);                             \
                raw_cpu_write(pcp2, nval2);                             \
                __ret = 1;                                              \
        }                                                               \
        (__ret);                                                        \
})

pcp(pcp1, pcp2) 값이 old 값(oval1, oval2)과 같은 경우 pcp에 new 값(nval1, nval2)을 대입하는데 성공하면 true를 반환한다.

 

 

raw_cpu_read()

include/linux/percpu-defs.h

#define raw_cpu_read(pcp)               __pcpu_size_call_return(raw_cpu_read_, pcp)

pcp 값을 반환한다.

 

raw_cpu_write()

include/linux/percpu-defs.h

#define raw_cpu_write(pcp, val)         __pcpu_size_call(raw_cpu_write_, pcp, val)

pcp 값으로 val 값을 대입한다.

 

__pcpu_size_call_return()

include/linux/percpu-defs.h

#define __pcpu_size_call_return(stem, variable)                         \
({                                                                      \
        typeof(variable) pscr_ret__;                                    \
        __verify_pcpu_ptr(&(variable));                                 \
        switch(sizeof(variable)) {                                      \
        case 1: pscr_ret__ = stem##1(variable); break;                  \
        case 2: pscr_ret__ = stem##2(variable); break;                  \
        case 4: pscr_ret__ = stem##4(variable); break;                  \
        case 8: pscr_ret__ = stem##8(variable); break;                  \
        default:                                                        \
                __bad_size_call_parameter(); break;                     \
        }                                                               \
        pscr_ret__;                                                     \
})

variable 형의 사이즈에 따라 인수로 지정된 stem1~8을 호출하여 pcp 값을 반환한다.

  • 예) stem=raw_cpu_read_
    • stem=raw_cpu_read_1, stem=raw_cpu_read_2, stem=raw_cpu_read_4, stem=raw_cpu_read_8

 

__pcpu_size_call()

include/linux/percpu-defs.h

#define __pcpu_size_call(stem, variable, ...)                           \
do {                                                                    \
        __verify_pcpu_ptr(&(variable));                                 \
        switch(sizeof(variable)) {                                      \
                case 1: stem##1(variable, __VA_ARGS__);break;           \
                case 2: stem##2(variable, __VA_ARGS__);break;           \
                case 4: stem##4(variable, __VA_ARGS__);break;           \
                case 8: stem##8(variable, __VA_ARGS__);break;           \
                default:                                                \
                        __bad_size_call_parameter();break;              \
        }                                                               \
} while (0)

variable 형의 사이즈에 따라 인수로 지정된 stem1~8을 호출하여 pcp 값에 val 값을 대입한다.

  • 예) stem=raw_cpu_write_
    • stem=raw_cpu_write_1, stem=raw_cpu_write_2, stem=raw_cpu_write_4, stem=raw_cpu_write_8

 

raw_cpu_read_1()

include/asm-generic/percpu.h

#ifndef raw_cpu_read_1
#define raw_cpu_read_1(pcp) (*raw_cpu_ptr(&(pcp)))
#endif
#ifndef raw_cpu_read_2
#define raw_cpu_read_2(pcp) (*raw_cpu_ptr(&(pcp)))
#endif
#ifndef raw_cpu_read_4
#define raw_cpu_read_4(pcp) (*raw_cpu_ptr(&(pcp)))
#endif
#ifndef raw_cpu_read_8
#define raw_cpu_read_8(pcp) (*raw_cpu_ptr(&(pcp)))
#endif

pcp의 값을 읽어온다.

 

raw_cpu_write_1()

include/asm-generic/percpu.h

#ifndef raw_cpu_write_1
#define raw_cpu_write_1(pcp, val)       raw_cpu_generic_to_op(pcp, val, =)
#endif
#ifndef raw_cpu_write_2
#define raw_cpu_write_2(pcp, val)       raw_cpu_generic_to_op(pcp, val, =)
#endif
#ifndef raw_cpu_write_4
#define raw_cpu_write_4(pcp, val)       raw_cpu_generic_to_op(pcp, val, =)
#endif
#ifndef raw_cpu_write_8
#define raw_cpu_write_8(pcp, val)       raw_cpu_generic_to_op(pcp, val, =)
#endif

pcp에 val 값을 저장한다.

 

raw_cpu_generic_to_op()

 

include/asm-generic/percpu.h

#define raw_cpu_generic_to_op(pcp, val, op)                             \
do {                                                                    \
        *raw_cpu_ptr(&(pcp)) op val;                                    \
} while (0)

pcp와 val 값에 op 연산을 한다.

  • 예) raw_cpu_generic_to_op(pcp, val, +=)
    • pcp += val

 

참고

 

Zonned Allocator -3- (Buddy 페이지 할당)

<kernel v5.0>

Zonned Allocator -3- (Buddy 페이지 할당)

Buddy Memory Allocator는 물리 메모리를 페이지 단위로 나눠 할당 관리하는 시스템으로 연속된(contiguous) 물리 페이지의 할당/해지를 지원한다. 또한 최대한 커다란 연속된 페이지를 확보하도록 단편화 관련 알고리즘들이 사용되고 있다.

  • free 메모리 페이지를 2의 차수 페이지 크기로 나누어 관리한다.
    • 2^0=1 페이지부터 2^(MAX_ORDER-1)=1024 페이지 까지 총 11 slot으로 나누어 관리한다.
      • MAX_ORDER=11
        • 커널 2.4.x에서는 defalut로 10을 사용하였었다.
        • CONFIG_FORCE_MAX_ZONEORDER 커널 옵션을 사용하여 크기를 바꿀 수 있다.
  • 각 order slot 또한 단편화 되지 않도록 관리하기 위해 다음과 같은 구조가 준비되어 있다.
    • 같은 mobility 속성을 가진 페이지들끼리 가능하면 뭉쳐서 있도록 각 order slot은 migratetype 별로 나뉘어 관리한다.
      • 이렇게 나누어 관리함으로 페이지 회수 및 메모리 compaction 과정이 효율을 높일 수 있다.
      • NUMA 시스템에서는 특별히 MIGRATE_MOVABLE 타입을 더 도와주기 위해 ZONE_MOVABLE 영역을 만들 수도 있다.
    • 각 페이지를 담는 free_list에서 free page 들은 짝(버디)을 이루어 두 개의 짝(버디)이 모이면 더 큰 order로 합병되어 올라가고 필요시 분할하여 하나 더 적은 order로 나뉠 수 있다.
      • 이제 더 이상 짝(버디)을 관리할 때 map이라는 이름의 bitmap을 사용하지 않고 free_list라는 이름의 리스트와 페이지 정보만을 사용하여 관리한다.
    • free_list는 선두 방향으로 hot 속성을 갖고 후미 방향으로 cold 속성을 갖는다.
      • hot, cold 속성은 각각 리스트의 head와 tail의 위치로 대응하여 관리된다.
        • hot: 리스트 검색에서 앞부분에 놓인 페이지들은 다시 할당되어 사용될 가능성이 높은 page 이다.
        • cold: 리스트 검색에서 뒷부분에 놓인 페이지들은 order가 통합되어 점점 상위 order로 올라갈 가능성이 높은 page 이다. 이를 통해 free 페이지의 단편화를 최대한 억제할 수 있다.
  • migrate 타입이 CMA인 경우 이 영역에는 CMA 페이지와 movable 페이지가 동시에 사용될 수 있다.
    • CMA 페이지들이 할당되면 CMA Memory Allocator에서도 별도로 이를 관리한다.
  • 버디 시스템의 관리 기법이 계속 버전 업하면서 복잡도는 증가하고 있지만 최대한 버디 시스템의 효율(단편화 방지)이 높아지고 있다

 

다음 그림은 Buddy 메모리 할당자의 core 부분의 모습을 보여준다.

  • 커널 v4.4-rc1부터 바뀐 내용은 다음과 같다.
    • MIGRATE_RECLAIMABLE 위치와 MIGRATE_MOVABLE의 위치가 바뀌었다.
    • MIGRATE_RESERVE가 없어지고 MIGRATE_HIGHATOMIC로 바뀌었다.

 

버디 시스템과 관련된 페이지 속성

  • _mapcount
    • 버디 시스템의 free page로 페이지=-128(PAGE_BUDDY_MAPCOUNT_VALUE)
    • 할당되어 버디 시스템에서 빠져나간 페이지=-1
  • private
    • 버디 시스템에서 관리될 때 사용되는 order 값
    • 페이지가 할당되어 버디 시스템에서 빠져나갈 때에는 0으로 리셋된다.
  • index
    • 버디 시스템에서 구분되는 migratetype
  • count
    • 사용자 없이 free 된 페이지=0
    • 할당되고 사용되는 경우 증가

 

다음 그림은 12페이지가 버디 시스템의 free page로 등록되어 있고 짝(버디)이 되는 페이지들이 할당되어 사용되는 모습을 보여준다.

  • 4 페이지의 연속된 free page 2 건이 free_area[2]에 등록
  • 2 페이지의 연속된 free page 1 건이 free_area[1]에 등록

buddy-2

버디 페이지 할당

 

다음 그림은 rmqueue() 함수 이후 호출 과정을 보여준다.

 

rmqueue()

mm/page_alloc.c

/*
 * Allocate a page from the given zone. Use pcplists for order-0 allocations.
 */
static inline
struct page *rmqueue(struct zone *preferred_zone,
                        struct zone *zone, unsigned int order,
                        gfp_t gfp_flags, unsigned int alloc_flags,
                        int migratetype)
{
        unsigned long flags;
        struct page *page;

        if (likely(order == 0)) {
                page = rmqueue_pcplist(preferred_zone, zone, order,
                                gfp_flags, migratetype, alloc_flags);
                goto out;
        }

        /*
         * We most definitely don't want callers attempting to
         * allocate greater than order-1 page units with __GFP_NOFAIL.
         */
        WARN_ON_ONCE((gfp_flags & __GFP_NOFAIL) && (order > 1));
        spin_lock_irqsave(&zone->lock, flags);

        do {
                page = NULL;
                if (alloc_flags & ALLOC_HARDER) {
                        page = __rmqueue_smallest(zone, order, MIGRATE_HIGHATOMIC);
                        if (page)
                                trace_mm_page_alloc_zone_locked(page, order, migratetype);
                }
                if (!page)
                        page = __rmqueue(zone, order, migratetype, alloc_flags);
        } while (page && check_new_pages(page, order));
        spin_unlock(&zone->lock);
        if (!page)
                goto failed;
        __mod_zone_freepage_state(zone, -(1 << order),
                                  get_pcppage_migratetype(page));

        __count_zid_vm_events(PGALLOC, page_zonenum(page), 1 << order);
        zone_statistics(preferred_zone, zone);
        local_irq_restore(flags);

out:
        /* Separate test+clear to avoid unnecessary atomics */
        if (test_bit(ZONE_BOOSTED_WATERMARK, &zone->flags)) {
                clear_bit(ZONE_BOOSTED_WATERMARK, &zone->flags);
                wakeup_kswapd(zone, 0, 0, zone_idx(zone));
        }

        VM_BUG_ON_PAGE(page && bad_range(zone, page), page);
        return page;

failed:
        local_irq_restore(flags);
        return NULL;
}

요청 zone의 버디 시스템을 통해 @migratype의 @order 페이지를 할당한다. 성공 시 할당한 페이지 디스크립터를 반환하고, 실패하는 경우 null을 반환한다.

  • 코드 라인 10~14에서 높은 확률로 0-order 페이지 할당 요청 시 0 order only 버디 캐시로 동작하는 pcp(Per CPU Page Frame Cache)에서 할당을 받는다.
  • 코드 라인 23~32에서 0-order가 아닌 페이지를 요청한 경우 버디 시스템에서 할당을 수행한다. 만일 ALLOC_HARDER 플래그가 사용된 경우 highatomic 타입 리스트에서 먼저 할당을 시도한다.
    • ALLOC_HARDER 플래그는 gfp_mask에서 GFP_ATOMIC  플래그를 사용 시 그 안에 사용되는 __GFP_HIGH 플래그에 의해 설정된다.
      • #define GFP_ATOMIC (__GFP_HIGH|__GFP_ATOMIC|__GFP_KSWAPD_RECLAIM)
    • check_new_pages() 함수에서는 디버그 사용 시 할당한 페이지의 무결성을 체크한다. (hwpoison 등)
  • 코드 라인 36~40에서 할당한 페이지 수만큼 free 페이지 카운터를 감소시키고, PGALLOC 카운터는 증가시킨다. 그리고 처음 요청한 존에서 할당했는지 여부에 대한 hit 및 miss 카운터등을 증/감시킨다.
  • 코드 라인 43~51에서 페이지 할당이 성공 또는 실패되어 빠져나가는 out: 레이블이다. 나가기 전에 존에 boost 워터마크를 조사하여 켜진 경우 플래그를 클리어하고 kswapd를 깨운다.

 

다음 그림은 rmqueue() 함수를 통해 pcp를 포함한 버디시스템에서 페이지 할당이 처리되는 모습을 보여준다.

 

__rmqueue()

mm/page_alloc.c

/*
 * Do the hard work of removing an element from the buddy allocator.
 * Call me with the zone->lock already held.
 */
static __always_inline struct page *
__rmqueue(struct zone *zone, unsigned int order, int migratetype,
                                                unsigned int alloc_flags)
{
        struct page *page;

retry:
        page = __rmqueue_smallest(zone, order, migratetype);
        if (unlikely(!page)) {
                if (migratetype == MIGRATE_MOVABLE)
                        page = __rmqueue_cma_fallback(zone, order);

                if (!page && __rmqueue_fallback(zone, order, migratetype,
                                                                alloc_flags))
                        goto retry;
        }

        trace_mm_page_alloc_zone_locked(page, order, migratetype);
        return page;
}

요청 zone의 버디 시스템을 통해 @migratype의 @order 페이지를 할당한다. 성공 시 할당한 페이지 디스크립터를 반환하고, 실패하는 경우 null을 반환한다.

  • 코드 라인 8에서 먼저 요청한 order부터 최대 order까지 즉, 작은 order 부터 검색하여 할당해본다.
  • 코드 라인9~11에서 페이지 할당이 실패한 경우 만일 movable 타입 요청인 경우 먼저 cma 공간에서 검색한다.
  • 코드 라인 13~15에서 여전히 페이지 할당이 실패한 경우 fallback 타입들을 검색하여 원하는 migrate 타입으로 이주시킨다. 이주가 성공적이면 다시 할당을 재시도한다.

 

다음 그림은 버디 시스템으로 부터 요청된 migrate type과 order로 free 페이지를 할당 받아올 때 만일 할당이 실패하는 경우 migrate type fallback list를 사용하여 검색을 계속하는 것을 보여준다.

 

__rmqueue_smallest()

mm/page_alloc.c

/*
 * Go through the free lists for the given migratetype and remove
 * the smallest available page from the freelists
 */
static __always_inline
struct page *__rmqueue_smallest(struct zone *zone, unsigned int order,
                                                int migratetype)
{
        unsigned int current_order;
        struct free_area *area;
        struct page *page;

        /* Find a page of the appropriate size in the preferred list */
        for (current_order = order; current_order < MAX_ORDER; ++current_order) {
                area = &(zone->free_area[current_order]);
                page = list_first_entry_or_null(&area->free_list[migratetype],
                                                        struct page, lru);
                if (!page)
                        continue;
                list_del(&page->lru);
                rmv_page_order(page);
                area->nr_free--;
                expand(zone, page, order, current_order, area, migratetype);
                set_pcppage_migratetype(page, migratetype);
                return page;
        }

        return NULL;
}

요청한 @migratetype으로 요청한 @order 부터 최상위 order 까지, 즉 작은 order부터 free_list를 검색하여 찾은 free 페이지를 반환한다.

  • 코드 라인 10~15에서 요청 @order 부터 최상위 order 까지@migratetype의 free_list를 순회하며 free 페이지를 찾는다.
  • 코드 라인 16에서 free_list에서 찾은 선두(hot) 페이지를 제거한다.
  • 코드 라인 17에서 할당할 페이지의 order 정보를 0으로 리셋하고 buddy 식별 플래그도 없앤다.
  • 코드 랑니 18에서 해당 order 슬롯의 nr_free를 감소시킨다.
  • 코드 라인 19에서 요청 order에 free 페이지가 없는 경우 큰 order를 가져와 확장한다.
    • guard 페이지를 사용하지 않는 한  큰 order 아래로 요청한 order까지 절반씩 분해하여 추가되는 과정을 알 수 있다.
    • 예) order=3, current_order=6인 경우
      • order=6을 잘라서 order 5, order 4, order 3에 하나씩 추가하고, 남은 order 3 페이지를 반환한다.
  • 코드 라인 20에서 migrate 타입을 기록한다.

 

큰 order 페이지 확장(분해)

expand()

/*
 * The order of subdivision here is critical for the IO subsystem.
 * Please do not alter this order without good reasons and regression
 * testing. Specifically, as large blocks of memory are subdivided,
 * the order in which smaller blocks are delivered depends on the order
 * they're subdivided in this function. This is the primary factor
 * influencing the order in which pages are delivered to the IO
 * subsystem according to empirical testing, and this is also justified
 * by considering the behavior of a buddy system containing a single
 * large block of memory acted on by a series of small allocations.
 * This behavior is a critical factor in sglist merging's success.
 *
 * -- nyc
 */
static inline void expand(struct zone *zone, struct page *page,
        int low, int high, struct free_area *area,
        int migratetype)
{
        unsigned long size = 1 << high;

        while (high > low) {
                area--;
                high--;
                size >>= 1;
                VM_BUG_ON_PAGE(bad_range(zone, &page[size]), &page[size]);

                if (IS_ENABLED(CONFIG_DEBUG_PAGEALLOC) &&
                        debug_guardpage_enabled() &&
                        high < debug_guardpage_minorder()) {
                        /*
                         * Mark as guard pages (or page), that will allow to
                         * merge back to allocator when buddy will be freed.
                         * Corresponding page table entries will not be touched,
                         * pages will stay not present in virtual address space
                         */
                        set_page_guard(zone, &page[size], high, migratetype);
                        continue;
                }
                list_add(&page[size].lru, &area->free_list[migratetype]);
                area->nr_free++;
                set_page_order(&page[size], high);
        }
}

요청한 @low order 보다 큰 @high order에서 페이지를 할당 받은 경우 확장(분해)하여 high-1 부터 low까지의 free 페이지를 등록한다.

  • 코드 라인 5에서 @high order 페이지 수를 미리 산출해둔다.
  • 코드 라인 7~10에서 @high order가 @low order 보다 큰 경우 area 및 high를 감소시키고 size를 반으로 감소시킨다.
  • 코드 라인 13~24에서 디버그용 guard 페이지를 사용하는 경우 max order의 절반 이하(0~5 order)들에 대해서는 skip 한다.
  • 코드 라인 25에서 area->free_list[migratetype]에 page[size]를 추가한다.
  • 코드 라인 26에서 area의 free 엔트리 수를 증가시킨다.
  • 코드 라인 27에서 추가한 페이지에 해당 order 값을 저장한다.

 

다음 그림은 order 8 페이지를 확장하여 할당 요청한 order 5 페이지를 이외에, 남는 페이지들을 order 7 ~ 5에 해당하는 free_list에 추가한다.

 


migrate type 부족 시 fallback

CMA 영역 사용

__rmqueue_cma_fallback()

mm/page_alloc.c

static __always_inline struct page *__rmqueue_cma_fallback(struct zone *zone,
                                        unsigned int order)
{
        return __rmqueue_smallest(zone, order, MIGRATE_CMA);
}

movable 타입 요청 시 해당 migrate 타입에서 검색이 실패하는 경우 cma 영역에서도 시도한다.

 

fallback migrate 타입 사용

__rmqueue_fallback()

mm/page_alloc.c

/*
 * Try finding a free buddy page on the fallback list and put it on the free
 * list of requested migratetype, possibly along with other pages from the same
 * block, depending on fragmentation avoidance heuristics. Returns true if
 * fallback was found so that __rmqueue_smallest() can grab it.
 *
 * The use of signed ints for order and current_order is a deliberate
 * deviation from the rest of this file, to make the for loop
 * condition simpler.
 */
static __always_inline bool
__rmqueue_fallback(struct zone *zone, int order, int start_migratetype,
                                                unsigned int alloc_flags)
{
        struct free_area *area;
        int current_order;
        int min_order = order;
        struct page *page;
        int fallback_mt;
        bool can_steal;

        /*
         * Do not steal pages from freelists belonging to other pageblocks
         * i.e. orders < pageblock_order. If there are no local zones free,
         * the zonelists will be reiterated without ALLOC_NOFRAGMENT.
         */
        if (alloc_flags & ALLOC_NOFRAGMENT)
                min_order = pageblock_order;

        /*
         * Find the largest available free page in the other list. This roughly
         * approximates finding the pageblock with the most free pages, which
         * would be too costly to do exactly.
         */
        for (current_order = MAX_ORDER - 1; current_order >= min_order;
                                --current_order) {
                area = &(zone->free_area[current_order]);
                fallback_mt = find_suitable_fallback(area, current_order,
                                start_migratetype, false, &can_steal);
                if (fallback_mt == -1)
                        continue;

                /*
                 * We cannot steal all free pages from the pageblock and the
                 * requested migratetype is movable. In that case it's better to
                 * steal and split the smallest available page instead of the
                 * largest available page, because even if the next movable
                 * allocation falls back into a different pageblock than this
                 * one, it won't cause permanent fragmentation.
                 */
                if (!can_steal && start_migratetype == MIGRATE_MOVABLE
                                        && current_order > order)
                        goto find_smallest;

                goto do_steal;
        }

        return false;

find_smallest:
        for (current_order = order; current_order < MAX_ORDER;
                                                        current_order++) {
                area = &(zone->free_area[current_order]);
                fallback_mt = find_suitable_fallback(area, current_order,
                                start_migratetype, false, &can_steal);
                if (fallback_mt != -1)
                        break;
        }

        /*
         * This should not happen - we already found a suitable fallback
         * when looking for the largest page.
         */
        VM_BUG_ON(current_order == MAX_ORDER);

do_steal:
        page = list_first_entry(&area->free_list[fallback_mt],
                                                        struct page, lru);

        steal_suitable_fallback(zone, page, alloc_flags, start_migratetype,
                                                                can_steal);

        trace_mm_page_alloc_extfrag(page, order, current_order,
                start_migratetype, fallback_mt);

        return true;

}

요청한 migrate 타입에서 페이지 할당이 실패할 때 호출되는데, migrate 타입 fallback 순서에 의해 다음 migrate 타입을 뺏어온다.(steal). 다른 migrate 타입을 뺏어올 때에는 가장 큰 order 위주로 뺏어온다. 이렇게 큰 페이지를 뺏어와야 다음에 동일한 migrate 타입 요청 시 즉각 대응할 수 있다.

  • 코드 라인 17~18에서 ALLOC_NOFRAGMENT 요청이 있는 경우 페이지 블럭내에서 migrate 타입이 섞이지 않도록 min_order 값에 페이지 블럭 order를 대입한다.
  • 코드 라인 25~31에서 가장 큰 order 부터 min_order까지 버디 리스트를 역방향으로 순회하며 fallback migrate 타입 순으로 free 페이지가 있는지 여부를 알아온다.
  • 코드 라인 41~45에서 요청한 타입이 movable이고, 출력 결과인 can_steal이 false이면 find_smallest 레이블로 이동한다. 그렇지 않은 경우 do_steal 레이블로 이동한다.
  • 코드 라인 48에서 루프를 완료할 때까지 fallback migrate 타입에서도 페이지를 찾지 못한 경우 false를 반환한다.
  • 코드 라인 50~58에서 find_smallest: 레이블이다. 요청한 migrate 타입이 movable인 경우이므로 페이지 블럭내에서 migrate 타입이 섞이더라도 요청한 order 부터 가장 큰 오더까지, 즉 작은 order 순서부터 시작하여 fallback migrate 타입에 free 페이지가 있으면 루프를 탈출한다.
  • 코드 라인 66~76에서 do_steal: 레이블이다. 찾은 fallback migrate 타입의 free 페이지를 요청한 migrate 타입의 free_list로 뺏어온다. 그런 후 true를 반환한다.

 

아래 그림은 unmovable 타입으로 5-order page를 할당 받으려는데 unmovable 타입의 free_list에서 free 페이지를 확보하지 못한 경우 fallback migrate 타입순으로 free_list의 free 페이지를 검색하여 steal 하는 모습을 보여준다.

  • unmovable에 대한 첫 번째 fallback migrate 타입인 reclaimable migrate 타입의 free_list를 검색하고, 그 다음 두 번째 movable migrate 타입의 free_list를 검색한다.

 

적합한 fallback migrate 타입 찾기

find_suitable_fallback()

mm/page_alloc.c

/*
 * Check whether there is a suitable fallback freepage with requested order.
 * If only_stealable is true, this function returns fallback_mt only if
 * we can steal other freepages all together. This would help to reduce
 * fragmentation due to mixed migratetype pages in one pageblock.
 */
int find_suitable_fallback(struct free_area *area, unsigned int order,
                        int migratetype, bool only_stealable, bool *can_steal)
{
        int i;
        int fallback_mt;

        if (area->nr_free == 0)
                return -1;

        *can_steal = false;
        for (i = 0;; i++) {
                fallback_mt = fallbacks[migratetype][i];
                if (fallback_mt == MIGRATE_TYPES)
                        break;

                if (list_empty(&area->free_list[fallback_mt]))
                        continue;

                if (can_steal_fallback(order, migratetype))
                        *can_steal = true;

                if (!only_stealable)
                        return fallback_mt;

                if (*can_steal)
                        return fallback_mt;
        }

        return -1;
}

order 페이지가 fallback migrate 타입을 사용하는 free_list에 존재하고 steal 할 수 있는 fallback migrate 타입을 알아온다. fallback migrate 타입에서도 더 이상 찾지못한 경우 -1을 반환한다. @only_stealable에 true를 사용한 경우 반드시 1 페이지 블럭 전체를 steal해야 경우에 사용한다.(compaction 루틴에서 호출할 때 true로 요청한다) 출력 인자 @can_steal이 true이면 해당 페이지 이외에 해당 페이지가 소속된 페이지 블럭에 포함된 나머지 free 페이지들을 모두 steal 가능한 상태라고 알려주고, false이면 해당 페이지에 대해서만 steal 가능한 상태라고 알려준다.

  • 코드 라인 7~8에서 해당 order에 어떠한 free 엔트리가 없는 경우 -1을 반환한다.
  • 코드 라인 11~17에서 요청한 migrate 타입의 fallback migrate 타입 순서대로 순회한다.
  • 코드 라인 19~26에서 1 페이지 블럭을 모두 steal 해야 하는지 여부를 판단해온다.
    • 한 페이지 블럭 전체의 steal이 가능한 경우 출력 인자 *can_steal에 true를 대입하고 순회 중인 해당 migrate 타입을 반환한다.
    • 만일 only_stealable이 false인 경우 *can_stean 여부와 관계 없이 더 이상 순회하지 않고 해당 migrate 타입을 반환한다.
  • 코드 라인 29에서 순회가 완료되도록 steal 가능한 migrate 타입이 없는 경우 -1을 반환한다.

 

다음 그림은 fallback 하여 사용할 migrate 타입을 알아오는 과정을 보여준다.

 

can_steal_fallback()

mm/page_alloc.c

/*
 * When we are falling back to another migratetype during allocation, try to
 * steal extra free pages from the same pageblocks to satisfy further
 * allocations, instead of polluting multiple pageblocks.
 *
 * If we are stealing a relatively large buddy page, it is likely there will
 * be more free pages in the pageblock, so try to steal them all. For
 * reclaimable and unmovable allocations, we steal regardless of page size,
 * as fragmentation caused by those allocations polluting movable pageblocks
 * is worse than movable allocations stealing from unmovable and reclaimable
 * pageblocks.
 */
static bool can_steal_fallback(unsigned int order, int start_mt)
{
        /*
         * Leaving this order check is intended, although there is
         * relaxed order check in next check. The reason is that
         * we can actually steal whole pageblock if this condition met,
         * but, below check doesn't guarantee it and that is just heuristic
         * so could be changed anytime.
         */
        if (order >= pageblock_order)
                return true;

        if (order >= pageblock_order / 2 ||
                start_mt == MIGRATE_RECLAIMABLE ||
                start_mt == MIGRATE_UNMOVABLE ||
                page_group_by_mobility_disabled)
                return true;

        return false;
}

1 페이지 블럭을 모두 steal 해야 하는지 여부를 판단해온다. 할당 시 원하던 migratetype의 freelist에서 할당이 불가능한 경우 fallback migratetype을 사용한다. 만일 이러한 fallback을 통한 할당을 사용하는 경우 steal할 페이지의 같은 페이지 블럭내의 나머지 free 페이지들을 모두 steal해 오면 다가오는 미래의 추가 할당을 안전하게 수행할 수 있고, 이를 통해 페이지블럭들의 오염(많은 페이지 블럭들이 unmovable로 여러군데에 퍼짐)을 막는 효과가 있다.  이렇게 1 페이지 블럭내의 free 페이지들을 모두 steal 해올지 여부의 판단은 다음과 같이 한다.

  • 어느 정도 큰 order 요청
    • x86, arm, arm64 시스템등이 사용하는 huge page를 사용할 때 pageblock_order는 대체로 9이므로 이의 절반 4.5에서 소숫점 이하를 버리고 4 이상의 order 요청이다.
  • reclaimable 타입이나 unmovable 타입 방향이 목적지(dest)인 경우 이러한 타입의 오염을 막아야 하므로 무조건 요청
  • freelist를 migratetype으로 나누어 관리할 만큼의 주 메모리가 너무 적은 경우

 

fallback migrate 타입에서 steal 하기

steal_suitable_fallback()

mm/page_alloc.c

/*
 * This function implements actual steal behaviour. If order is large enough,
 * we can steal whole pageblock. If not, we first move freepages in this
 * pageblock to our migratetype and determine how many already-allocated pages
 * are there in the pageblock with a compatible migratetype. If at least half
 * of pages are free or compatible, we can change migratetype of the pageblock
 * itself, so pages freed in the future will be put on the correct free list.
 */
static void steal_suitable_fallback(struct zone *zone, struct page *page,
                unsigned int alloc_flags, int start_type, bool whole_block)
{
        unsigned int current_order = page_order(page);
        struct free_area *area;
        int free_pages, movable_pages, alike_pages;
        int old_block_type;

        old_block_type = get_pageblock_migratetype(page);

        /*
         * This can happen due to races and we want to prevent broken
         * highatomic accounting.
         */
        if (is_migrate_highatomic(old_block_type))
                goto single_page;

        /* Take ownership for orders >= pageblock_order */
        if (current_order >= pageblock_order) {
                change_pageblock_range(page, current_order, start_type);
                goto single_page;
        }

        /*
         * Boost watermarks to increase reclaim pressure to reduce the
         * likelihood of future fallbacks. Wake kswapd now as the node
         * may be balanced overall and kswapd will not wake naturally.
         */
        boost_watermark(zone);
        if (alloc_flags & ALLOC_KSWAPD)
                set_bit(ZONE_BOOSTED_WATERMARK, &zone->flags);

        /* We are not allowed to try stealing from the whole block */
        if (!whole_block)
                goto single_page;

        free_pages = move_freepages_block(zone, page, start_type,
                                                &movable_pages);
        /*
         * Determine how many pages are compatible with our allocation.
         * For movable allocation, it's the number of movable pages which
         * we just obtained. For other types it's a bit more tricky.
         */
        if (start_type == MIGRATE_MOVABLE) {
                alike_pages = movable_pages;
        } else {
                /*
                 * If we are falling back a RECLAIMABLE or UNMOVABLE allocation
                 * to MOVABLE pageblock, consider all non-movable pages as
                 * compatible. If it's UNMOVABLE falling back to RECLAIMABLE or
                 * vice versa, be conservative since we can't distinguish the
                 * exact migratetype of non-movable pages.
                 */
                if (old_block_type == MIGRATE_MOVABLE)
                        alike_pages = pageblock_nr_pages
                                                - (free_pages + movable_pages);
                else
                        alike_pages = 0;
        }

        /* moving whole block can fail due to zone boundary conditions */
        if (!free_pages)
                goto single_page;

        /*
         * If a sufficient number of pages in the block are either free or of
         * comparable migratability as our allocation, claim the whole block.
         */
        if (free_pages + alike_pages >= (1 << (pageblock_order-1)) ||
                        page_group_by_mobility_disabled)
                set_pageblock_migratetype(page, start_type);

        return;

single_page:
        area = &zone->free_area[current_order];
        list_move(&page->lru, &area->free_list[start_type]);
}
  • 코드 라인 9에서 현재 페이지가 속한 페이지 블럭의 변경 되기 전 migrate 타입을 알아온다.
  • 코드 라인 15~16에서 highatomic 타입인 경우 single_page 레이블로 이동한다.
  • 코드 라인 19~22에서 페이지 블럭보다 큰 order 요청인 경우 페이지 블럭들내에서 migrate 타입이 섞이지 않는다. 따라서 single_page 레이블로 이동한다.
  • 코드 라인 29~31에서 페이지 블럭보다 작은 fallback order가 동작하면 watermark_boost_factor 비율(디폴트=15000, 150%)이 적용된 워터마크 boost를 지정하고, ALLOC_KSWAPD 요청이 있는 경우 존의 boost 워터마크 플래그를 설정한다.
  • 코드 라인 34~35에서 @whole_block 요청이 없으면 해당 페이지만을 처리하기 위해 single_page 레이블로 이동한다.
  • 코드 라인 37~38에서 해당 페이지가 포함된 페이지 블럭의 모든 free 페이지들이 있는 버디 시스템의 migrate 타입을 @start_type으로 이동시킨다.
  • 코드 라인 44~59에서 요청 migrate 타입별로 호환되는 migrate 타입 페이지들을 다음과 같이 산출하여 alike_pages에 대입한다.
    • 사용중인 페이지는 각 타입에 동조하는 유사 페이지(alike_pages)로 인정한다.
    • 그러나 사용중인 페이지는 정확히 3가지 migratetype으로 구분하지 못하고 movable과 !movable로만 구분할 수 있다.
    • 따라서 MIGRATE_UNMOVABLE과 MIGRATE_RECLAIMABLE간의 migration에는 alike 페이지에 참여할 수 없다.
  • 코드 라인 62~63에서 free 페이지가 없는 경우 single_page 레이블로 이동한다.
  • 코드 라인 69~71에서 호환되는 페이지를 포함한 free 페이지가 페이지 블럭의 절반 이상인 경우 해당 페이지 블럭의 migrate 타입을 변경한다.
  • 코드 라인 75~77에서 single_page: 레이블이다. 해당 페이지만 free_list의 @start_type 으로 옮긴다.

 

다음 그림은 steal_suitable_fallback() 함수에서 페이지 블럭을 steal 할지, 아니면 해당 페이지만 steal 할지 여부를 판단하여 동작하는 모습을 보여준다.

 

change_pageblock_range()

mm/page_alloc.c

static void change_pageblock_range(struct page *pageblock_page,
                                        int start_order, int migratetype)
{
        int nr_pageblocks = 1 << (start_order - pageblock_order);

        while (nr_pageblocks--) {
                set_pageblock_migratetype(pageblock_page, migratetype);
                pageblock_page += pageblock_nr_pages;
        }
}

요청한 order 내에 있는 모든 페이지 블럭의 수 만큼 각 페이지 블럭에 대해 migratetype을 설정한다.

  • 코드 라인 4에서 start_order내에 들어갈 수 있는 pageblock의 수를 산출한다.
  • 코드 라인 6~9에서 페이지 블럭 수 만큼 순회하며 페이지 블럭의 migrate 타입을 설정한다.

 

페이지 블럭내 모든 free 페이지의 migrate 타입 이동

move_freepages_block()

mm/page_alloc.c

int move_freepages_block(struct zone *zone, struct page *page,
                                int migratetype)
{
        unsigned long start_pfn, end_pfn;
        struct page *start_page, *end_page;

        start_pfn = page_to_pfn(page);
        start_pfn = start_pfn & ~(pageblock_nr_pages-1);
        start_page = pfn_to_page(start_pfn);
        end_page = start_page + pageblock_nr_pages - 1;
        end_pfn = start_pfn + pageblock_nr_pages - 1;

        /* Do not cross zone boundaries */
        if (!zone_spans_pfn(zone, start_pfn))
                start_page = page;
        if (!zone_spans_pfn(zone, end_pfn))
                return 0;

        return move_freepages(zone, start_page, end_page, migratetype);
}

지정된 zone의 요청 page가 있는 pageblock내의 모든 free page들을 요청 migrate 타입으로 변경하고 버디 시스템에서 요청 migrate 타입으로 이동하고 이동된 페이지 수를 반환한다. 단 존 경계를 이유로 페이지블럭의 끝 부분이 partial된 경우 move 시킬 수 없다.

  • 요청 zone의 시작 페이지가 pageblock 단위로 정렬되지 않은 경우에도 zone의 시작 주소부터 move 적용가능 (partial)
  • 요청 zone의 끝 페이지가 pageblock 단위로 정렬되지 않은 경우 그 pageblock은 사용할 수 없음

 

아래 그림은 지정된 zone의 페이지블럭에 있는 free page들의 migrate type을 요청한 타입으로 변경하는 모습을 보여준다.

move_freepages_block-1

 

zone_spans_pfn()

include/linux/mmzone.h

static inline bool zone_spans_pfn(const struct zone *zone, unsigned long pfn)
{
        return zone->zone_start_pfn <= pfn && pfn < zone_end_pfn(zone);
}

pfn이 zone의 영역에 들어있는지 여부를 반환한다.

 

move_freepages()

mm/page_alloc.c

/*
 * Move the free pages in a range to the free lists of the requested type.
 * Note that start_page and end_pages are not aligned on a pageblock
 * boundary. If alignment is required, use move_freepages_block()
 */
static int move_freepages(struct zone *zone,
                          struct page *start_page, struct page *end_page,
                          int migratetype, int *num_movable)
{
        struct page *page;
        unsigned int order;
        int pages_moved = 0;

#ifndef CONFIG_HOLES_IN_ZONE
        /*
         * page_zone is not safe to call in this context when
         * CONFIG_HOLES_IN_ZONE is set. This bug check is probably redundant
         * anyway as we check zone boundaries in move_freepages_block().
         * Remove at a later date when no bug reports exist related to
         * grouping pages by mobility
         */
        VM_BUG_ON(pfn_valid(page_to_pfn(start_page)) &&
                  pfn_valid(page_to_pfn(end_page)) &&
                  page_zone(start_page) != page_zone(end_page));
#endif
        for (page = start_page; page <= end_page;) {
                if (!pfn_valid_within(page_to_pfn(page))) {
                        page++;
                        continue;
                }

                /* Make sure we are not inadvertently changing nodes */
                VM_BUG_ON_PAGE(page_to_nid(page) != zone_to_nid(zone), page);

                if (!PageBuddy(page)) {
                        /*
                         * We assume that pages that could be isolated for
                         * migration are movable. But we don't actually try
                         * isolating, as that would be expensive.
                         */
                        if (num_movable &&
                                        (PageLRU(page) || __PageMovable(page)))
                                (*num_movable)++;

                        page++;
                        continue;
                }

                order = page_order(page);
                list_move(&page->lru,
                          &zone->free_area[order].free_list[migratetype]);
                page += 1 << order;
                pages_moved += 1 << order;
        }

        return pages_moved;
}

요청 zone의 시작 페이지부터 끝 페이지까지 모든 free page들에 대해 migrate 타입을 이동시키고, 이동시킨 페이지 수를 반환한다.

  • 코드 라인 21~25에서 시작 페이지부터 끝 페이지까지 순회하며 페이지가 hole 영역인 경우 skip 한다.
  • 코드 라인 30~42에서 버디 시스템에서 free되어 관리되는 페이지가 아니면 skip 한다.
    • 사용 중인 페이지가 lru movable 또는 non-lru movable에 포함된 경우 출력 인자 *num_movable을 증가시킨다.
  • 코드 라인44~48에서 현재 order에 해당하는 free_list의 지정한 migratetype으로 이동 시킨다.
  • 코드 라인 51에서 이동 시킨 페이지 수를 반환한다.

 

다음 그림은 요청 범위에 있는 페이지들을 찾아 버디 시스템의 지정된 migratetype으로 이주시키는 모습을 보여준다.

 

참고

 

Zoned Allocator -4- (Buddy 페이지 해지)

<kernel v5.0>

관련 커널 옵션들

CONFIG_KMEMCHECK

  • x86 아키텍처에서만 지원되며 할당된 kernel memory를 dynamic하게 tracing할 수 있게하는 커널 옵션이다.
  • cmdline에서 “kmemcheck=0” or “kmemcheck=1” early 커널 파라메터를 사용하여 enable/disable 시킬 수 있다.

 

CONFIG_MEMORY_ISOLATION

  • 최근 커널은 CONFIG_{CMA | MEMORY_HOTPLUG | MEMORY_FAILURE} 커널 옵션을 사용하지 않고 CONFIG_MEMORY_ISOLATION 커널 옵션만 사용해도 memory의 isolation 기능을 사용할 수 있도록 하였다.
  • rpi2: 이 커널 옵션을 사용한다.

 

CONFIG_KASAN

  • KASAN(Kernel Address Sanitizer) –  SLUB에 대한 런타임 메모리 디버거
  • 이 기능을 사용하면 최대 3배까지 성능 저하가 발생되고 약 1/8의 free 메모리를 소모한다.
  • 더 좋은 에러 감지를 위해 CONFIG_STACKTRACE 및 cmdline에 “slub_debug=U”를 같이 사용한다.

 

페이지 해지(free)

다음 그림은 free_pages() 함수 이후의 호출 과정을 보여준다.

 

free_pages()

mm/page_alloc.c

void free_pages(unsigned long addr, unsigned int order)
{
        if (addr != 0) {
                VM_BUG_ON(!virt_addr_valid((void *)addr));
                __free_pages(virt_to_page((void *)addr), order);
        }
}

EXPORT_SYMBOL(free_pages);

버디 시스템으로 @addr 주소부터 @order 페이지를 할당 해제한다.

 

__free_pages()

mm/page_alloc.c

void __free_pages(struct page *page, unsigned int order)
{
        if (put_page_testzero(page))
                free_the_page(page, order);
}
EXPORT_SYMBOL(__free_pages);

참조 카운터가 감소시키며, 0이 되면 버디 시스템으로 @order 페이지를 할당 해제한다.

 

free_the_page()

mm/page_alloc.c

static inline void free_the_page(struct page *page, unsigned int order)
{
        if (order == 0)         /* Via pcp? */
                free_unref_page(page);
        else
                __free_pages_ok(page, order);
}

사용이 완료된 2^order 페이지인 경우 free 하되 0-order 페이지의 경우 Per CPU Page Frame Cache로의 free를 시도한다.

  • 0-order 페이지를 pcp의 hot 페이지로 이주시킨다.
  • pcp 이주 시킬 때 migrate 타입에 따라 처리가 달라진다.
    • unmovable, reclaimable, movable 타입인 경우는 그대로 이주된다.
    • reserve, cma 타입인 경우는 movable 타입으로 변환하여 이주된다.
    • isolate 타입인 경우 pcp로 이주되지 않고 다시 buddy로 보낸다.

 

아래 그림은 order 비트에 따라 처리되는 모습을 보여준다.

  • order=0인 single page를 free할 때
    • pcp로 회수되는데 isolate 타입의 경우 페이지를 버디 시스템으로 회수시킨다. 그리고 cma 및 highatomic 타입은 movable 타입의 pcp로 회수시킨다.
    • pcp 이주 시 overflow인 경우 batch 수 만큼 버디 시스템으로 회수시킨다.
  • order가 0이 아닌 multi page를 free할 때
    • pcp를 사용하지 않고 버디 시스템으로 회수시킨다.

 

__free_pages_ok()

mm/page_alloc.c

static void __free_pages_ok(struct page *page, unsigned int order)
{
        unsigned long flags;
        int migratetype;
        unsigned long pfn = page_to_pfn(page);

        if (!free_pages_prepare(page, order, true))
                return;

        migratetype = get_pfnblock_migratetype(page, pfn);
        local_irq_save(flags);
        __count_vm_events(PGFREE, 1 << order);
        free_one_page(page_zone(page), page, pfn, order, migratetype);
        local_irq_restore(flags);
}

사용이 완료된 order 페이지를 버디 시스템으로 회수한다.

  • 코드 라인 7~8에서 할당 해제할 페이지를 체크한다.
  • 코드 라인 12에서 PGFREE 카운터를 페이지 수 만큼 증가시킨다.
  • 코드 라인 13에서 order 페이지를 버디 시스템으로 회수한다.

 

free_one_page()

mm/page_alloc.c

static void free_one_page(struct zone *zone,
                                struct page *page, unsigned long pfn,
                                unsigned int order,
                                int migratetype)
{
        spin_lock(&zone->lock);
        if (unlikely(has_isolate_pageblock(zone) ||
                is_migrate_isolate(migratetype))) {
                migratetype = get_pfnblock_migratetype(page, pfn);
        }
        __free_one_page(page, pfn, zone, order, migratetype);
        spin_unlock(&zone->lock);
}

사용이 완료된 order 페이지를 버디 시스템으로 회수한다.

  • 코드 라인 7~10에서 낮은 확률로 zone에 isolate 타입이 존재하거나 인수로 isolate 타입이 지정된 경우 페이지가 속한 페이지블럭의 migratetype을 사용한다.
  • 코드 라인 11에서 order 페이지를 버디 시스템의 migrate 타입으로 회수한다.

 

__free_one_page()

mm/page_alloc.c -1/2-

/*
 * Freeing function for a buddy system allocator.
 *
 * The concept of a buddy system is to maintain direct-mapped table
 * (containing bit values) for memory blocks of various "orders".
 * The bottom level table contains the map for the smallest allocatable
 * units of memory (here, pages), and each level above it describes
 * pairs of units from the levels below, hence, "buddies".
 * At a high level, all that happens here is marking the table entry
 * at the bottom level available, and propagating the changes upward
 * as necessary, plus some accounting needed to play nicely with other
 * parts of the VM system.
 * At each level, we keep a list of pages, which are heads of continuous
 * free pages of length of (1 << order) and marked with PageBuddy.
 * Page's order is recorded in page_private(page) field.
 * So when we are allocating or freeing one, we can derive the state of the
 * other.  That is, if we allocate a small block, and both were
 * free, the remainder of the region must be split into blocks.
 * If a block is freed, and its buddy is also free, then this
 * triggers coalescing into a block of larger size.
 *
 * -- nyc
 */
static inline void __free_one_page(struct page *page,
                unsigned long pfn,
                struct zone *zone, unsigned int order,
                int migratetype)
{
        unsigned long combined_pfn;
        unsigned long uninitialized_var(buddy_pfn);
        struct page *buddy;
        unsigned int max_order;

        max_order = min_t(unsigned int, MAX_ORDER, pageblock_order + 1);

        VM_BUG_ON(!zone_is_initialized(zone));
        VM_BUG_ON_PAGE(page->flags & PAGE_FLAGS_CHECK_AT_PREP, page);

        VM_BUG_ON(migratetype == -1);
        if (likely(!is_migrate_isolate(migratetype)))
                __mod_zone_freepage_state(zone, 1 << order, migratetype);

        VM_BUG_ON_PAGE(pfn & ((1 << order) - 1), page);
        VM_BUG_ON_PAGE(bad_range(zone, page), page);

continue_merging:
        while (order < max_order - 1) {
                buddy_pfn = __find_buddy_pfn(pfn, order);
                buddy = page + (buddy_pfn - pfn);

                if (!pfn_valid_within(buddy_pfn))
                        goto done_merging;
                if (!page_is_buddy(page, buddy, order))
                        goto done_merging;
                /*
                 * Our buddy is free or it is CONFIG_DEBUG_PAGEALLOC guard page,
                 * merge with it and move up one order.
                 */
                if (page_is_guard(buddy)) {
                        clear_page_guard(zone, buddy, order, migratetype);
                } else {
                        list_del(&buddy->lru);
                        zone->free_area[order].nr_free--;
                        rmv_page_order(buddy);
                }
                combined_pfn = buddy_pfn & pfn;
                page = page + (combined_pfn - pfn);
                pfn = combined_pfn;
                order++;
        }
        if (max_order < MAX_ORDER) {
                /* If we are here, it means order is >= pageblock_order.
                 * We want to prevent merge between freepages on isolate
                 * pageblock and normal pageblock. Without this, pageblock
                 * isolation could cause incorrect freepage or CMA accounting.
                 *
                 * We don't want to hit this code for the more frequent
                 * low-order merging.
                 */
                if (unlikely(has_isolate_pageblock(zone))) {
                        int buddy_mt;

                        buddy_pfn = __find_buddy_pfn(pfn, order);
                        buddy = page + (buddy_pfn - pfn);
                        buddy_mt = get_pageblock_migratetype(buddy);

                        if (migratetype != buddy_mt
                                        && (is_migrate_isolate(migratetype) ||
                                                is_migrate_isolate(buddy_mt)))
                                goto done_merging;
                }
                max_order++;
                goto continue_merging;
        }

사용이 완료된 order 페이지를 버디 시스템의 migrate 타입으로 회수한다.

  • 코드 라인 11에서 최대 combine할 order 값으로 페이지 블럭 order와 max 오더중 작은 값을 사용한다. 루프에사용하므로 1추가된 값이다.
  • 코드 라인 17~18에서 버디 시스템에서 free 페이지 수를 관리할 때 isolate 타입은 free 페이지 수에 추가하지 않는다. 따라서 isolate 타입을 제외한 경우에만 free 페이지 수를 회수될 order 페이지 수만큼 감소시킨다.
  • 코드 라인 23~47에서 continue_merging: 레이블이다. max order 직전까지의 order를 증가시키며 페이지를 combine하기위해 가드 페이지가 아닌 버디 페이지를 찾은 경우 해당 order의 엔트리를 제거한다. 더 이상 combine할 버디 페이지가 없는 경우 루프를 벗어나 done_merging 레이블로 이동한다.
  • 코드 라인 48~71에서 페이지 블럭까지는 다른 migrate 타입을 combine하여도 상관이 없었다. 그러나 isolate 타입은 free 페이지 카운터에 추가되지 않기 때문에 isolation이 진행 중인 존에서는 페이지 블럭 이상의 order에 대해 combine될 buddy 페이지 역시 같은 migrate 타입을 사용하지 않는 경우 combine을 하지 못하게 하였다.

 

mm/page_alloc.c -2/2-

done_merging:
        set_page_order(page, order);

        /*
         * If this is not the largest possible page, check if the buddy
         * of the next-highest order is free. If it is, it's possible
         * that pages are being freed that will coalesce soon. In case,
         * that is happening, add the free page to the tail of the list
         * so it's less likely to be used soon and more likely to be merged
         * as a higher order page
         */
        if ((order < MAX_ORDER-2) && pfn_valid_within(buddy_pfn)) {
                struct page *higher_page, *higher_buddy;
                combined_pfn = buddy_pfn & pfn;
                higher_page = page + (combined_pfn - pfn);
                buddy_pfn = __find_buddy_pfn(combined_pfn, order + 1);
                higher_buddy = higher_page + (buddy_pfn - combined_pfn);
                if (pfn_valid_within(buddy_pfn) &&
                    page_is_buddy(higher_page, higher_buddy, order + 1)) {
                        list_add_tail(&page->lru,
                                &zone->free_area[order].free_list[migratetype]);
                        goto out;
                }
        }

        list_add(&page->lru, &zone->free_area[order].free_list[migratetype]);
out:
        zone->free_area[order].nr_free++;
}
  • 코드 라인 1~2에서 done_merging: 레이블이다. 더 이상 combine할 버디 페이지가 없는 경우에서 진입하였다. free 페이지에 order를 기록한다.
  • 코드 라인 12~24에서 2단 combine될 가능성이 있을 때 cold 방향에 추가한다. 회수할 order 페이지의 1 단계 위 order 페이지의 버디 페이지가 존재하는지 체크한다. 회수된 order 페이지의 짝이되는 버디 페이지가 추후 free될 때, 상위 order에서도 한 번 더 combine될 가능성이 커진다. 따라서 곧바로 할당되어 나가지 못하도록 최대한 더 보존하기 위해 tail(cold) 방향에 페이지를 추가한다.
  • 코드 라인 26~28에서 head(hot) 방향에 페이지를 추가하고, 해당 order의 엔트리 수를 1 증가시킨다.

 

다음 그림은 order 3 페이지 하나가 회수되었을 때 해당 페이지의 짝이되는 버디 페이지가 발견되면 상위 order로 병합(combine)되는 과정을 보여준다. 두 번의 병합(combine)을 통해 order 5에 등록되었다.

 

아래 그림은 free할 페이지가 해당 order slot에 추가될 때 상위 slot의 버디 페이지가 존재하면 2단 combine될 가능성이 높아진다. 따라서 free 페이지의 파편화를 최대한 억제하기 위해 방금 회수한 free 페이지를 cold 방향에 추가하는 모습을 보여준다.

 

has_isolate_pageblock()

include/linux/page-isolation.h

#ifdef CONFIG_MEMORY_ISOLATION
static inline bool has_isolate_pageblock(struct zone *zone)
{
        return zone->nr_isolate_pageblock;
} 
#else
static inline bool has_isolate_pageblock(struct zone *zone)
{
        return false;
}
#endif

지정된 zone에 isolate된 페이지가 존재하는지 여부를 리턴한다.

 


회수한 order 페이지 체크

free_pages_prepare()

mm/page_alloc.c

static __always_inline bool free_pages_prepare(struct page *page,
                                        unsigned int order, bool check_free)
{
        int bad = 0;

        VM_BUG_ON_PAGE(PageTail(page), page);

        trace_mm_page_free(page, order);

        /*
         * Check tail pages before head page information is cleared to
         * avoid checking PageCompound for order-0 pages.
         */
        if (unlikely(order)) {
                bool compound = PageCompound(page);
                int i;

                VM_BUG_ON_PAGE(compound && compound_order(page) != order, page);

                if (compound)
                        ClearPageDoubleMap(page);
                for (i = 1; i < (1 << order); i++) {
                        if (compound)
                                bad += free_tail_pages_check(page, page + i);
                        if (unlikely(free_pages_check(page + i))) {
                                bad++;
                                continue;
                        }
                        (page + i)->flags &= ~PAGE_FLAGS_CHECK_AT_PREP;
                }
        }
        if (PageMappingFlags(page))
                page->mapping = NULL;
        if (memcg_kmem_enabled() && PageKmemcg(page))
                memcg_kmem_uncharge(page, order);
        if (check_free)
                bad += free_pages_check(page);
        if (bad)
                return false;

        page_cpupid_reset_last(page);
        page->flags &= ~PAGE_FLAGS_CHECK_AT_PREP;
        reset_page_owner(page, order);

        if (!PageHighMem(page)) {
                debug_check_no_locks_freed(page_address(page),
                                           PAGE_SIZE << order);
                debug_check_no_obj_freed(page_address(page),
                                           PAGE_SIZE << order);
        }
        arch_free_page(page, order);
        kernel_poison_pages(page, 1 << order, 0);
        kernel_map_pages(page, 1 << order, 0);
        kasan_free_nondeferred_pages(page, order);

        return true;
}

order 페이지를 버디 시스템으로 회수하기 전에 각 페이지의 플래그들을 확인하여 bad 요건이 있는지 모두 확인한다. true=이상 없음, false=bad 페이지

  • 코드 라인 14~21에서 compound 페이지인 경우 두번째 페이지의 PG_double_map을 클리어한다.
  • 코드 라인 22~30에서 두번째 페이지부터 order 페이지의 마지막까지 순회하며 bad 페이지 여부를 확인한다. 그리고 PAGE_FLAGS_CHECK_AT_PREP 플래그를 제거한다.
  • 코드 라인 32~33에서 페이지 매핑을 null로 초기화한다.
  • 코드 라인 34~35에서 kmemcg 페이지인 경우 order 페이지 만큼 uncharge한다.
  • 코드 라인 36~37에서 @check_free가 설정된 경우
  • 코드 라인 38~39에서 bad 페이지가 결과인 경우 false를 반환한다.
  • 코드 라인 41~43에서 페이지에서 cpupid 정보를 리셋하고,  PAGE_FLAGS_CHECK_AT_PREP 플래그도 제거하고, 페이지 owner를 리셋한다.
  • 코드 라인 45~50에서 highmem 페이지인 경우 디버그용 체크를 수행한다.
  • 코드 라인 51에서 아키텍처에서 free 페이지에 대한 검사를 지원하는 경우 수행한다.
  • 코드 라인 52에서 poison 디버그 기능을 사용하는 경우 free 페이지에 대해 poison 처리한다.
  • 코드 라인 53에서 pagealloc 디버그 기능을 사용하는 경우 메모리의 valid 여부를 확인한다.
  • 코드 라인 54에서 kasan 디버그 기능을 사용하는 경우의 처리이다.
  • 코드 라인 56에서 페이지에 이상이 없으므로 true를 반환한다.

 


버디 페이지 확인

__find_buddy_pfn()

mm/internal.h

/*
 * Locate the struct page for both the matching buddy in our
 * pair (buddy1) and the combined O(n+1) page they form (page).
 *
 * 1) Any buddy B1 will have an order O twin B2 which satisfies
 * the following equation:
 *     B2 = B1 ^ (1 << O)
 * For example, if the starting buddy (buddy2) is #8 its order
 * 1 buddy is #10:
 *     B2 = 8 ^ (1 << 1) = 8 ^ 2 = 10
 *
 * 2) Any buddy B will have an order O+1 parent P which
 * satisfies the following equation:
 *     P = B & ~(1 << O)
 *
 * Assumption: *_mem_map is contiguous at least up to MAX_ORDER
 */
static inline unsigned long
__find_buddy_pfn(unsigned long page_pfn, unsigned int order)
{
        return page_pfn ^ (1 << order);
}

요청한 order의 pfn과 짝을 이루는 버디 페이지의 pfn을 반환한다.

  • 예) pfn=0x1000, order=3
    • =0x1008
  • 예) page_idx=0x1008, order=3
    • =0x1000

 

page_is_buddy()

mm/page_alloc.c

/*
 * This function checks whether a page is free && is the buddy
 * we can coalesce a page and its buddy if
 * (a) the buddy is not in a hole (check before calling!) &&
 * (b) the buddy is in the buddy system &&
 * (c) a page and its buddy have the same order &&
 * (d) a page and its buddy are in the same zone.
 *
 * For recording whether a page is in the buddy system, we set PageBuddy.
 * Setting, clearing, and testing PageBuddy is serialized by zone->lock.
 *
 * For recording page's order, we use page_private(page).
 */
static inline int page_is_buddy(struct page *page, struct page *buddy,
                                                        unsigned int order)
{
        if (page_is_guard(buddy) && page_order(buddy) == order) {
                if (page_zone_id(page) != page_zone_id(buddy))
                        return 0;

                VM_BUG_ON_PAGE(page_count(buddy) != 0, buddy);

                return 1;
        }

        if (PageBuddy(buddy) && page_order(buddy) == order) {
                /*
                 * zone check is done late to avoid uselessly
                 * calculating zone/node ids for pages that could
                 * never merge.
                 */
                if (page_zone_id(page) != page_zone_id(buddy))
                        return 0;

                VM_BUG_ON_PAGE(page_count(buddy) != 0, buddy);

                return 1;
        }
        return 0;
}

같은 존에 포함된 @page와 @buddy 페이지가 짝이면 1을 반환하고 그렇지 않으면 0을 반환한다.

  • 코드 라인 4~11에서 @buddy 페이지가 가드 페이지로 사용되고 있고, order도 동일한 경우 1을 반환한다. 단 @page와 @buddy 페이지가 같은 존에 위치하지 않은 경우 0을 반환한다.
  • 코드 라인 13~25에서 @buddy 페이지가 PG_buddy 플래그가 설정되어 있고, order도 동일한 경우 1을 반환한다. 단 @page와 @buddy 페이지가 같은 존에 위치하지 않은 경우 0을 반환한다.
  • 코드 라인 26에서 @page와 @buddy 페이지가 짝이 아니므로 0을 반환한다.

 


order 값 삭제

rmv_page_order()

mm/page_alloc.c

static inline void rmv_page_order(struct page *page)
{
        __ClearPageBuddy(page);
        set_page_private(page, 0);
}

페이지의 PG_buddy 플래그 클리어하고 order bit를 나타내는 페이지의 private에 0을 대입한다.

 

include/linux/mm.h

#define set_page_private(page, v)       ((page)->private = (v))

 

참고

 

 

mem_init()

<kernel v5.0>

free 메모리를 버디 시스템으로 이관

lowmem 및 highmem에 대한 모든 free 메모리에 대해 memblock 영역에서 버디 메모리 할당자로 이관한다.

 

mem_init() – ARM64

arch/arm64/mm/init.c

/*
 * mem_init() marks the free areas in the mem_map and tells us how much memory
 * is free.  This is done after various parts of the system have claimed their
 * memory after the kernel image.
 */
void __init mem_init(void)
{
        if (swiotlb_force == SWIOTLB_FORCE ||
            max_pfn > (arm64_dma_phys_limit >> PAGE_SHIFT))
                swiotlb_init(1);
        else
                swiotlb_force = SWIOTLB_NO_FORCE;

        set_max_mapnr(pfn_to_page(max_pfn) - mem_map);

#ifndef CONFIG_SPARSEMEM_VMEMMAP
        free_unused_memmap();
#endif
        /* this will put all unused low memory onto the freelists */
        memblock_free_all();

        kexec_reserve_crashkres_pages();

        mem_init_print_info(NULL);

        /*
         * Check boundaries twice: Some fundamental inconsistencies can be
         * detected at build time already.
         */
#ifdef CONFIG_COMPAT
        BUILD_BUG_ON(TASK_SIZE_32 > DEFAULT_MAP_WINDOW_64);
#endif

        if (PAGE_SIZE >= 16384 && get_num_physpages() <= 128) {
                extern int sysctl_overcommit_memory;
                /*
                 * On a machine this small we won't get anywhere without
                 * overcommit, so turn it on by default.
                 */
                sysctl_overcommit_memory = OVERCOMMIT_ALWAYS;
        }
}

기존 memblock에서 reserve한 공간을 제외한 빈 공간들을 모두 버디 시스템에 등록하여 버디 시스템을 사용할 준비를 수행한다.

  • 코드 라인 3~7에서 “swiotlb=force” 커널 파라미터가 지정되거나 max_pfn이 dma 영역을 초과한 경우 sw 방식의 iotlb를 사용을 위해 I/O TLB용 버퍼 메모리를 할당한다.
    • arm64 디폴트 커널은 CONFIG_SWIOTLB를 사용한다.
    • “swiotlb=<force|noforce>” early 파라미터로 설정된다.
    • 예) ” software IO TLB [mem 0xef400000-0xf3400000] (64MB) mapped at [ffffffc0ef400000-ffffffc0f33fffff]”
  • 코드 라인 9에서 싱글 노드를 사용하는 시스템인 경우에만 전역 max_mapnr에 mem_map[] 배열에 대한 인덱스 번호를 저장한다.
  • 코드 라인 11~13에서 vmemmap을 사용하지 않는 경우 sparse 메모리 모델 또는 discontiguous 메모리 모델에서 메모리 사이의 사용되지 않는 공간이 상당히 클 수 있다. 따라서 이에 대해 메모리 낭비가 발생하지 않도록 미사용 공간에 대한 mem_map[ ]을 페이지 단위로 reserved memblock에서 free시킨다. x86과 ARM64 등에서는 CONFIG_SPARSEMEM_VMEMMAP의 사용이 가능하다.
  • 코드 라인 15에서 free memblock 영역에 대해 모두 버디 메모리 할당자의 빈 페이지로 이관 등록한다. memblock을 더 이상 사용하지 않는 경우 reserved & memory memblock 관리 배열까지 버디로 free시킨다.
    • ARM32의 경우는 추가 코드로 highmem 메모리 영역에 대해 버디 시스템으로 이관하는 free_highpage( ) 함수를 호출하는 코드가 있지만, ARM64에서는 highmem이 없으므로 해당 코드를 호출하지 않는다.
  • 코드 라인 19에서 메모리 초기화 정보를 출력한다.
    • 예) ” Memory: 2611360K/3145728K available (16060K kernel code, 8910K rwdata, 10300K rodata, 1664K init, 9117K bss, 501600K reserved, 32768K cma-reserved)”
  • 코드 라인 29~36에서 페이지 사이즈가 16K 이상이면서 물리 페이지가 128개 이하인 경우에 한해 메모리 할당 시 오버 커밋을 허용하게 한다.

 

다음 그림은 vmemmap을 사용하는 ARM64 시스템에서 mem_init() 함수를 통해 free 메모리를  memblock 할당자에서 버디 메모리 할당자로 전환하는 모습을 보여준다.

 

mem_init() – ARM32

arch/arm/mm/init.c

/*
 * mem_init() marks the free areas in the mem_map and tells us how much
 * memory is free.  This is done after various parts of the system have
 * claimed their memory after the kernel image.
 */
void __init mem_init(void)
{
#ifdef CONFIG_HAVE_TCM
        /* These pointers are filled in on TCM detection */
        extern u32 dtcm_end;
        extern u32 itcm_end;
#endif

        set_max_mapnr(pfn_to_page(max_pfn) - mem_map);

        /* this will put all unused low memory onto the freelists */
        free_unused_memmap();
        memblock_free_all();

#ifdef CONFIG_SA1111
        /* now that our DMA memory is actually so designated, we can free it */
        free_reserved_area(__va(PHYS_OFFSET), swapper_pg_dir, -1, NULL);
#endif

        free_highpages();

        mem_init_print_info(NULL);

기존 memblock에서 reserve한 공간을 제외한 빈 공간들을 모두 버디 시스템에 등록하여 버디 시스템을 사용할 준비를 수행한다.

  • 코드 라인 9에서 싱글 노드 시스템에서만 전역 max_mapnr에 mem_map[] 배열에 대한 인덱스 번호를 저장한다.
  • 코드 라인 12에서 Sparse 메모리 모델 또는 Discontig 메모리 모델에서 메모리 사이의 사용되지 않는 공간이 상당히 클 수 있다. 따라서 이에 대해 메모리 낭비가 발생하지 않도록 미사용 공간에 대한 mem_map[]을 페이지 단위로 reserve memblock에서 free 시킨다.
  • 코드 라인 13에서 free memblock 영역에 대해 모두 Buddy memory allocator의 빈 페이지로 이관 등록한다.
    • memblock을 더 이상 사용하지 않는 경우 reserved & memory memblock 관리배열까지 Buddy로 free 시킨다.
  • 코드 라인 20에서 highmem 메모리 영역을 모두 Buddy memory allocator의 free 페이지로 이관 등록한다.

 

#define MLK(b, t) b, t, ((t) - (b)) >> 10
#define MLM(b, t) b, t, ((t) - (b)) >> 20
#define MLK_ROUNDUP(b, t) b, t, DIV_ROUND_UP(((t) - (b)), SZ_1K)

        pr_notice("Virtual kernel memory layout:\n"
                        "    vector  : 0x%08lx - 0x%08lx   (%4ld kB)\n"
#ifdef CONFIG_HAVE_TCM
                        "    DTCM    : 0x%08lx - 0x%08lx   (%4ld kB)\n"
                        "    ITCM    : 0x%08lx - 0x%08lx   (%4ld kB)\n"
#endif
                        "    fixmap  : 0x%08lx - 0x%08lx   (%4ld kB)\n"
                        "    vmalloc : 0x%08lx - 0x%08lx   (%4ld MB)\n"
                        "    lowmem  : 0x%08lx - 0x%08lx   (%4ld MB)\n"
#ifdef CONFIG_HIGHMEM
                        "    pkmap   : 0x%08lx - 0x%08lx   (%4ld MB)\n"
#endif
#ifdef CONFIG_MODULES
                        "    modules : 0x%08lx - 0x%08lx   (%4ld MB)\n"
#endif
                        "      .text : 0x%p" " - 0x%p" "   (%4td kB)\n"
                        "      .init : 0x%p" " - 0x%p" "   (%4td kB)\n"
                        "      .data : 0x%p" " - 0x%p" "   (%4td kB)\n"
                        "       .bss : 0x%p" " - 0x%p" "   (%4td kB)\n",

                        MLK(VECTORS_BASE, VECTORS_BASE + PAGE_SIZE),
#ifdef CONFIG_HAVE_TCM
                        MLK(DTCM_OFFSET, (unsigned long) dtcm_end),
                        MLK(ITCM_OFFSET, (unsigned long) itcm_end),
#endif
                        MLK(FIXADDR_START, FIXADDR_END),
                        MLM(VMALLOC_START, VMALLOC_END),
                        MLM(PAGE_OFFSET, (unsigned long)high_memory),
#ifdef CONFIG_HIGHMEM
                        MLM(PKMAP_BASE, (PKMAP_BASE) + (LAST_PKMAP) *
                                (PAGE_SIZE)),
#endif
#ifdef CONFIG_MODULES
                        MLM(MODULES_VADDR, MODULES_END),
#endif

                        MLK_ROUNDUP(_text, _etext),
                        MLK_ROUNDUP(__init_begin, __init_end),
                        MLK_ROUNDUP(_sdata, _edata),
                        MLK_ROUNDUP(__bss_start, __bss_stop));

#undef MLK
#undef MLM
#undef MLK_ROUNDUP

        /*
         * Check boundaries twice: Some fundamental inconsistencies can
         * be detected at build time already.
         */
#ifdef CONFIG_MMU
        BUILD_BUG_ON(TASK_SIZE                          > MODULES_VADDR);
        BUG_ON(TASK_SIZE                                > MODULES_VADDR);
#endif

#ifdef CONFIG_HIGHMEM
        BUILD_BUG_ON(PKMAP_BASE + LAST_PKMAP * PAGE_SIZE > PAGE_OFFSET);
        BUG_ON(PKMAP_BASE + LAST_PKMAP * PAGE_SIZE      > PAGE_OFFSET);
#endif
}

가상 메모리 레이아웃 정보를 출력한다.

  • 예) 다음과 같은 출력 정보를 보여준다.
Virtual kernel memory layout:
    modules : 0xffffff8000000000 - 0xffffff8008000000   (   128 MB)
    vmalloc : 0xffffff8008000000 - 0xffffffbdbfff0000   (   246 GB)
      .init : 0xffffff8009040000 - 0xffffff8009160000   (  1152 KB)
      .text : 0xffffff8008080000 - 0xffffff8008bf0000   ( 11712 KB)
    .rodata : 0xffffff8008bf0000 - 0xffffff8009040000   (  4416 KB)
      .data : 0xffffff8009160000 - 0xffffff80092f6008   (  1625 KB)
    vmemmap : 0xffffffbdc0000000 - 0xffffffbfc0000000   (     8 GB maximum)
              0xffffffbdc0008000 - 0xffffffbdc3e00000   (    61 MB actual)
    fixed   : 0xffffffbffe7fd000 - 0xffffffbffec00000   (  4108 KB)
    PCI I/O : 0xffffffbffee00000 - 0xffffffbfffe00000   (    16 MB)
    memory  : 0xffffffc000200000 - 0xffffffc0f8000000   (  3966 MB)

 

다음 그림은 early 메모리 할당자인 memblock 에서 버디 메모리 할당자로 free 메모리 관리가 전환되는 모습을 보여준다.

 


미사용 memmap 할당 해제

free_unused_memmap() – ARM32

arch/arm/mm/init.c

/*
 * The mem_map array can get very big.  Free the unused area of the memory map.
 */
static void __init free_unused_memmap(void)
{
        unsigned long start, prev_end = 0;
        struct memblock_region *reg;

        /*
         * This relies on each bank being in address order.
         * The banks are sorted previously in bootmem_init().
         */
        for_each_memblock(memory, reg) {
                start = memblock_region_memory_base_pfn(reg);

#ifdef CONFIG_SPARSEMEM
                /*
                 * Take care not to free memmap entries that don't exist
                 * due to SPARSEMEM sections which aren't present.
                 */
                start = min(start,
                                 ALIGN(prev_end, PAGES_PER_SECTION));
#else
                /*
                 * Align down here since the VM subsystem insists that the
                 * memmap entries are valid from the bank start aligned to
                 * MAX_ORDER_NR_PAGES.
                 */
                start = round_down(start, MAX_ORDER_NR_PAGES);
#endif
                /*
                 * If we had a previous bank, and there is a space
                 * between the current bank and the previous, free it.
                 */
                if (prev_end && prev_end < start)
                        free_memmap(prev_end, start);

                /*
                 * Align up here since the VM subsystem insists that the
                 * memmap entries are valid from the bank end aligned to
                 * MAX_ORDER_NR_PAGES.
                 */
                prev_end = ALIGN(memblock_region_memory_end_pfn(reg),
                                 MAX_ORDER_NR_PAGES);
        }

#ifdef CONFIG_SPARSEMEM
        if (!IS_ALIGNED(prev_end, PAGES_PER_SECTION))
                free_memmap(prev_end,
                            ALIGN(prev_end, PAGES_PER_SECTION));
#endif
}

mem_map[]에서 메모리 영역에 해당하지 않는 부분만 memblock에서 페이지 단위로 free하는데 메모리 모델에 따라 다음과 같이 동작한다.

  • Sparse 메모리 모델 (O)
    • 메모리와 메모리 사이의 hole에 대해서는 섹션이 구성되어 있지 않아 관련 mem_map[] 배열이 없어서 삭제할 mem_map[]이 없다.
    • 하나의 섹션을 가득 채우지 못한 메모리의 경우 남는 공간만큼의 mem_map[] 배열에 페이지 단위로 free 시킬 수 있다.
  • Discontig 메모리 모델 (X)
    • hole에 해당하는 mem_map[] 배열을 페이지 단위로 free시킬 수 있으나 arm에서 Discontig 메모리 모델을 사용하지 않게 되어 해당 사항 없다.
  • Flat 메모리 모델 (X)
    • hole이 없어 해당 사항 없다.

 

  • 코드 라인 10~11에서 memory memblock 엔트리 수 만큼 순회하며 시작 pfn 값을 알아온다.
    • PFN_UP(reg->base);
      • 물리 시작 주소에 PAGE_SIZE 단위로 round up한 후 pfn으로 변환
  • 코드 라인 18~19에서 Sparse memory model을 사용하는 경우 물리 시작 주소가 이전 기억해둔 memblock의 끝 주소를 섹션당 페이지 수(PAGES_PER_SECTION) 단위로 round up한 수를 초과하지 않게 한다.
  • 코드 라인 26에서 Sparse memory model이 아닌 경우 물리 시작 주소를 MAX_ORDER_NR_PAGES 단위로 round down 한다.
    • MAX_ORDER_NR_PAGES(1024)
  • 코드 라인 32~33에서 hole이 있는 경우 hole 만큼의 page를 관리하는 mem_map[]을 free 시킨다.
  • 코드 라인 40~41에서 memblock의 끝 주소를 round down하여 pfn으로 변환한 수를 MAX_ORDER_NR_PAGES 단위로 round up한 pfn 값
  • 코드 라인 45~47에서 Sparsemem에서 마지막 memory memblock의 끝이 PAGES_PER_SECTION 단위로 align되지 않은 경우 해당 영역에 해당하는 mem_map[]을 free 시킨다.

 

아래 그림은 두 개의 메모리 사이에 hole을 발생시켰고 각각의 섹션에 메모리가 일부만 채워진 경우 남는 공간에 대한 mem_map[] 배열에서 사용되지 않는 공간을 페이지 단위로 free 하는 것을  보여준다.

free_unused_memmap-1a

 

free_memmap()

arch/arm/mm/init.c

static inline void
free_memmap(unsigned long start_pfn, unsigned long end_pfn)
{
        struct page *start_pg, *end_pg;
        phys_addr_t pg, pgend;

        /*
         * Convert start_pfn/end_pfn to a struct page pointer.
         */
        start_pg = pfn_to_page(start_pfn - 1) + 1;
        end_pg = pfn_to_page(end_pfn - 1) + 1;

        /*
         * Convert to physical addresses, and
         * round start upwards and end downwards.
         */
        pg = PAGE_ALIGN(__pa(start_pg));
        pgend = __pa(end_pg) & PAGE_MASK;

        /*
         * If there are free pages between these,
         * free the section of the memmap array.
         */
        if (pg < pgend)
                memblock_free_early(pg, pgend - pg);
}

start_pfn ~ end_pfn에 해당하는 mem_map[] 영역을 reserve memblock에서 free(remove)한다.

  • start_pg = pfn_to_page(start_pfn – 1) + 1;
    • start_pfn으로 mem_map[]의 page 구조체 주소를 알아온다.
    • 인수로 주어진 start_pfn은 sparse memory의 경우 커널이 인식하지 못하는 unused 또는 hole 영역일 수 있기 때문에 이 주소를 사용하여 page 주소를 알아오려는 경우 잘못된 값을 알아올 수 있어서 패치를 하였다.
    • 참고: ARM: 5747/1: Fix the start_pg value in free_memmap()
  • end_pg = pfn_to_page(end_pfn – 1) + 1;
    • end_pfn으로 mem_map[]의 page 구조체 주소를 알아온다.
    • 역시 인수로 주어진 end_pfn은 sparse memory의 경우 커널이 인식하지 못하는 unused 또는 hole 영역일 수 있기 때문에 이 주소를 사용하여 page 주소를 알아오려는 경우 잘못된 값을 알아올 수 있어서 패치를 하였다
    • 참고: ARM: 6890/1: memmap: only free allocated memmap entries when using SPARSEMEM
  • pg = PAGE_ALIGN(__pa(start_pg));
    • start_pg를 물리주소로 변환하고 페이지 사이즈 단위로 round up 한다.
  • pgend = __pa(end_pg) & PAGE_MASK;
    • end_pg를 물리주소로 변환하고 페이지 사이즈 단위로 round down 한다.
  •  if (pg < pgend) memblock_free_early(pg, pgend – pg);
    • reserve memblock 영역에 등록된 mem_map[]의 unused 공간을 reserve memblock에서 free(remove) 한다.

 


free 메모리를 버디 시스템으로 이관

memblock_free_all()

mm/memblock.c

/**
 * memblock_free_all - release free pages to the buddy allocator
 *
 * Return: the number of pages actually released.
 */
unsigned long __init memblock_free_all(void)
{
        unsigned long pages;

        reset_all_zones_managed_pages();

        pages = free_low_memory_core_early();
        totalram_pages_add(pages);

        return pages;
}

memblock을 스캔하여 free 메모리 영역을 찾아 버디 시스템에 이관하는 과정을 알아본다. 이 함수에서는 모든 free lowmem 영역을 버디 시스템의 free_list에 이관 등록한다. 핫플러그 메모리를 사용하지 않는 대부분의 시스템은 버디 시스템이 활성화되면 memblock을 더 이상 사용하지 않게 되는데, 이때 reserved & memory memblock의 관리 배열을 더 이상 사용하지 않으므로 이에 대한 영역도 버디 시스템에 이관한다.

  • 코드 라인 5에서 모든 온라인 노드의 각 zone->managed_pages를 0으로 초기화한다. managed_pages 필드는 존에서 사용 가능한 free 페이지 수를 나타낸다.
  • 코드 라인 7에서 모든 free lowmem 영역들을 버디 시스템의 free_list에 이관 등록한다. memblock을 더 이상 사용하지 않는 경우 reserved & memory memblock 관리 배열도 버디 시스템의 free_list에 이관 등록한다.
  • 코드 라인 8에서 free된 페이지들을 전역 totalram_pages에 추가한다.

 

reset_all_zones_managed_pages()

mm/memblock.c

void __init reset_all_zones_managed_pages(void)
{
        struct pglist_data *pgdat;

        if (reset_managed_pages_done)
                return;

        for_each_online_pgdat(pgdat)
                reset_node_managed_pages(pgdat);

        reset_managed_pages_done = 1;
}

모든 online 노드의 각 zone->managed_pages를 0으로 초기화한다.

 

reset_node_managed_pages()

mm/memblock.c

void reset_node_managed_pages(pg_data_t *pgdat)
{
        struct zone *z;

        for (z = pgdat->node_zones; z < pgdat->node_zones + MAX_NR_ZONES; z++)
                atomic_long_set(&z->managed_pages, 0);
}

해당 노드의 모든 zone->managed_pages를 0으로 초기화한다.

 

커널 초기화 과정 이후 필요 없는 메모리 해제하기

free_low_memory_core_early()

mm/memblock.c

static unsigned long __init free_low_memory_core_early(void)
{
        unsigned long count = 0;
        phys_addr_t start, end;
        u64 i;

        memblock_clear_hotplug(0, -1);

        for_each_reserved_mem_region(i, &start, &end)
                reserve_bootmem_region(start, end);

        /*
         * We need to use NUMA_NO_NODE instead of NODE_DATA(0)->node_id
         *  because in some case like Node0 doesn't have RAM installed
         *  low ram will be on Node1
         */
        for_each_free_mem_range(i, NUMA_NO_NODE, MEMBLOCK_NONE, &start, &end,
                                NULL)
                count += __free_memory_core(start, end);

        return count;
}

모든 free lowmem 영역을 모두 버디 시스템의 free_list에 이관 등록한다. CONFIG_ARCH_DISCARD_MEMBLOCK 커널 옵션을 사용하는 경우 reserved & memory memblock의 관리 배열을 더 이상 사용하지 않으므로 이에 대한 영역도 버디 시스템에 이관한다.

  • 코드 라인 7에서 전체 memory memblock 영역에 대해 MEMBLOCK_HOTPLUG 비트를 클리어(clear)한다.
  • 코드 라인 9~10에서 전체 reserved memblock 영역의 모든 page 구조체에서 PG_reserved 비트를 설정하여 메모리가 이미 reserve되어 사용 중임을 마크한다.
  • 코드 라인 17~19에서 모든 free lowmem 영역을 버디 시스템의 free_list에 이관 등록한다.

 

reserve_bootmem_region()

mm/page_alloc.c

/*
 * Initialised pages do not have PageReserved set. This function is
 * called for each range allocated by the bootmem allocator and
 * marks the pages PageReserved. The remaining valid pages are later
 * sent to the buddy page allocator.
 */
void __meminit reserve_bootmem_region(phys_addr_t start, phys_addr_t end)
{
        unsigned long start_pfn = PFN_DOWN(start);
        unsigned long end_pfn = PFN_UP(end);

        for (; start_pfn < end_pfn; start_pfn++) {
                if (pfn_valid(start_pfn)) {
                        struct page *page = pfn_to_page(start_pfn);

                        init_reserved_page(start_pfn);

                        /* Avoid false-positive PageTail() */
                        INIT_LIST_HEAD(&page->lru);

                        /*
                         * no need for atomic set_bit because the struct
                         * page is not visible yet so nobody should
                         * access it yet.
                         */
                        __SetPageReserved(page);
                }
        }
}

요청 범위의 pfn 모두에 대해 page 구조체에서 PG_reserved 플래그를 설정한다. 추후 PG_reserved 플래그가 설정되지 않는 범위의 유효 페이지들에 대해서는 모두 버디 시스템으로 이관된다.

 

__free_memory_core()

mm/memblock.c

static unsigned long __init __free_memory_core(phys_addr_t start,
                                 phys_addr_t end)
{
        unsigned long start_pfn = PFN_UP(start);
        unsigned long end_pfn = min_t(unsigned long,
                                      PFN_DOWN(end), max_low_pfn);

        if (start_pfn > end_pfn)
                return 0;

        __free_pages_memory(start_pfn, end_pfn);

        return end_pfn - start_pfn;
}

페이지 단위 올림 정렬한 시작 주소(start) ~ 페이지 내림 정렬한 끝 주소(end)까지의 pfn에 대해 해당 영역을 버디 시스템의 free_list에 추가한다.

  • 코드 라인 4~6에서 시작 물리 주소와 끝 물리 주소로 pfn 값을 구한다.
  • 코드 라인 11에서 페이지를 해제하여 버디 시스템으로 보낸다.
  • 코드 라인 해제한 페이지 수를 리턴한다.

 

__free_pages_memory()

mm/memblock.c

static void __init __free_pages_memory(unsigned long start, unsigned long end)
{
        int order;

        while (start < end) {
                order = min(MAX_ORDER - 1UL, __ffs(start));

                while (start + (1UL << order) > end)
                        order--;

                memblock_free_pages(pfn_to_page(start), start, order);

                start += (1UL << order);
        }
}

free 요청한 페이지들에 대해 2order 단위로 잘라서 버디 시스템에 free 요청한다

  • 코드 라인 5~6에서 start~end pfn까지 순회하며 start pfn 값으로 처음 order를 결정한다.
    • 2^n 단위로 잘라낸다.
    • __ffs()는 lsb -> msb 순으로 1로 설정된 비트를 찾는다. 못 찾은 경우에는 -1을 리턴한다. 예를 들어, start가 0x10003인 경우 order는 0이 된다.
  • 코드 라인 8~9에서 start pfn에 2^order를 더한 페이지 번호가 end를 초과한다면 order를 1씩 감소시킨다.
  • 코드 라인 2^order 페이지 공간을 버디에 free시키고 start += 2^order를 한 후 다시 루프를 수행한다.

 

아래 그림은 0x10003 ~ 0x10013 pfn에 대해 버디에 free 할 때 5 조각으로 나누어 처리하는 과정을 보여준다.

__free_pages_memory-1

 


오더 페이지 해제하기

memblock_free_pages()

mm/page_alloc.c

void __init memblock_free_pages(struct page *page, unsigned long pfn,
                                                        unsigned int order)
{
        if (early_page_uninitialised(pfn))
                return;
        return __free_pages_boot_core(page, order);
}

요청한 2^order 페이지들을 해제한다.

  • 코드 라인 4~5에서 pfn에 해당하는 page 구조체가 초기화되지 않았다면 함수를 종료한다. page 구조체는 보통 부팅 초반에 싱글 스레드에 의해 초기화된다. 하지만 대용량 메모리를 가진 시스템에서는 이로 인해 부팅 속도 지연이 발생하게 된다. 따라서 이런 문제를 막기 위해  CONFIG_DEFERRED_STRUCT_PAGE_INIT 커널 설정을 사용하면 꼭 필요한 page 구조체만 초기화하고 나머지 page 구조체의 초기화는 뒤로 미룰 수가 있다.
  • 코드 라인 6에서 요청한 2^order 페이지를 버디 시스템으로 회수한다.

 

__free_pages_boot_core()

mm/page_alloc.c

void __init __free_pages_boot_core(struct page *page, unsigned int order)
{
        unsigned int nr_pages = 1 << order;
        struct page *p = page;
        unsigned int loop;

        prefetchw(p);
        for (loop = 0; loop < (nr_pages - 1); loop++, p++) {
                prefetchw(p + 1);
                __ClearPageReserved(p);
                set_page_count(p, 0);
        }
        __ClearPageReserved(p);
        set_page_count(p, 0);

        atomic_long_add(nr_pages, &page_zone(page)->managed_pages);
        set_page_refcounted(page);
        __free_pages(page, order);
}

free시킬 페이지에 대해 PG_reserved 비트를 클리어하고 페이지가 참조되지 않음으로 설정(_count = 0)한다. managed_pages에 free시킬 페이지만큼 추가하고 첫 페이지를 참조 설정(_count = 1)한 후 버디 시스템으로 반환한다.

  • 코드 라인 7에서 요청한 시작 page 구조체를 캐시에 미리 로드한다.
  • 코드 라인 8~9에서 처리할 페이지들에 대해 마지막 페이지를 제외하고 루프를 돌며 다음 page 구조체를 캐시에 미리 로드한다. page 구조체 p를 조작하기 전에 pregetchw(p + 1)을 호출하여 다음 page 구조체마저 캐시 라인에 미리 로드하면 그전 page의 구조체 전체에 대해 어토믹 연산 작업 수행 시 한 번에 성공할 확률이 높아지므로 성능 향상에 도움이 된다.
    • page 구조체 p에 해당하는 데이터를 캐시에 로드하고 조작할 p->_count는 p 주소와 12바이트가 떨어져 있어서 p를 캐시에 미리 로드해도 같은 캐시 라인에 p->_count 영역이 로드되어 있지 않을 확률이 있다. 어차피 높은 확률로 다음 페이지 구조체 데이터도 필요하므로 미리 로드를 해놓으면 p->_count에 접근 시 어토믹 연산이 한 번에 성공할 확률이 더 높아져 성능에 도움이 될 수 있다.
    • ARM64에서 L1 데이터 캐시는 최소 16바이트부터 존재한다. 참고로 커널 v4.7부터는 _count 필드가 _refcount로 이름이 변경되었다.
  • 코드 라인 10~11에서 page 구조체의 플래그에 Reserved 비트를 클리어하고 참조 카운터를 0으로 만든다.
  • 코드 라인 13~14에서 마지막 페이지를 제외하고 루프를 돌았으므로 마지막 페이지에 대해 Reserved 비트를 클리어하고 참조 카운터를 0으로 만든다.
  • 코드 라인 16~18에서 존의 managed_pages에 버디 시스템으로 회수한 페이지 수를 더하고, 대표 페이지의 참조 카운터를 1로 설정하고, 페이지 회수 API를 호출한다.
    • 페이지의 참조 카운터를 0으로 하지 않고 1로 설정한 이유는 _ _free_pages( ) 함수를 분석해보면 금방 알 수 있다. _ _free_pages( ) 함수는 커널에서 페이지 회수 시에 사용하며, 많이 사용되는 API로 이 함수를 호출할 때 참조 카운터를 1 감소시키는 코드가 내부에 있는데 이 값이 0이 되는 순간에 실제 요청한 페이지들을 버디 시스템으로 회수하게 한다.

 

다음 그림과 같이 prefetcw()를 사용하여 next page 구조체를 미리 prefetch하는 이유를 확인해보자.

 


highmem 페이지들을 버디 시스템으로 이관

free_highpages-1

 

free_highpages() – ARM32

arch/arm/mm/init.c

static void __init free_highpages(void)
{
#ifdef CONFIG_HIGHMEM
        unsigned long max_low = max_low_pfn;
        struct memblock_region *mem, *res;

        /* set highmem page free */
        for_each_memblock(memory, mem) {
                unsigned long start = memblock_region_memory_base_pfn(mem);
                unsigned long end = memblock_region_memory_end_pfn(mem);

                /* Ignore complete lowmem entries */
                if (end <= max_low)
                        continue;

                /* Truncate partial highmem entries */
                if (start < max_low)
                        start = max_low;

                /* Find and exclude any reserved regions */
                for_each_memblock(reserved, res) {
                        unsigned long res_start, res_end;

                        res_start = memblock_region_reserved_base_pfn(res);
                        res_end = memblock_region_reserved_end_pfn(res);

                        if (res_end < start)
                                continue;
                        if (res_start < start)
                                res_start = start;
                        if (res_start > end)
                                res_start = end;
                        if (res_end > end)
                                res_end = end;
                        if (res_start != start)
                                free_area_high(start, res_start);
                        start = res_end;
                        if (start == end)
                                break;
                }

                /* And now free anything which remains */
                if (start < end)
                        free_area_high(start, end);
        }
#endif
}

memblock에서 highmem에 해당하는 free 영역을 버디 시스템에 이관한다.

  • 코드 라인 8~10에서 memory memblock 수 만큼 순회하며 memblock 영역의 시작과 끝 pfn을 구한다.
    • 물리 시작 주소에 PAGE_SIZE 단위로 round up한 후 pfn으로 변환
    • 물리 끝 주소에 round down된 값을 pfn으로 변환
  • 코드 라인 13~14에서 memblock 엔트리가 lowmem인 경우는 skip 한다.
  • 코드 라인 16~17에서 노매핑 설정된 블럭은 skip 한다.
  • 코드 라인 20~21에서 memblock 엔트리가 lowmem/highmem 경계에 걸친 경우 lowmem 영역을 skip 한다.
  • 코드 라인 24~47에서 reserved 영역을 제외한 free 공간을 버디 시스템으로 이관한다.

 

free_area_high()

arch/arm/mm/init.c

#ifdef CONFIG_HIGHMEM
static inline void free_area_high(unsigned long pfn, unsigned long end)
{
        for (; pfn < end; pfn++)
                free_highmem_page(pfn_to_page(pfn));
}
#endif

pfn ~ end 까지 각각의 pfn에 해당하는 highmem 페이지를 버디 시스템에서 free 처리 한다.

 

free_highmem_page()

mm/page_alloc.c

#ifdef  CONFIG_HIGHMEM
/*
 * Free a highmem page into the buddy system, adjusting totalhigh_pages
 * and totalram_pages.
 */
void free_highmem_page(struct page *page) 
{
        __free_reserved_page(page);
        totalram_pages++;
        page_zone(page)->managed_pages++;
        totalhigh_pages++;
}
#endif

해당 highmem 페이지에서 reserved 플래그를 clear하고 _count=1로 대입한 후 버디 시스템에서 free 처리 한다. 관련 stat들 또한 증가시킨다.

 

__free_reserved_page()

include/linux/mm.h

/* Free the reserved page into the buddy system, so it gets managed. */
static inline void __free_reserved_page(struct page *page)
{
        ClearPageReserved(page);
        init_page_count(page);
        __free_page(page);
}

해당 highmem 페이지에서 reserved 플래그를 clear하고 _count=1로 대입한 후 버디 시스템에서 free 처리 한다.

 

get_num_physpages()

include/linux/mm.h

static inline unsigned long get_num_physpages(void)
{
        int nid;
        unsigned long phys_pages = 0;

        for_each_online_node(nid) 
                phys_pages += node_present_pages(nid);  

        return phys_pages;
}

전체 노드의 present(hole 제외) 페이지 수를 알아온다.

 

include/linux/mmzone.h

#define node_present_pages(nid) (NODE_DATA(nid)->node_present_pages)

 

참고