Linux 大頁內存 Huge Pages 虛擬內存

Linux爲什麼要有大頁內存?爲什麼DPDK要求必須要設置大頁內存?這都是由系統架構決定的,系統架構發展到現在,又是在原來的基礎上一點點演變的。一開始爲了解決一個問題,大家設計了一個很好的方案,隨着事物的發展,發現無法滿足需求,就在原來的基礎上改進,慢慢的變成了現在的樣子。不過技術革新一直在進行,包括現在。

物理內存 Physical address

物理內存就是電腦的內存條,上面的每一個方塊就是存儲芯片,芯片中還有顆粒。訪問數據的時候,會使用各種技術,儘可能從多個內存條,每個內存條的多個存儲芯片獲取數據,這樣多通道,併發大,速度更快。

虛擬內存 Linear address (also known as virtual address)

程序運行在操作系統上,不可能直接訪問物理內存。一是太複雜,需要自己管理內存,哪些被其他進程佔用了,哪些可以用,如果連續空間不夠,如何拼接等;二是不安全,用戶可以直接訪問到其他進程的數據。所以操作系統在後續增加了虛擬內存的概念。

每個進程看到的都是整個可用內存,比如4G,所有進程看到的都是4G,自己進程維護一張表,當進程訪問內存時,只能訪問到虛擬內存表,由操作系統再映射到具體的物理內存。這樣的好處除了解決了上面的問題,還有幾個優點:一是系統同一管理,更合理,可以做更多優化,比如同一塊數據多個進程讀取,只需要在內存保存一份即可,節省了空間(類庫的加載);二是程序申請內存,並不一定會使用,或者說不會立馬使用,那麼系統可以不分配物理內存,當程序真正訪問內存時,觸發中斷,系統再映射到物理內存,節省資源;三是程序訪問的都是連續資源,具體內存分配是由系統管理,簡化了開發。

MMU Memory Management Unit 內存管理單元

用來把虛擬內存地址轉化爲物理內存地址的硬件,提供物理內存數據訪問和權限控制。

頁表 虛擬內存表

虛擬內存向物理內存映射時,需要創建數據結構保存對應的數據,如果完全一一映射,肯定會需要很多資源,所以爲了減少內存空間的消耗,又提出了新的方案:把內存分爲一塊塊的數據(每塊稱作爲一頁數據),然後映射這些塊,這樣就可以減少映射條目,降低內存佔用。虛擬內存往往稱作頁;物理內存按照同樣大小分割,有時候稱作塊或者幀(frame)

MMU存儲的針對這些內存頁的表,稱作爲頁表。頁表中每一條數據保存了映射的物理內存頁的位置和在該頁內數據的偏移,這樣就能找到具體的內存單元了。

除了這種使用頁表的頁式管理方式,還有段式和段頁式兩種。

多級分頁

使用頁表後,數據量還是很大,比如32G的內存,使用4K作爲一頁,那麼需要8,388,608個頁表條目。爲了進一步減少資源消耗,我們發現大部分程序是不需要全部內存的,一個應用運行起來,並不是必須要32G的內存,有可能只有幾百兆。多級分頁就做了進一步優化:

比如32G,每級頁表按照1G分,有32條記錄;1G再按照10M分,就有100條記錄,以此類推。開始只創建第一張表,後續需要的時候,再動態創建其他的表,大大減少了頁表的記錄數。

Translation Lookaside Buffer TLB

多級分頁後,數據量少了,但是增加了一個問題,就是訪問效率,原來直接訪問內存,只需要一次;增加了頁表後,需要兩次,先訪問頁表,再訪問物理內存;多級頁表,需要多次。這相當於成倍的增加了訪問內存的時間。TLB就是爲了解決這個問題的,把常用的頁表,放到CPU的高速緩存,避免訪問內存,直接在緩存中獲取,提高效率。

大頁內存

我們知道,計算機中每個硬件的運算速度是不一樣的,其順序就是:硬盤/網絡(IO) < 內存 < 內存緩存(如果有) < CPU L3(CPU3級緩存) < CPU L2 < CPU L1 < CPU,每個之間的差距都是幾倍甚至幾十上百倍的,同樣其空間大小也是相差數量級的,只不過正好反過來。硬盤可以做到TB甚至PB,內存常見的只有幾十GB或者幾百GB,而CPU的緩存,3級的可能有幾十兆,而1級的往往只有字節級別的了。由於這個原因,TLB是不可能把所有的頁表都映射到緩存中,那麼在CPU緩存中的TLB命中率越高,性能提升越大。如果命中率很低,不僅沒有提升,還額外增加了緩存的訪問,反而降低了效率。

如果程序頻繁的訪問一塊很大的內存,並且是無序的,比如頻繁搜索一個100G的無規律的文本數據,這個時候就會導致CPU中的緩存頻頻失效,因爲CPU緩存的頁表條目有限,程序使用的條目超出了緩存TLB的範圍,就會不斷的刪除舊的,加入新的,實際上如果緩存足夠大,會發現剛刪除的條目又被加了進來。

如何解決呢?就是提高命中率,怎樣提高命中率呢?就是TLB中保存的數據條目雖然固定,但是其解析內存可以擴展,擴展到可以包含程序訪問的空間大小,這樣,不管程序如何亂序訪問,因爲頁表都保存在TLB中了,都可以命中。比如原來TLB保存100條,由於一頁4K,按照最簡單計算,就是400K的空間。我把一頁設置爲4M,那就可以表示400M的空間,如果程序訪問的內存在400M以內,就可以完全命中。

查看大頁內存

cat /proc/meminfo|grep -i huge
AnonHugePages:      4096 kB
ShmemHugePages:        0 kB
HugePages_Total:       4
HugePages_Free:        2
HugePages_Rsvd:        0
HugePages_Surp:        0
Hugepagesize:    1048576 kB
Hugetlb:         4194304 kB

HugePages_TotalHugepagesize都大於0,就表示配置了大頁內存,從字面意思也很好理解:HugePages_Total表示大頁內存的個數;Hugepagesize表示一個大頁內存的大小。同樣Hugetlb大於0也表示配置了大頁內存,這個是計算下來的總數。不過有的系統可能沒有這個字段。

配置大頁內存

方法一 修改grub

grub的文件位置在/boot目錄下,大部分有如下兩個地方,一個是Legacy,一個是UEFI,不同系統可能有細微區別:/boot/grub2/grub.cfg /boot/efi/EFI/openEuler/grub.cfg。打開文件,找到如下位置

 menuentry 'openEuler (4.19.90-2003.4.0.0036.oe1.x86_64) 20.03 (LTS)' --class openeuler --class gnu-linux --class gnu --class os --unrestricted $menuentry_id_option 'gnulinux-4.19.90-2003.4.0.0036.oe1.x86_    64-advanced-db3b87ac-c948-4347-b1a4-e8ca943688b6' {
     load_video
     set gfxpayload=keep
     insmod gzio
     insmod part_msdos
     insmod ext2
     set root='hd0,msdos1'
     if [ x$feature_platform_search_hint = xy ]; then
       search --no-floppy --fs-uuid --set=root --hint-bios=hd0,msdos1 --hint-efi=hd0,msdos1 --hint-baremetal=ahci0,msdos1 --hint='hd0,msdos1'  75039a04-9431-47c8-923c-795ba2b37e3e
     else
       search --no-floppy --fs-uuid --set=root 75039a04-9431-47c8-923c-795ba2b37e3e
     fi
     linux   /vmlinuz-4.19.90-2003.4.0.0036.oe1.x86_64 root=/dev/mapper/openeuler-root ro resume=/dev/mapper/openeuler-swap rd.lvm.lv=openeuler/root rd.lvm.lv=openeuler/swap rhgb quiet quiet crashkernel=51    default_hugepagesz=1G hugepagesz=1G hugepages=4
     initrd /initramfs-4.19.90-2003.4.0.0036.oe1.x86_64.img
 }

要找對地方,這是啓動系統的選項,還有一個是用作安全啓動的。在倒數第二行增加default_hugepagesz=1G hugepagesz=1G hugepages=4,這三個字段分別表示默認大頁內存大小,就是如果不配置,就用這個默認配置;配置的大頁內存大小;配置的大頁內存個數。重啓系統,再次查看就可以看到大頁內存信息。

掛載大頁內存

mount -t hugetlbfs nodev /mnt/huge
mount -t hugetlbfs hugetlbfs /mnt/huge

必須保證目錄/mnt/huge存在,參數意義:-t指定掛載格式,hugetlbfs表示是大頁內存,後面表示掛載設備,可以寫hugetlbfs,也可以寫nodev不掛載設備,最後是掛載目錄。

NUMA

爲什麼需要了解NUMA呢?因爲與上面的大頁內存配置有關。瞭解NUMA,又需要先知道SMP(Symmetric Multi-Processor)對稱多處理器結構。SMP就是指系統中多個CPU對稱工作,每個CPU的優先級一樣,訪問資源(內存)速度一樣,沒有主次之分。SMP又叫做UMA(Uniform Memory Access)一致內存訪問。

但是隨着CPU的發展,內核越來越多,訪問同一資源的競爭越來越大,導致CPU性能受到限制,所以推出了NUMA(Non Uniform Memory Access)非一致內存訪問架構。

NUMA設計

SMP或者UMA,CPU統一經過北橋(內存控制器)訪問內存。
NUMA,CPU把內存控制器做到CPU內部,一般一個CPU socket一個。每個CPU內部的內存控制器與一部分內存連接。CPU訪問自己連接的內存,速度很快,叫做本地內存,可以通過QPI(Quick Path Interconnect)總線訪問其他的內存,不過速度會慢。

Node Socket Core Processor

把多個core封裝到一起,叫做一個CPU Socket,系統中根據Socket定義Node,也就是正常情況Node數量與Socket相同,或者一個是軟件概念,一個是硬件概念。

Core就是物理CPU,原來是單CPU,性能不夠,研發了多CPU架構,現在一個Core就相當於原來的一塊CPU

Thread,也叫做邏輯CPU,或者Processor,是把core通過超線程記錄模擬出來的處理單元。

lscpu
lscpu
Architecture:          x86_64
CPU op-mode(s):        32-bit, 64-bit
Byte Order:            Little Endian
CPU(s):                12
On-line CPU(s) list:   0-11
Thread(s) per core:    1
Core(s) per socket:    6
Socket(s):             2
NUMA node(s):          2
Vendor ID:             GenuineIntel
CPU family:            6
Model:                 45
Stepping:              7
CPU MHz:               1200.000
BogoMIPS:              3999.47
Virtualization:        VT-x
L1d cache:             32K
L1i cache:             32K
L2 cache:              256K
L3 cache:              15360K
NUMA node0 CPU(s):     0-5
NUMA node1 CPU(s):     6-11

CPU(s) 表示邏輯CPU個數
Thread(s) per core 表示一個core可以實現的超線程數
Core(s) per socket 表示一個socket上的core數
Socket(s) 表示socket的個數
NUMA node(s) 表示NOMA的結點數

這裏如何計算呢?首先一塊中央處理器(CPU),有多個插槽socket,每個socket中又有多個core,每個core又可以使用超線程技術模擬出多個線程(這個是邏輯CPU)。所以CPU(s)=SOckets * Cores * Threads

numactl

numactl --hardware
available: 2 nodes (0-1)
node 0 cpus: 0 1 2 3 4 5 6 7
node 0 size: 15516 MB
node 0 free: 11705 MB
node 1 cpus: 8 9 10 11 12 13 14 15
node 1 size: 16100 MB
node 1 free: 1386 MB
node distances:
node   0   1 
  0:  10  21 
  1:  21  10

CPU分爲兩個node,也就是有兩個CPU socket。
第一個node包含0-7號cpu,第二個node包含8-15個cpu
第一個node直連的內存是15516MB,第二個node直連的內存是161000MB
第一個node直連的內存空閒是11705MB,第二個node直連的內存是1386MB
node distances表示每個node訪問對應的內存的距離,比如node0訪問node0的距離是10,訪問node1的距離是21。

numactl還有其他作用,比如綁定程序在哪一個node上執行,指定在哪個node上分配內存等。

numastat

numastat
                           node0           node1
numa_hit                18743091         9973793
numa_miss                      0               0
numa_foreign                   0               0
interleave_hit            101249          101451
local_node              18656240         9048143
other_node                 86851          925650

numa_hit 在node關聯的內存上申請的內存數量
numa_miss 不在node關聯的內存上申請的內存數量
numa_foreign 在其他node關聯的內存上申請的內存數量
interleave_hit 採用interleave策略申請的內存數量
local_node 該node上的進程在該node關聯的內存上申請的內存數量
other_node 該node上的進程在其他node關聯的內存上申請的內存數量

關閉開啓NUMA

在上面配置大頁內存的一行加上numa=off,會關閉numa,理論上刪除該字段,默認是開啓。

調優經驗

linux分配內存的策略是優先在自己node上分配內存,如果不夠再考慮其他node上的內存,這是合理的。不過如果程序綁定的node內存不夠,可以考慮綁定到其他node或者給程序運行的node多分配一些內存。

查看Node上的大頁內存

上面我們介紹了,現代CPU都是有很多Node的,在NUMA架構下,配置在grub.cfg中的大頁內存是被多個Node平分的。
比如我機器上按照上面配置了4個1G的大頁內存,查看每個Node上的信息,Node0和Node1上各兩個。

$ cat /sys/devices/system/node/node0/hugepages/hugepages-1048576kB/nr_hugepages
2

$ cat /sys/devices/system/node/node1/hugepages/hugepages-1048576kB/nr_hugepages
2

大頁內存配置目錄介紹

/sys/devices/system/node目錄下,可以看到系統中每一個Node對應的目錄。在每個Node目錄下/sys/devices/system/node/node0/hugepages,有關於大頁內存的配置信息,一般有兩個目錄hugepages-1048576kB hugepages-2048kB,這是Linux系統支持的兩種大頁,一個是1G,一個是2M。

在每個大頁內存目錄下有三個文件free_hugepages nr_hugepages surplus_hugepages,分別表示當前Node,當前大頁內存中空閒的大頁內存數、設定的大頁內存數,超出使用的大頁內存數。

方法二 臨時設置大頁內存

如果想臨時修改某個Node的大頁內存,可以直接向對應的nr_hugepages寫入對應數字,比如 echo 2 > /sys/devices/system/node/node0/hugepages/hugepages-1048576kB/nr_hugepages

如果不想每個Node自己控制,可以向系統統一的大頁內存文件寫入數字 echo 2 > /sys/kernel/mm/hugepages/hugepages-1048576kB/nr_hugepages,這個目錄與上面的目錄不一樣,保存的是整個系統的大頁內存配置。

https://www.kernel.org/doc/Documentation/vm/hugetlbpage.txt
Understanding Linux Kernel

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章