1. 程式人生 > >Linux內存管理機制簡析

Linux內存管理機制簡析

physical emp head www. include -128 () map 釋放

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)

Linux內存管理機制簡析