2021-05-12 14:32:11
Linux記憶體管理機制簡單分析
本文對Linux記憶體管理機制做一個簡單的分析,試圖讓你快速理解Linux一些記憶體管理的概念並有效的利用一些管理方法。
NUMA
Linux 2.6開始支援NUMA( Non-Uniform Memory Access )記憶體管理模式。在多個CPU的系統中,記憶體按CPU劃分為不同的Node,每個CPU掛一個Node,其存取本地Node比存取其他CPU上的Node速度要快很多。
通過numactl -H檢視NUMA硬體資訊,可以看到2個node的大小和對應的CPU核,以及CPU存取node的distances。如下所示CPU存取遠端node的distances是本地node的2倍多。
[root@localhost ~]# numactl -H
available: 2 nodes (0-1)
node 0 cpus: 0 1 2 3 4 5 6 7 16 17 18 19 20 21 22 23
node 0 size: 15870 MB
node 0 free: 13780 MB
node 1 cpus: 8 9 10 11 12 13 14 15 24 25 26 27 28 29 30 31
node 1 size: 16384 MB
node 1 free: 15542 MB
node distances:
node 0 1
0: 10 21
1: 21 10
通過numastat檢視NUMA的統計資訊,包括記憶體分配的命中次數、未命中次數、本地分配次數和遠端分配次數等。
[root@localhost ~]# numastat
node0 node1
numa_hit 2351854045 3021228076
numa_miss 22736854 2976885
numa_foreign 2976885 22736854
interleave_hit 14144 14100
local_node 2351844760 3021220020
other_node 22746139 2984941
Zone
Node下面劃分為一個或多個Zone,為啥要有Zone,兩個原因:1.DMA裝置能夠存取的記憶體範圍有限(ISA裝置只能存取16MB);2.x86-32bit系統地址空間有限(32位元最多只能4GB),為了使用更大記憶體,需要使用HIGHMEM機制。
ZONE_DMA
地址段最低的一塊記憶體區域,用於ISA(Industry Standard Architecture)裝置DMA存取。在x86架構下,該Zone大小限制為16MB。
ZONE_DMA32
該Zone用於支援32-bits地址匯流排的DMA裝置,只在64-bits系統裡才有效。
ZONE_NORMAL
該Zone的記憶體被核心直接對映為線性地址並可以直接使用。在X86-32架構下,該Zone對應的地址範圍為16MB~896MB。在X86-64架構下,DMA和DMA32之外的記憶體全部在NORMAL的Zone裡管理。
ZONE_HIGHMEM
該Zone只在32位元系統才有,通過建立臨時頁表的方式對映超過896MB的記憶體空間。即在需要存取的時候建立地址空間和記憶體的對映關係,在存取結束後拆掉對映關係釋放地址空間,該地址空間可以用於其他HIGHMEM的記憶體對映。
通過/proc/zoneinfo可以檢視Zone相關的資訊。如下所示X86-64系統上兩個Node,Node0上有DMA、DMA32和Normal三個Zone,Node1上只有一個Normal Zone。
[root@localhost ~]# cat /proc/zoneinfo |grep -E "zone| free|managed"
Node 0, zone DMA
pages free 3700
managed 3975
Node 0, zone DMA32
pages free 291250
managed 326897
Node 0, zone Normal
pages free 3232166
managed 3604347
Node 1, zone Normal
pages free 3980110
managed 4128056
Page
Page是Linux底層記憶體管理的基本單位,大小為4KB。一個Page對映為一段連續的實體記憶體,記憶體的分配和釋放都要以Page為單位進行。進程虛擬地址到實體地址的對映也是通過Page Table頁表進行,頁表的每一項記錄一個Page的虛擬地址對應的實體地址。
TLB
記憶體存取時需要查詢地址對應的Page結構,這個資料記錄在頁表裡。所有對記憶體地址的存取都要先查詢頁表,因此頁表的存取次數是頻率最高的。為了提高對頁表的存取速度,引入了TLB(Translation Lookaside Buffer)機制,將存取較多頁表快取在CPU的cache裡。因此CPU的效能統計裡很重要的一項就是L1/L2 cache的TLB miss統計項。在記憶體較大的系統裡,如256GB記憶體全量的頁表項有256GB/4KB=67108864條,每個條目佔用16位元組的話,需要1GB,顯然是CPU cache無法全量快取的。這時候如果存取的記憶體範圍較廣很容易出現TLB miss導致存取延時的增加。
Hugepages
為了降低TLB miss的概率,Linux引入了Hugepages機制,可以設定Page大小為2MB或者1GB。2MB的Hugepages機制下,同樣256GB記憶體需要的頁表項降低為256GB/2MB=131072,僅需要2MB。因此Hugepages的頁表可以全量快取在CPU cache中。
通過sysctl -w vm.nr_hugepages=1024可以設定hugepages的個數為1024,總大小為4GB。需要注意是,設定huagepages會從系統申請連續2MB的記憶體塊並進行保留(不能用於正常記憶體申請),如果系統執行一段時間導致記憶體碎片較多時,再申請hugepages會失敗。
如下所示為hugepages的設定和mount方法,mount之後應用程式需要在mount路徑下通過mmap進行檔案對映來使用這些hugepages。
sysctl -w vm.nr_hugepages=1024
mkdir -p /mnt/hugepages
mount -t hugetlbfs hugetlbfs /mnt/hugepages
Buddy System
Linux Buddy System是為了解決以Page為單位的記憶體分配導致外記憶體碎片問題:即系統缺少連續的Page頁導致需要連續Page頁的記憶體申請無法得到滿足。原理很簡單,將不同個數的連續Pages組合成Block進行分配,Block按2的冪次方個Pages劃分為11個Block連結串列,分別對應1,2,4,8,16,32,64,128,256,512和1024個連續的Pages。呼叫Buddy System進行記憶體分配時,根據申請的大小找最合適的Block。
如下所示為各個Zone上的Buddy System基本資訊,後面11列為11個Block連結串列裡可用的Block個數。
[root@localhost ~]# cat /proc/buddyinfo
Node 0, zone DMA 0 0 1 0 1 1 1 0 0 1 3
Node 0, zone DMA32 102 79 179 229 230 166 251 168 107 78 169
Node 0, zone Normal 1328 900 1985 1920 2261 1388 798 972 539 324 2578
Node 1, zone Normal 466 1476 2133 7715 6026 4737 2883 1532 778 490 2760
Slab
Buddy System的記憶體都是大塊申請,但是大多數應用需要的記憶體都很小,比如常見的幾百個Bytes的資料結構,如果也申請一個Page,將會非常浪費。為了滿足小而不規則的記憶體分配需求,Linux設計了Slab分配器。原理簡單說就是為特定的資料結構建立memcache,從Buddy System裡申請Pages,將每個Page按資料結構的大小劃分為多個Objects,使用者從memcache裡申請資料結構時分配一個Object。
如下所示為Linux檢視slab資訊的方法:
[root@localhost ~]# 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>
fat_inode_cache 90 90 720 45 8 : tunables 0 0 0 : slabdata 2 2 0
fat_cache 0 0 40 102 1 : tunables 0 0 0 : slabdata 0 0 0
kvm_vcpu 0 0 16576 1 8 : tunables 0 0 0 : slabdata 0 0 0
kvm_mmu_page_header 0 0 168 48 2 : tunables 0 0 0 : slabdata 0 0 0
ext4_groupinfo_4k 4440 4440 136 30 1 : tunables 0 0 0 : slabdata 148 148 0
ext4_inode_cache 63816 65100 1032 31 8 : tunables 0 0 0 : slabdata 2100 2100 0
ext4_xattr 1012 1012 88 46 1 : tunables 0 0 0 : slabdata 22 22 0
ext4_free_data 16896 17600 64 64 1 : tunables 0 0 0 : slabdata 275 275 0
通常我們都是通過slabtop命令檢視排序後的slab資訊:
OBJS ACTIVE USE OBJ SIZE SLABS OBJ/SLAB CACHE SIZE NAME
352014 352014 100% 0.10K 9026 39 36104K buffer_head
93492 93435 99% 0.19K 2226 42 17808K dentry
65100 63816 98% 1.01K 2100 31 67200K ext4_inode_cache
48128 47638 98% 0.06K 752 64 3008K kmalloc-64
47090 43684 92% 0.05K 554 85 2216K shared_policy_node
44892 44892 100% 0.11K 1247 36 4988K sysfs_dir_cache
43624 43177 98% 0.07K 779 56 3116K Acpi-ParseExt
43146 42842 99% 0.04K 423 102 1692K ext4_extent_status
kmalloc
和glibc的malloc()一樣,核心也提供kmalloc()用於分配任意大小的記憶體空間。同樣,如果放任應用程式隨意從Page裡申請任意大小的記憶體也會導致Page內???記憶體碎片化。為了解決內部碎片問題,Linux使用Slab機制來實現kmalloc記憶體分配。原理和Buddy System類似,即建立2的冪次方的Slab池用於kmalloc根據大小適配最佳的Slab進行分配。
如下所示為用於kmalloc分配的Slabs:
[root@localhost ~]# 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>
kmalloc-8192 196 200 8192 4 8 : tunables 0 0 0 : slabdata 50 50 0
kmalloc-4096 1214 1288 4096 8 8 : tunables 0 0 0 : slabdata 161 161 0
kmalloc-2048 2861 2928 2048 16 8 : tunables 0 0 0 : slabdata 183 183 0
kmalloc-1024 7993 8320 1024 32 8 : tunables 0 0 0 : slabdata 260 260 0
kmalloc-512 6030 6144 512 32 4 : tunables 0 0 0 : slabdata 192 192 0
kmalloc-256 7813 8576 256 32 2 : tunables 0 0 0 : slabdata 268 268 0
kmalloc-192 15542 15750 192 42 2 : tunables 0 0 0 : slabdata 375 375 0
kmalloc-128 16814 16896 128 32 1 : tunables 0 0 0 : slabdata 528 528 0
kmalloc-96 17507 17934 96 42 1 : tunables 0 0 0 : slabdata 427 427 0
kmalloc-64 48590 48704 64 64 1 : tunables 0 0 0 : slabdata 761 761 0
kmalloc-32 7296 7296 32 128 1 : tunables 0 0 0 : slabdata 57 57 0
kmalloc-16 14336 14336 16 256 1 : tunables 0 0 0 : slabdata 56 56 0
kmalloc-8 21504 21504 8 512 1 : tunables 0 0 0 : slabdata 42 42 0
核心引數
Linux提供了一些記憶體管理相關的核心引數,在/proc/sys/vm目錄下可以檢視或者通過sysctl -a |grep vm檢視:
[root@localhost vm]# sysctl -a |grep vm
vm.admin_reserve_kbytes = 8192
vm.block_dump = 0
vm.dirty_background_bytes = 0
vm.dirty_background_ratio = 10
vm.dirty_bytes = 0
vm.dirty_expire_centisecs = 3000
vm.dirty_ratio = 20
vm.dirty_writeback_centisecs = 500
vm.drop_caches = 1
vm.extfrag_threshold = 500
vm.hugepages_treat_as_movable = 0
vm.hugetlb_shm_group = 0
vm.laptop_mode = 0
vm.legacy_va_layout = 0
vm.lowmem_reserve_ratio = 256 256 32
vm.max_map_count = 65530
vm.memory_failure_early_kill = 0
vm.memory_failure_recovery = 1
vm.min_free_kbytes = 1024000
vm.min_slab_ratio = 1
vm.min_unmapped_ratio = 1
vm.mmap_min_addr = 4096
vm.nr_hugepages = 0
vm.nr_hugepages_mempolicy = 0
vm.nr_overcommit_hugepages = 0
vm.nr_pdflush_threads = 0
vm.numa_zonelist_order = default
vm.oom_dump_tasks = 1
vm.oom_kill_allocating_task = 0
vm.overcommit_kbytes = 0
vm.overcommit_memory = 0
vm.overcommit_ratio = 50
vm.page-cluster = 3
vm.panic_on_oom = 0
vm.percpu_pagelist_fraction = 0
vm.stat_interval = 1
vm.swappiness = 60
vm.user_reserve_kbytes = 131072
vm.vfs_cache_pressure = 100
vm.zone_reclaim_mode = 0
vm.drop_caches
vm.drop_caches是最常用到的引數,因為Linux的Page cache(檔案系統快取)機制會導致大量的記憶體被用於檔案系統快取,包括資料快取和後設資料(dentry、inode)快取。當記憶體不足時,我們通過該引數可以快速釋放檔案系統快取:
To free pagecache:
echo 1 > /proc/sys/vm/drop_caches
To free reclaimable slab objects (includes dentries and inodes):
echo 2 > /proc/sys/vm/drop_caches
To free slab objects and pagecache:
echo 3 > /proc/sys/vm/drop_caches
vm.min_free_kbytes
vm.min_free_kbytes用於決定記憶體低於多少時啟動記憶體回收機制(包括上面提到的檔案系統快取和下面會提到的可回收的Slab),該值預設值較小,在記憶體較多的系統設定為一個較大的值(如1GB)可以在記憶體還不會太少時自動觸發記憶體回收。但也不能設定太大,導致頻繁應用程式經常被OOM killed。
sysctl -w vm.min_free_kbytes=1024000
vm.min_slab_ratio
vm.min_slab_ratio用於決定Slab池裡可回收的Slab空間在該Zone裡的占比達到多少時進行回收,預設是5%。但經過筆者試驗,當記憶體充足時根本不會觸發Slab回收,也只有在記憶體水位線達到上面min_free_kbytes時才會觸發Slab回收。該值最小可以設定為1%:
sysctl -w vm.min_slab_ratio=1
總結
以上簡單描述了Linux記憶體管理機制和幾個常用的記憶體管理核心引數。
參考資料
Understanding The Linux Kernel 3rd Edition
[Linux Physical Memory Description]](http://www.ilinuxkernel.com/files/Linux_Physical_Memory_Description.pdf)
本文永久更新連結地址:https://www.linuxidc.com/Linux/2018-05/152624.htm
相關文章