認識 Linux 內(nèi)存構(gòu)成:Linux 內(nèi)存調(diào)優(yōu)之頁表、TLB、缺頁異常、大頁認知
認識 Linux 內(nèi)存構(gòu)成:Linux 內(nèi)存調(diào)優(yōu)之頁表、TLB、大頁認知
當(dāng)啟動一個程序時,會先給程序分配合適的虛擬地址空間,但是不需要把所有虛擬地址空間都映射到物理內(nèi)存,而是把程序在運行中需要的數(shù)據(jù),映射到物理內(nèi)存,需要時可以再動態(tài)映射分配物理內(nèi)存
因為每個進程都維護著自己的虛擬地址空間,每個進程都有一個頁表來定位虛擬內(nèi)存到物理內(nèi)存的映射,每個虛擬內(nèi)存也在表中都有一個對應(yīng)的條目。
這里的頁表是進程用于跟蹤虛擬內(nèi)存到物理內(nèi)存的映射,那么實際的數(shù)據(jù)結(jié)構(gòu)是什么的?
頁表
如果每個進程都分配一個大的頁表,64位系統(tǒng) 理論虛擬地址空間為2^64字節(jié),但實際 Linux 系統(tǒng)通常采用48位有效虛擬地址
┌──[root@liruilongs.github.io]-[~]
└─$cat /proc/cpuinfo | grep address
address sizes : 45 bits physical, 48 bits virtual
address sizes : 45 bits physical, 48 bits virtual
┌──[root@liruilongs.github.io]-[~]
└─$
即2 ^48字節(jié)(256TB)。若頁面大小為4KB(2^12字節(jié)),則需管理的頁表項數(shù)量為 虛擬頁數(shù) = 2^48 / 2^12 = 2^36
每個頁表項需要存儲物理頁幀號(PFN)和權(quán)限標志,通常占用8字節(jié)。所以頁表的總內(nèi)存需求為: 總大小 = 2^36 × 8 = 2^39 字節(jié) = 512GB
512G ,即一個進程的頁表本身就是巨大的,如果多個進程更夸張,但是實際中進程僅使用少量內(nèi)存(如1GB),可能只需要幾個映射,單級頁表仍需預(yù)分配全部虛擬地址空間對應(yīng)的頁表項,造成大部分的空間浪費,況且也沒有那么多內(nèi)存存放頁表。
多級頁表
這里優(yōu)化的方案就是將頁面分級管理(多級頁表 Multi-Level Page Table),將一個大頁表大小分成很多小表,最終指向頁表條目(一條映射記錄),系統(tǒng)只需要給進程分配頁表目錄,從而降低映射總表的大小。
這里怎么理解多級頁表和頁表目錄?
想象你要管理一個超大的圖書館(相當(dāng)于虛擬地址空間),里面有 幾百萬本書(相當(dāng)于內(nèi)存頁)。如果用一個超大的總目錄(只有小標題)記錄每本書的位置,這個目錄本身就會占據(jù)整個圖書館的空間,顯然不現(xiàn)實。多級頁表就像是對只有小標題的目錄作了多級目錄劃分。
- 第一層目錄(PGD):記錄整個圖書館分為 512個大區(qū)(每個大區(qū)對應(yīng)9位索引,2?=512)。
- 第二層目錄(PUD):每個大區(qū)再分為 512個小區(qū)。
- 第三層目錄(PMD):每個小區(qū)再分 512個書架。
- 第四層目錄(PTE):每個書架對應(yīng) 512本書(每本書即4KB內(nèi)存頁)
我們知道數(shù)組存儲只存儲首地址,之后的元素會根據(jù)首地址計算,這里的頁表目錄類似首地址,所以可以通過多級目錄位置直接定位映射記錄。
現(xiàn)代系統(tǒng)多使用上面多級頁表(如 x86-64 的 四級頁表)的方式,逐步縮小搜索范圍,但是多級頁表也有一定的弊端,后面我們會討論
首先會按照上面的方式對 48位虛擬地址進行拆分,虛擬地址被分割為多個索引字段,每一級索引對應(yīng)一級頁表,逐級查詢頁表項(PTE),48 位虛擬地址可能拆分為:
PGD索引(9位) → PUD索引(9位) → PMD索引(9位) → PTE索引(9位) → 頁內(nèi)偏移(12位)
每級頁表僅需512(2^9)項(9位索引),每個表項是 8 字節(jié),所以單級占用4KB,而且僅在實際需要時分配下級頁表。
當(dāng)進程需要映射1GB內(nèi)存時,只需要分配必要的頁表僅需
總頁表大小 = 1(PGD)+1(PUD)+1(PMD)+512(PTE) = 515×4KB ≈ 2.02MB
PGD、PUD、PMD各一個,PTE需要512個,總共515個頁表項,每個4KB,總共約2.02MB。
那么這里的 512個索引頁面是如何計算的?
1GB/4KB=262,144個頁面, 262,144/512=512 個PTE索引頁(一個索引頁存放512頁表項),一個頁表項對應(yīng)一個內(nèi)存頁
前面我們也有講過,在具體的分配上,內(nèi)核空間位于虛擬地址的高位(高24位),用戶態(tài)內(nèi)存空間位于虛擬地址低位,頁表本身存儲在內(nèi)核空間,用戶程序無法直接修改,僅能通過系統(tǒng)調(diào)用請求內(nèi)核操作。用戶態(tài)程序申請內(nèi)存時,內(nèi)核僅分配虛擬地址,實際物理頁的分配由缺頁異常觸發(fā)。此時內(nèi)核介入,更新頁表項并映射物理頁,這一過程需切換到內(nèi)核態(tài)執(zhí)行。
那里這里的缺頁異常又是什么?
缺頁異常
當(dāng)進程訪問系統(tǒng)沒有映射物理頁的虛擬內(nèi)存頁時,內(nèi)核就會產(chǎn)生一個 page fault 異常事件。
minor fualt
當(dāng)進程缺頁事件發(fā)生在第一次訪問虛擬內(nèi)存時,虛擬內(nèi)存已分配但未映射(如首次訪問、寫時復(fù)制、共享內(nèi)存同步)物理地址,內(nèi)核會產(chǎn)生一個 minor page fualt,并分配新的物理內(nèi)存頁。minor page fault 產(chǎn)生的開銷比較小。
minor page fualt 典型場景:
- 首次訪問:進程申請內(nèi)存后,內(nèi)核延遲分配物理頁(Demand Paging),首次訪問時觸發(fā)。
- 寫時復(fù)制(COW):fork()創(chuàng)建子進程時共享父進程內(nèi)存,子進程寫操作前觸發(fā)
- 共享庫加載:動態(tài)鏈接庫被多個進程共享,首次加載到物理內(nèi)存時觸發(fā),即會共享頁表
major fault
當(dāng)物理頁未分配且需從磁盤(Swap分區(qū)或文件)加載數(shù)據(jù),內(nèi)核就會產(chǎn)生一個 majorpage fault,比如內(nèi)核通過Swap分區(qū),將內(nèi)存中的數(shù)據(jù)交換出去放到了硬盤,需要時從硬盤中重新加載程序或庫文件的代碼到內(nèi)存。涉及到磁盤I/O,因此一個major fault對性能影響比較大,典型場景有
- Swap In:物理內(nèi)存不足時,內(nèi)核將內(nèi)存頁換出到 Swap 分區(qū),再次訪問需換回。
- 文件映射(mmap):通過 mmap 映射文件到內(nèi)存,首次訪問文件內(nèi)容需從磁盤讀取。
Minor Fault 是內(nèi)存層面的輕量級操作,Major Fault 是涉及磁盤I/O的重型操作。頻繁的 Major Fault 就需要考慮性能問題, 對于缺頁異常,我們通過 ps、vmstat、perf等工具定位性能瓶頸
通過 ps 命令查看當(dāng)前系統(tǒng)存在缺頁異常的進程的排序
┌──[root@liruilongs.github.io]-[~]
└─$ps -eo pid,minflt,majflt,comm | awk '$2 > 0 && $3 > 0 {print}'
PID MINFLT MAJFLT COMMAND
1 55646 189 systemd
704 959 7 systemd-journal
719 1912 2 systemd-udevd
892 80 3 auditd
913 553 12 dbus-broker-lau
915 281 4 dbus-broker
918 15617 206 firewalld
919 325 6 irqbalance
921 740 5 systemd-logind
925 166 5 chronyd
955 1243 100 NetworkManager
991 26090 281 /usr/sbin/httpd
998 2683 260 php-fpm
999 923 17 sshd
1002 9775 7 tuned
1006 862 3 crond
1121 6976 225 mariadbd
1150 2060 125 polkitd
1213 731 24 rsyslogd
1498 390 7 pmcd
1518 516 11 pmdaroot
1535 470 4 pmdaproc
1544 410 2 pmdaxfs
1551 447 4 pmdalinux
1558 409 2 pmdakvm
1872 2109 1 /usr/sbin/httpd
1874 3701 9 /usr/sbin/httpd
2201 2654 2 bash
2245 678 6 sudo
2246 3300 1 bash
4085 541 10 htop
┌──[root@liruilongs.github.io]-[~]
└─$
也可以通過 perf stat 來查看指定命令,進程的 缺頁異常情況
┌──[root@liruilongs.github.io]-[~]
└─$perfstat -e minor-faults,major-faults hostnamectl
Static hostname: liruilongs.github.io
Icon name: computer-vm
Chassis: vm ?
Machine ID: 7deac2815b304f9795f9e0a8b0ae7765
Boot ID: 5041b68a4d574df2b59289e33e85bdd5
Virtualization: vmware
Operating System: Rocky Linux 9.4 (Blue Onyx)
CPE OS Name: cpe:/o:rocky:rocky:9::baseos
Kernel: Linux 5.14.0-427.20.1.el9_4.x86_64
Architecture: x86-64
Hardware Vendor: VMware, Inc.
Hardware Model: VMware Virtual Platform
Firmware Version: 6.00
Performance counter stats for'hostnamectl':
463 minor-faults
0 major-faults
0.132397887 seconds time elapsed
0.009642000 seconds user
0.004471000 seconds sys
┌──[root@liruilongs.github.io]-[~]
└─$
可以看到 hostnamectl 命令因內(nèi)存動態(tài)分配觸發(fā)了 463 次次缺頁中斷,下面是一些常見的對應(yīng)缺頁異常的調(diào)優(yōu)建議
減少 Major Fault:
- 增加或者禁用物理內(nèi)存:避免頻繁 Swap。
- 調(diào)整 Swappiness:降低內(nèi)核參數(shù) /proc/sys/vm/swappiness,減少內(nèi)存換出傾向。
- 預(yù)加載數(shù)據(jù):使用 mlock() 鎖定關(guān)鍵內(nèi)存頁(如實時系統(tǒng)),禁止換出。
- 優(yōu)化文件訪問:對 mmap 文件進行順序讀取或預(yù)讀(posix_fadvise)。
降低 Minor Fault:
- 預(yù)分配內(nèi)存:避免 Demand Paging 的延遲(如啟動時初始化全部內(nèi)存)。
- 減少 COW 開銷:避免頻繁 fork(),改用 posix_spawn 或線程。
通過多級頁表的方式極大的縮小和頁表空間,可以按需分配,但是多級頁表也有一定的局限性,一是地址轉(zhuǎn)換復(fù)雜度,層級增加會降低轉(zhuǎn)換效率,需依賴硬件加速(如MMU的并行查詢能力)。二是內(nèi)存碎片風(fēng)險,子表的離散分配可能導(dǎo)致物理內(nèi)存碎片化(內(nèi)存不連續(xù)+頻繁的回收創(chuàng)建),需操作系統(tǒng)優(yōu)化分配策略
為了解決多級頁表的地址轉(zhuǎn)換需多次訪存(如四級頁表需4次內(nèi)存訪問),導(dǎo)致延遲增加,常見的解決方案包括:
- TLB(快表)緩存:存儲最近使用的頁表項,命中時直接獲取物理地址,減少訪存次數(shù)
- 巨型頁(Huge Page):使用2MB或1GB的頁面粒度,減少頁表層級和項數(shù)(大頁的使用需要操作系統(tǒng)和應(yīng)用程序的支持)
所以進程通過頁表查詢虛擬地址和物理地址的映射關(guān)系, 首先會檢查 TLB(Translation Lookaside Buffer)高速緩存頁表項,CPU硬件緩存.
那么這里的 TLB 是如何參與到到內(nèi)存映射的?
TLB
TLB 是內(nèi)存管理單元(MMU)的一部分,本質(zhì)是頁表的高速緩存,存儲最近被頻繁訪問的頁表項(虛擬地址到物理地址的映射關(guān)系)的副本,是集成在 CPU 內(nèi)部的 高速緩存硬件,用于加速虛擬地址到物理地址轉(zhuǎn)換的專用緩存,通過專用電路實現(xiàn)高速地址轉(zhuǎn)換,與數(shù)據(jù)緩存(Data Cache)和指令緩存(Instruction Cache)并列,共同構(gòu)成 CPU 緩存體系
上面我們講當(dāng)進程查詢分層頁面的映射信息會導(dǎo)致延遲增加。因此,當(dāng)缺頁異常觸發(fā)內(nèi)核分配物理內(nèi)存將從虛擬地址到物理地址的映射添加到頁表中時,它還將該映射條目緩存在 TLB 硬件緩存中,通過緩存進程最近使用的頁映射來加速地址轉(zhuǎn)換。
當(dāng)下一次查詢發(fā)生的時候,首先會在 TLB 中查詢是否有緩存,如果有的話會直接獲取,沒有的話,走上面缺頁異常的流程
進程訪問虛擬地址 → MMU 查詢 TLB → [命中 → 直接獲取物理地址]
│
└→ [未命中 → 查詢頁表 → 權(quán)限檢查 → 缺頁處理(可選)→ 生成物理地址 → 更新 TLB]
│
└→ 訪問物理內(nèi)存
所以 TLB 命中率直接影響程序效率。若 TLB 未命中(Miss),需通過頁表遍歷獲取物理地址,導(dǎo)致額外延遲(通常是 TLB 命中時間的數(shù)十倍),從內(nèi)存加載頁表項,并更新TLB緩存(可能觸發(fā)條目替換,如LRU算法)
可以通過 perf stat 命令來查看某一個命令或者進程的 TLB 命中情況
┌──[root@liruilongs.github.io]-[~]
└─$perfstat -e dTLB-loads,dTLB-load-misses,iTLB-loads,iTLB-load-misses hostnamectl
Static hostname: liruilongs.github.io
Icon name: computer-vm
Chassis: vm ?
Machine ID: 7deac2815b304f9795f9e0a8b0ae7765
Boot ID: 5041b68a4d574df2b59289e33e85bdd5
Virtualization: vmware
Operating System: Rocky Linux 9.4 (Blue Onyx)
CPE OS Name: cpe:/o:rocky:rocky:9::baseos
Kernel: Linux 5.14.0-427.20.1.el9_4.x86_64
Architecture: x86-64
Hardware Vendor: VMware, Inc.
Hardware Model: VMware Virtual Platform
Firmware Version: 6.00
Performance counter stats for'hostnamectl':
0 dTLB-loads
0 dTLB-load-misses
<not supported> iTLB-loads
0 iTLB-load-misses
0.131288737 seconds time elapsed
0.010681000 seconds user
0.005377000 seconds sys
┌──[root@liruilongs.github.io]-[~]
- dTLB-loads 表示數(shù)據(jù)地址轉(zhuǎn)換(Data TLB)的加載次數(shù),
- dTLB-load-misses 表示未命中次數(shù)
- iTLB-loads 表示指令地址轉(zhuǎn)換(Instruction TLB)的加載次數(shù)
- iTLB-load-misses 指令 TLB 未命中次數(shù)為 0,說明所有指令訪問均命中 TLB 緩存。
查看指定進程的命中情況使用 -p <pid> 的方式
┌──[root@liruilongs.github.io]-[/usr/lib/systemd/system]
└─$perf stat -e dTLB-loads,dTLB-load-misses,iTLB-loads,iTLB-load-misses -p 1
┌──[root@liruilongs.github.io]-[/usr/lib/systemd/system]
└─$
大頁(巨型頁)
大頁是另一種解決多級頁表多次訪問內(nèi)存的手段,顧名思義,傳統(tǒng)的內(nèi)存頁是 4KB,大于 4KB 的內(nèi)存頁被稱為大頁,通過大頁可以降低多級頁表的層級
同時 TLB 也有一定的局限性,存儲條目是固定的,當(dāng)進程需要訪問大量內(nèi)存的時候,比如數(shù)據(jù)庫應(yīng)用,將會導(dǎo)致大量 TLB 未命中而影響性能,還是需要通過多級頁表來轉(zhuǎn)化地址,所以除了 4KB 頁面之外,Linux 內(nèi)核還通過大頁面機制支持大容量內(nèi)存頁面。
通過查看 /proc/meminfo 文件確定具體系統(tǒng)的大頁大小以及使用情況,大頁分為 標準大頁(靜態(tài)大頁)和透明大頁
靜態(tài)大頁其核心原理是通過增大內(nèi)存頁的尺寸(如2MB或1GB),優(yōu)化虛擬地址到物理地址的轉(zhuǎn)換效率,從而提升系統(tǒng)性能。x86 64 位架構(gòu)支持多種大頁規(guī)格,比如 4KiB,2MiB 以及 1GiB。Linux 系統(tǒng)默認是 2MiB
需要說明的是,大頁配置僅受用語支持大頁的應(yīng)用程序,對于不支持大頁的應(yīng)用程序來說是無效的,同時大頁會導(dǎo)致內(nèi)存剩余空間變小 后面我們會介紹幾個Demo
透明大頁用于合并傳統(tǒng)內(nèi)存頁。
┌──[root@liruilongs.github.io]-[~]
└─$cat /proc/meminfo | grep Hug
AnonHugePages: 165888 kB
ShmemHugePages: 0 kB
FileHugePages: 0 kB
HugePages_Total: 0
HugePages_Free: 0
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 2048 kB
Hugetlb: 0 kB
┌──[root@liruilongs.github.io]-[~]
└─$
Hugepagesize: 2048 kB: 靜態(tài)大頁的默認大小為 2MB,這里的大頁是標準大頁,若需使用 1GB 大頁,需修改內(nèi)核參數(shù)配置,前提是需要CPU 支持才行
AnonHugePages: 165888 kB: 透明大頁(Transparent HugePages) 匿名頁占用的內(nèi)存總量為 165,888 KB(約 162 MB)
大部分部署數(shù)據(jù)庫的機器會禁用透明大頁?這是什么原因
透明大頁
透明大頁(Transparent Huge Pages,THP)是內(nèi)核提供的一種動態(tài)內(nèi)存管理機制,它通過自動將多個 4KB 小頁 合并為 2MB 或 1GB 大頁,減少頁表項數(shù)量并提升 TLB(地址轉(zhuǎn)換緩存)命中率,從而優(yōu)化內(nèi)存訪問性能
與需手動預(yù)分配的靜態(tài)大頁(HugeTLB)不同,THP 對應(yīng)用程序透明且無需配置,適用于順序內(nèi)存訪問(如大數(shù)據(jù)處理)和低實時性場景。但動態(tài)合并可能引發(fā) 內(nèi)存碎片 和 性能抖動,因此對延遲敏感的數(shù)據(jù)庫(如 MySQL)或高并發(fā)系統(tǒng)建議關(guān)閉 THP
下面為透明大頁相關(guān)配置
┌──[root@liruilongs.github.io]-[~]
└─$ls /sys/kernel/mm/transparent_hugepage/
defrag enabled hpage_pmd_size khugepaged/ shmem_enabled use_zero_page
enabled 用于配置是否開啟 THP
┌──[root@liruilongs.github.io]-[~]
└─$cat /sys/kernel/mm/transparent_hugepage/enabled
[always] madvise never
┌──[root@liruilongs.github.io]-[~]
└─$cat /proc/meminfo | grep Anon
AnonPages: 356692 kB
AnonHugePages: 167936 kB
┌──[root@liruilongs.github.io]-[~]
└─$
禁用透明大頁 某些場景(如數(shù)據(jù)庫)建議禁用 THP 以穩(wěn)定性能:
# 臨時禁用
echo never > /sys/kernel/mm/transparent_hugepage/enabled
使用 grubby 更新內(nèi)核啟動參數(shù),grubby 用于 動態(tài)修改內(nèi)核啟動參數(shù) 或 設(shè)置默認內(nèi)核,無需手動編輯配置文件
# 永久禁用
┌──[root@liruilongs.github.io]-[~]
└─$grubby --update-kernel=ALL --args="transparent_hugepage=never"
┌──[root@liruilongs.github.io]-[~]
└─$reboot
確認配置
┌──[root@liruilongs.github.io]-[~]
└─$cat /sys/kernel/mm/transparent_hugepage/enabled
always madvise [never]
┌──[root@liruilongs.github.io]-[~]
└─$
再次查看透明大頁使用情況
┌──[root@liruilongs.github.io]-[~]
└─$grep AnonHugePages /proc/meminfo
AnonHugePages: 0 kB
┌──[root@liruilongs.github.io]-[~]
└─$
shmem_enabled 用于配置 共享內(nèi)存(如 tmpfs、共享匿名映射)是否啟用透明大頁(THP)
┌──[root@liruilongs.github.io]-[~]
└─$cat /sys/kernel/mm/transparent_hugepage/shmem_enabled
always within_size advise [never] deny force
這里的 never 表示完全禁用共享內(nèi)存的透明大頁。常用于數(shù)據(jù)庫(如 Oracle、MySQL)或高延遲敏感型應(yīng)用,避免因動態(tài)內(nèi)存合并引發(fā)性能抖動
透明大頁會涉及到一個進程 khugepaged,khugepaged 是 Linux 內(nèi)核的一部分,負責(zé)處理透明大頁(Transparent HugePages, THP)的管理。透明大頁是內(nèi)核自動將小頁合并為大頁以提升性能的機制,而 khugepaged 就是負責(zé)這個合并過程的守護進程。自動掃描內(nèi)存區(qū)域,尋找可以合并的小頁,并嘗試將它們轉(zhuǎn)換為透明大頁。此過程在后臺靜默運行,無需應(yīng)用程序顯式請求。
┌──[root@liruilongs.github.io]-[/sys/devices/system/node]
└─$ps -eaf | grep khug
root 44 2 0 4月23 ? 00:00:00 [khugepaged]
root 16049 9747 0 10:49 pts/0 00:00:00 grep --color=auto khug
它會嘗試將多個常規(guī)小頁(4KB)合并成 大頁(2MB 或 1GB),以減少頁表項數(shù)量,從而提升內(nèi)存訪問性能。
控制 khugepaged 的掃描頻率,合并閾值等可以通過下面的文件修改
┌──[root@liruilongs.github.io]-[/sys/devices/system/node]
└─$cat /sys/kernel/mm/transparent_hugepage/khugepaged/alloc_sleep_millisecs
60000
┌──[root@liruilongs.github.io]-[/sys/devices/system/node]
└─$cat /sys/kernel/mm/transparent_hugepage/khugepaged/scan_sleep_millisecs
10000
┌──[root@liruilongs.github.io]-[/sys/devices/system/node]
└─$
靜態(tài)大頁
靜態(tài)大頁需要單獨配置,使用 sysctl 修改內(nèi)核參數(shù),可以設(shè)置分配的靜態(tài)大頁的數(shù)量,大頁內(nèi)存是系統(tǒng)啟動時或通過 sysctl 預(yù)先分配的,這部分內(nèi)存會被鎖定,普通進程無法使用,所以配置需要考慮清楚
┌──[root@liruilongs.github.io]-[~]
└─$sysctl -a | grep huge
vm.hugetlb_optimize_vmemmap = 0
vm.hugetlb_shm_group = 0
vm.nr_hugepages = 0
vm.nr_hugepages_mempolicy = 0
vm.nr_overcommit_hugepages = 0
vm.nr_hugepages:表示系統(tǒng)要預(yù)留的 大頁數(shù)量,通過 -w 臨時配置內(nèi)核參數(shù),配置大頁數(shù)量為 50
┌──[root@liruilongs.github.io]-[~]
└─$sysctl -w vm.nr_hugepages=50
vm.nr_hugepages = 50
確認配置
┌──[root@liruilongs.github.io]-[~]
└─$sysctl -a | grep huge
vm.hugetlb_optimize_vmemmap = 0
vm.hugetlb_shm_group = 0
vm.nr_hugepages = 50
vm.nr_hugepages_mempolicy = 50
vm.nr_overcommit_hugepages = 0
永久生效將配置寫入 /etc/sysctl.conf 并執(zhí)行 sysctl -p
可以通過 grub 修改內(nèi)核參數(shù)來設(shè)置大頁的數(shù)量以及大小
/etc/default/grub 是 Linux 系統(tǒng)中用于配置 GRUB(GRand Unified Bootloader) 引導(dǎo)程序的核心文件。GRUB 是大多數(shù) Linux 發(fā)行版默認的啟動管理器,負責(zé)在系統(tǒng)啟動時加載內(nèi)核和初始化內(nèi)存盤(initramfs)。該文件定義了 GRUB 的全局行為和啟動菜單的默認選項。和 上面 grubby 的方式略有區(qū)別
- hugepages=N : 設(shè)置大頁的數(shù)量
- hugepagesz=N 或 default_hugepagesz=N 設(shè)置大頁大小(默認 2MiB)
下面是一個Demo
┌──[root@liruilongs.github.io]-[~]
└─$cat /etc/default/grub
GRUB_TIMEOUT=5
GRUB_DISTRIBUTOR="$(sed 's, release .*$,,g' /etc/system-release)"
GRUB_DEFAULT=saved
GRUB_DISABLE_SUBMENU=true
GRUB_TERMINAL_OUTPUT="console"
GRUB_CMDLINE_LINUX="crashkernel=1G-4G:192M,4G-64G:256M,64G-:512M resume=/dev/mapper/rl-swap rd.lvm.lv=rl/root rd.lvm.lv=rl/swap"
GRUB_DISABLE_RECOVERY="true"
GRUB_ENABLE_BLSCFG=true
┌──[root@liruilongs.github.io]-[~]
└─$
GRUB_CMDLINE_LINUX 傳遞給所有 Linux 內(nèi)核的公共啟動參數(shù)(包括默認內(nèi)核和恢復(fù)模式內(nèi)核)
┌──[root@liruilongs.github.io]-[~]
└─$vim /etc/default/grub
8L, 374B written
┌──[root@liruilongs.github.io]-[~]
└─$cat /etc/default/grub
GRUB_TIMEOUT=5
GRUB_DISTRIBUTOR="$(sed 's, release .*$,,g' /etc/system-release)"
GRUB_DEFAULT=saved
GRUB_DISABLE_SUBMENU=true
GRUB_TERMINAL_OUTPUT="console"
GRUB_CMDLINE_LINUX="default_hugepagesz=1G hugepages=10 hugepagesz=1G net.ifnames=0 consoleblank=600 console=tty0 console=ttyS0,115200n8 spectre_v2=off nopti noibrs noibpb selinux=0 crashkern
el=512M"
GRUB_DISABLE_RECOVERY="true"
上面的配置 hugepages=10 hugepagesz=1G,靜態(tài)大頁大小為 1G,數(shù)量為 10
需要說明的是,大頁需要使用連續(xù)的內(nèi)存空間,盡量設(shè)置永久規(guī)則,在開機時分配大頁,如果系統(tǒng)已經(jīng)運行了很久,大量的內(nèi)存碎片,有可能無法分配大頁,因為沒有足夠的連續(xù)內(nèi)存空間。
配置 1G 的靜態(tài)大頁需要CPU 支持,檢查是否包含 pdpe1gb 標簽
┌──[root@liruilongs.github.io]-[~]
└─$grep pdpe1gb /proc/cpuinfo
flags: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss ht syscall nx pdpe1gb rdtscp lm constant_tsc rep_good nopl xtopology no
nstop_tsc cpuid tsc_known_freq pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm abm 3dnowprefetch invpcid_s
ingle ssbd ibrs ibpb stibp ibrs_enhanced fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid mpx avx512f avx512dq rdseed adx smap clflushopt clwb avx512cd avx512bw avx512vl xsaveopt xsavec
xgetbv1 arat avx512_vnni md_clear flush_l1d arch_capabilities
.........................................
┌──[root@liruilongs.github.io]-[~]
└─$
修改之后使用 grub2-mkconfig 生成了新的 GRUB 配置文件。重啟系統(tǒng)使配置生效。
┌──[root@liruilongs.github.io]-[~]
└─$grub2-mkconfig -o /boot/grub2/grub.cfg
正在生成 grub 配置文件 ...
找到 Linux 鏡像:/boot/vmlinuz-5.10.0-60.139.0.166.oe2203.x86_64
找到 initrd 鏡像:/boot/initramfs-5.10.0-60.139.0.166.oe2203.x86_64.img
找到 Linux 鏡像:/boot/vmlinuz-5.10.0-60.18.0.50.oe2203.x86_64
找到 initrd 鏡像:/boot/initramfs-5.10.0-60.18.0.50.oe2203.x86_64.img
找到 Linux 鏡像:/boot/vmlinuz-0-rescue-f902bd6553f24605a695d4a876a40b7a
找到 initrd 鏡像:/boot/initramfs-0-rescue-f902bd6553f24605a695d4a876a40b7a.img
Adding boot menu entry for UEFI Firmware Settings ...
完成
┌──[root@liruilongs.github.io]-[~]
└─$reboot
確認配置,可以看到 Hugepagesize 是1G,但是 nr_hugepages 大小為 5 ,并不是我們配置的 10,這是什么原因,前面我們講,靜態(tài)大頁會直接分配內(nèi)存,即只有配置就會位于常駐內(nèi)存,當(dāng)系統(tǒng)內(nèi)存沒有配置的靜態(tài)大頁大時,系統(tǒng)會自動減少
┌──[root@liruilongs.github.io]-[~]
└─$cat /proc/meminfo | grep Hug
AnonHugePages: 131072 kB
ShmemHugePages: 0 kB
FileHugePages: 0 kB
HugePages_Total: 5
HugePages_Free: 5
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 1048576 kB
Hugetlb: 5242880 kB
┌──[root@liruilongs.github.io]-[~]
└─$sysctl -a | grep huge
vm.hugepage_mig_noalloc = 0
vm.hugepage_nocache_copy = 0
vm.hugepage_pmem_allocall = 0
vm.hugetlb_shm_group = 0
vm.nr_hugepages = 5
vm.nr_hugepages_mempolicy = 5
vm.nr_overcommit_hugepages = 0
┌──[root@liruilongs.github.io]-[~]
└─$
我們可以使用 free 命令查看內(nèi)存使用情況驗證這一點
┌──[root@liruilongs.github.io]-[~]
└─$free -g
total used free shared buff/cache available
Mem: 7 5 0 0 0 1
Swap: 0 0 0
通過臨時修改內(nèi)核參數(shù)調(diào)整靜態(tài)大頁數(shù)目(實際調(diào)整需要考慮靜態(tài)大頁是否使用)
┌──[root@liruilongs.github.io]-[~]
└─$sysctl -w vm.nr_hugepages=2
vm.nr_hugepages = 2
┌──[root@liruilongs.github.io]-[~]
└─$sysctl -a | grep nr_hug
vm.nr_hugepages = 2
vm.nr_hugepages_mempolicy = 2
┌──[root@liruilongs.github.io]-[~]
└─$cat /proc/meminfo | grep Hug
AnonHugePages: 169984 kB
ShmemHugePages: 0 kB
FileHugePages: 0 kB
HugePages_Total: 2
HugePages_Free: 2
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 1048576 kB
Hugetlb: 2097152 kB
確認配置是否生效
┌──[root@liruilongs.github.io]-[~]
└─$free -g
total used free shared buff/cache available
Mem: 7 2 3 0 0 4
Swap: 0 0 0
┌──[root@liruilongs.github.io]-[~]
└─$
為了讓進程可以使用大頁,進程必須進行系統(tǒng)函數(shù)調(diào)用,可以調(diào)用 mmap() 函數(shù),或者 shmat() 函數(shù),又或者是 shmget()函數(shù)。如果進程使用的是 mmap()系統(tǒng)函數(shù)調(diào)用,則必須掛載-個 hugetlbfs 文件系統(tǒng)。
┌──[root@liruilongs.github.io]-[~]
└─$mkdir /largepage
┌──[root@liruilongs.github.io]-[~]
└─$mount -t hugetlbfs none /largepage
如果在 NUMA 系統(tǒng)上,內(nèi)核將大頁劃分到所有 NUMA 節(jié)點上,對應(yīng)的靜態(tài)大頁參數(shù)需要分別設(shè)置,而不用設(shè)置全局參數(shù)
┌──[root@liruilongs.github.io]-[~]
└─$cat /sys/devices/system/node/node0/hugepages/hugepages-2048kB/nr_hugepages
50
┌──[root@liruilongs.github.io]-[~]
└─$echo 20 > /sys/devices/system/node/node0/hugepages/hugepages-2048kB/nr_hugepages
┌──[root@liruilongs.github.io]-[~]
└─$cat /sys/devices/system/node/node0/meminfo
Node 0 MemTotal: 16082492 kB
Node 0 MemFree: 14969744 kB
Node 0 MemUsed: 1112748 kB
Node 0 SwapCached: 0 kB
Node 0 Active: 562104 kB
Node 0 Inactive: 215520 kB
Node 0 Active(anon): 369388 kB
Node 0 Inactive(anon): 0 kB
Node 0 Active(file): 192716 kB
Node 0 Inactive(file): 215520 kB
Node 0 Unevictable: 0 kB
Node 0 Mlocked: 0 kB
Node 0 Dirty: 28140 kB
Node 0 Writeback: 0 kB
Node 0 FilePages: 420444 kB
Node 0 Mapped: 93924 kB
Node 0 AnonPages: 356560 kB
Node 0 Shmem: 12208 kB
Node 0 KernelStack: 9744 kB
Node 0 PageTables: 6216 kB
Node 0 SecPageTables: 0 kB
Node 0 NFS_Unstable: 0 kB
Node 0 Bounce: 0 kB
Node 0 WritebackTmp: 0 kB
Node 0 KReclaimable: 45436 kB
Node 0 Slab: 111576 kB
Node 0 SReclaimable: 45436 kB
Node 0 SUnreclaim: 66140 kB
Node 0 AnonHugePages: 167936 kB
Node 0 ShmemHugePages: 0 kB
Node 0 ShmemPmdMapped: 0 kB
Node 0 FileHugePages: 0 kB
Node 0 FilePmdMapped: 0 kB
Node 0 Unaccepted: 0 kB
Node 0 HugePages_Total: 20
Node 0 HugePages_Free: 20
Node 0 HugePages_Surp: 0
┌──[root@liruilongs.github.io]-[~]
└─$
透明大頁 vs 靜態(tài)大頁簡單比較
特性 | 透明大頁(THP) | 靜態(tài)大頁(Huge Pages) |
配置方式 | 內(nèi)核自動管理,無需用戶干預(yù)。 | 需手動預(yù)留(如通過 |
適用場景 | 通用型應(yīng)用(如 Java、Web 服務(wù))。 | 高性能計算、數(shù)據(jù)庫(如 Oracle、MySQL)。 |
內(nèi)存碎片化 | 可能因頻繁合并/拆分導(dǎo)致碎片。 | 預(yù)留固定內(nèi)存,無碎片問題。 |
性能穩(wěn)定性 | 可能因動態(tài)合并產(chǎn)生性能波動。 | 性能更穩(wěn)定可控。 |