認(rèn)識(shí) Linux 內(nèi)存構(gòu)成:Linux 內(nèi)存調(diào)優(yōu)之頁(yè)表、TLB、缺頁(yè)異常、大頁(yè)認(rèn)知
認(rèn)識(shí) Linux 內(nèi)存構(gòu)成:Linux 內(nèi)存調(diào)優(yōu)之頁(yè)表、TLB、大頁(yè)認(rèn)知
當(dāng)啟動(dòng)一個(gè)程序時(shí),會(huì)先給程序分配合適的虛擬地址空間,但是不需要把所有虛擬地址空間都映射到物理內(nèi)存,而是把程序在運(yùn)行中需要的數(shù)據(jù),映射到物理內(nèi)存,需要時(shí)可以再動(dòng)態(tài)映射分配物理內(nèi)存
因?yàn)槊總€(gè)進(jìn)程都維護(hù)著自己的虛擬地址空間,每個(gè)進(jìn)程都有一個(gè)頁(yè)表來(lái)定位虛擬內(nèi)存到物理內(nèi)存的映射,每個(gè)虛擬內(nèi)存也在表中都有一個(gè)對(duì)應(yīng)的條目。
這里的頁(yè)表是進(jìn)程用于跟蹤虛擬內(nèi)存到物理內(nèi)存的映射,那么實(shí)際的數(shù)據(jù)結(jié)構(gòu)是什么的?
頁(yè)表
如果每個(gè)進(jìn)程都分配一個(gè)大的頁(yè)表,64位系統(tǒng) 理論虛擬地址空間為2^64字節(jié),但實(shí)際 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)。若頁(yè)面大小為4KB(2^12字節(jié)),則需管理的頁(yè)表項(xiàng)數(shù)量為 虛擬頁(yè)數(shù) = 2^48 / 2^12 = 2^36
每個(gè)頁(yè)表項(xiàng)需要存儲(chǔ)物理頁(yè)幀號(hào)(PFN)和權(quán)限標(biāo)志,通常占用8字節(jié)。所以頁(yè)表的總內(nèi)存需求為: 總大小 = 2^36 × 8 = 2^39 字節(jié) = 512GB
512G ,即一個(gè)進(jìn)程的頁(yè)表本身就是巨大的,如果多個(gè)進(jìn)程更夸張,但是實(shí)際中進(jìn)程僅使用少量?jī)?nèi)存(如1GB),可能只需要幾個(gè)映射,單級(jí)頁(yè)表仍需預(yù)分配全部虛擬地址空間對(duì)應(yīng)的頁(yè)表項(xiàng),造成大部分的空間浪費(fèi),況且也沒(méi)有那么多內(nèi)存存放頁(yè)表。
多級(jí)頁(yè)表
這里優(yōu)化的方案就是將頁(yè)面分級(jí)管理(多級(jí)頁(yè)表 Multi-Level Page Table),將一個(gè)大頁(yè)表大小分成很多小表,最終指向頁(yè)表?xiàng)l目(一條映射記錄),系統(tǒng)只需要給進(jìn)程分配頁(yè)表目錄,從而降低映射總表的大小。
這里怎么理解多級(jí)頁(yè)表和頁(yè)表目錄?
想象你要管理一個(gè)超大的圖書(shū)館(相當(dāng)于虛擬地址空間),里面有 幾百萬(wàn)本書(shū)(相當(dāng)于內(nèi)存頁(yè))。如果用一個(gè)超大的總目錄(只有小標(biāo)題)記錄每本書(shū)的位置,這個(gè)目錄本身就會(huì)占據(jù)整個(gè)圖書(shū)館的空間,顯然不現(xiàn)實(shí)。多級(jí)頁(yè)表就像是對(duì)只有小標(biāo)題的目錄作了多級(jí)目錄劃分。
- 第一層目錄(PGD):記錄整個(gè)圖書(shū)館分為 512個(gè)大區(qū)(每個(gè)大區(qū)對(duì)應(yīng)9位索引,2?=512)。
- 第二層目錄(PUD):每個(gè)大區(qū)再分為 512個(gè)小區(qū)。
- 第三層目錄(PMD):每個(gè)小區(qū)再分 512個(gè)書(shū)架。
- 第四層目錄(PTE):每個(gè)書(shū)架對(duì)應(yīng) 512本書(shū)(每本書(shū)即4KB內(nèi)存頁(yè))
我們知道數(shù)組存儲(chǔ)只存儲(chǔ)首地址,之后的元素會(huì)根據(jù)首地址計(jì)算,這里的頁(yè)表目錄類似首地址,所以可以通過(guò)多級(jí)目錄位置直接定位映射記錄。
現(xiàn)代系統(tǒng)多使用上面多級(jí)頁(yè)表(如 x86-64 的 四級(jí)頁(yè)表)的方式,逐步縮小搜索范圍,但是多級(jí)頁(yè)表也有一定的弊端,后面我們會(huì)討論
首先會(huì)按照上面的方式對(duì) 48位虛擬地址進(jìn)行拆分,虛擬地址被分割為多個(gè)索引字段,每一級(jí)索引對(duì)應(yīng)一級(jí)頁(yè)表,逐級(jí)查詢頁(yè)表項(xiàng)(PTE),48 位虛擬地址可能拆分為:
PGD索引(9位) → PUD索引(9位) → PMD索引(9位) → PTE索引(9位) → 頁(yè)內(nèi)偏移(12位)
每級(jí)頁(yè)表僅需512(2^9)項(xiàng)(9位索引),每個(gè)表項(xiàng)是 8 字節(jié),所以單級(jí)占用4KB,而且僅在實(shí)際需要時(shí)分配下級(jí)頁(yè)表。
當(dāng)進(jìn)程需要映射1GB內(nèi)存時(shí),只需要分配必要的頁(yè)表僅需
總頁(yè)表大小 = 1(PGD)+1(PUD)+1(PMD)+512(PTE) = 515×4KB ≈ 2.02MB
PGD、PUD、PMD各一個(gè),PTE需要512個(gè),總共515個(gè)頁(yè)表項(xiàng),每個(gè)4KB,總共約2.02MB。
那么這里的 512個(gè)索引頁(yè)面是如何計(jì)算的?
1GB/4KB=262,144個(gè)頁(yè)面, 262,144/512=512 個(gè)PTE索引頁(yè)(一個(gè)索引頁(yè)存放512頁(yè)表項(xiàng)),一個(gè)頁(yè)表項(xiàng)對(duì)應(yīng)一個(gè)內(nèi)存頁(yè)
前面我們也有講過(guò),在具體的分配上,內(nèi)核空間位于虛擬地址的高位(高24位),用戶態(tài)內(nèi)存空間位于虛擬地址低位,頁(yè)表本身存儲(chǔ)在內(nèi)核空間,用戶程序無(wú)法直接修改,僅能通過(guò)系統(tǒng)調(diào)用請(qǐng)求內(nèi)核操作。用戶態(tài)程序申請(qǐng)內(nèi)存時(shí),內(nèi)核僅分配虛擬地址,實(shí)際物理頁(yè)的分配由缺頁(yè)異常觸發(fā)。此時(shí)內(nèi)核介入,更新頁(yè)表項(xiàng)并映射物理頁(yè),這一過(guò)程需切換到內(nèi)核態(tài)執(zhí)行。
那里這里的缺頁(yè)異常又是什么?
缺頁(yè)異常
當(dāng)進(jìn)程訪問(wèn)系統(tǒng)沒(méi)有映射物理頁(yè)的虛擬內(nèi)存頁(yè)時(shí),內(nèi)核就會(huì)產(chǎn)生一個(gè) page fault 異常事件。
minor fualt
當(dāng)進(jìn)程缺頁(yè)事件發(fā)生在第一次訪問(wèn)虛擬內(nèi)存時(shí),虛擬內(nèi)存已分配但未映射(如首次訪問(wèn)、寫(xiě)時(shí)復(fù)制、共享內(nèi)存同步)物理地址,內(nèi)核會(huì)產(chǎn)生一個(gè) minor page fualt,并分配新的物理內(nèi)存頁(yè)。minor page fault 產(chǎn)生的開(kāi)銷比較小。
minor page fualt 典型場(chǎng)景:
- 首次訪問(wèn):進(jìn)程申請(qǐng)內(nèi)存后,內(nèi)核延遲分配物理頁(yè)(Demand Paging),首次訪問(wèn)時(shí)觸發(fā)。
- 寫(xiě)時(shí)復(fù)制(COW):fork()創(chuàng)建子進(jìn)程時(shí)共享父進(jìn)程內(nèi)存,子進(jìn)程寫(xiě)操作前觸發(fā)
- 共享庫(kù)加載:動(dòng)態(tài)鏈接庫(kù)被多個(gè)進(jìn)程共享,首次加載到物理內(nèi)存時(shí)觸發(fā),即會(huì)共享頁(yè)表
major fault
當(dāng)物理頁(yè)未分配且需從磁盤(pán)(Swap分區(qū)或文件)加載數(shù)據(jù),內(nèi)核就會(huì)產(chǎn)生一個(gè) majorpage fault,比如內(nèi)核通過(guò)Swap分區(qū),將內(nèi)存中的數(shù)據(jù)交換出去放到了硬盤(pán),需要時(shí)從硬盤(pán)中重新加載程序或庫(kù)文件的代碼到內(nèi)存。涉及到磁盤(pán)I/O,因此一個(gè)major fault對(duì)性能影響比較大,典型場(chǎng)景有
- Swap In:物理內(nèi)存不足時(shí),內(nèi)核將內(nèi)存頁(yè)換出到 Swap 分區(qū),再次訪問(wèn)需換回。
- 文件映射(mmap):通過(guò) mmap 映射文件到內(nèi)存,首次訪問(wèn)文件內(nèi)容需從磁盤(pán)讀取。
Minor Fault 是內(nèi)存層面的輕量級(jí)操作,Major Fault 是涉及磁盤(pán)I/O的重型操作。頻繁的 Major Fault 就需要考慮性能問(wèn)題, 對(duì)于缺頁(yè)異常,我們通過(guò) ps、vmstat、perf等工具定位性能瓶頸
通過(guò) ps 命令查看當(dāng)前系統(tǒng)存在缺頁(yè)異常的進(jìn)程的排序
┌──[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]-[~]
└─$也可以通過(guò) perf stat 來(lái)查看指定命令,進(jìn)程的 缺頁(yè)異常情況
┌──[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)存動(dòng)態(tài)分配觸發(fā)了 463 次次缺頁(yè)中斷,下面是一些常見(jiàn)的對(duì)應(yīng)缺頁(yè)異常的調(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)存頁(yè)(如實(shí)時(shí)系統(tǒng)),禁止換出。
- 優(yōu)化文件訪問(wèn):對(duì) mmap 文件進(jìn)行順序讀取或預(yù)讀(posix_fadvise)。
降低 Minor Fault:
- 預(yù)分配內(nèi)存:避免 Demand Paging 的延遲(如啟動(dòng)時(shí)初始化全部?jī)?nèi)存)。
- 減少 COW 開(kāi)銷:避免頻繁 fork(),改用 posix_spawn 或線程。
通過(guò)多級(jí)頁(yè)表的方式極大的縮小和頁(yè)表空間,可以按需分配,但是多級(jí)頁(yè)表也有一定的局限性,一是地址轉(zhuǎn)換復(fù)雜度,層級(jí)增加會(huì)降低轉(zhuǎn)換效率,需依賴硬件加速(如MMU的并行查詢能力)。二是內(nèi)存碎片風(fēng)險(xiǎn),子表的離散分配可能導(dǎo)致物理內(nèi)存碎片化(內(nèi)存不連續(xù)+頻繁的回收創(chuàng)建),需操作系統(tǒng)優(yōu)化分配策略
為了解決多級(jí)頁(yè)表的地址轉(zhuǎn)換需多次訪存(如四級(jí)頁(yè)表需4次內(nèi)存訪問(wèn)),導(dǎo)致延遲增加,常見(jiàn)的解決方案包括:
- TLB(快表)緩存:存儲(chǔ)最近使用的頁(yè)表項(xiàng),命中時(shí)直接獲取物理地址,減少訪存次數(shù)
- 巨型頁(yè)(Huge Page):使用2MB或1GB的頁(yè)面粒度,減少頁(yè)表層級(jí)和項(xiàng)數(shù)(大頁(yè)的使用需要操作系統(tǒng)和應(yīng)用程序的支持)
所以進(jìn)程通過(guò)頁(yè)表查詢虛擬地址和物理地址的映射關(guān)系, 首先會(huì)檢查 TLB(Translation Lookaside Buffer)高速緩存頁(yè)表項(xiàng),CPU硬件緩存.
那么這里的 TLB 是如何參與到到內(nèi)存映射的?
TLB
TLB 是內(nèi)存管理單元(MMU)的一部分,本質(zhì)是頁(yè)表的高速緩存,存儲(chǔ)最近被頻繁訪問(wèn)的頁(yè)表項(xiàng)(虛擬地址到物理地址的映射關(guān)系)的副本,是集成在 CPU 內(nèi)部的 高速緩存硬件,用于加速虛擬地址到物理地址轉(zhuǎn)換的專用緩存,通過(guò)專用電路實(shí)現(xiàn)高速地址轉(zhuǎn)換,與數(shù)據(jù)緩存(Data Cache)和指令緩存(Instruction Cache)并列,共同構(gòu)成 CPU 緩存體系
上面我們講當(dāng)進(jìn)程查詢分層頁(yè)面的映射信息會(huì)導(dǎo)致延遲增加。因此,當(dāng)缺頁(yè)異常觸發(fā)內(nèi)核分配物理內(nèi)存將從虛擬地址到物理地址的映射添加到頁(yè)表中時(shí),它還將該映射條目緩存在 TLB 硬件緩存中,通過(guò)緩存進(jìn)程最近使用的頁(yè)映射來(lái)加速地址轉(zhuǎn)換。
當(dāng)下一次查詢發(fā)生的時(shí)候,首先會(huì)在 TLB 中查詢是否有緩存,如果有的話會(huì)直接獲取,沒(méi)有的話,走上面缺頁(yè)異常的流程
進(jìn)程訪問(wèn)虛擬地址 → MMU 查詢 TLB → [命中 → 直接獲取物理地址]
│
└→ [未命中 → 查詢頁(yè)表 → 權(quán)限檢查 → 缺頁(yè)處理(可選)→ 生成物理地址 → 更新 TLB]
│
└→ 訪問(wèn)物理內(nèi)存所以 TLB 命中率直接影響程序效率。若 TLB 未命中(Miss),需通過(guò)頁(yè)表遍歷獲取物理地址,導(dǎo)致額外延遲(通常是 TLB 命中時(shí)間的數(shù)十倍),從內(nèi)存加載頁(yè)表項(xiàng),并更新TLB緩存(可能觸發(fā)條目替換,如LRU算法)
可以通過(guò) perf stat 命令來(lái)查看某一個(gè)命令或者進(jìn)程的 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,說(shuō)明所有指令訪問(wèn)均命中 TLB 緩存。
查看指定進(jìn)程的命中情況使用 -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]
└─$大頁(yè)(巨型頁(yè))
大頁(yè)是另一種解決多級(jí)頁(yè)表多次訪問(wèn)內(nèi)存的手段,顧名思義,傳統(tǒng)的內(nèi)存頁(yè)是 4KB,大于 4KB 的內(nèi)存頁(yè)被稱為大頁(yè),通過(guò)大頁(yè)可以降低多級(jí)頁(yè)表的層級(jí)
同時(shí) TLB 也有一定的局限性,存儲(chǔ)條目是固定的,當(dāng)進(jìn)程需要訪問(wèn)大量?jī)?nèi)存的時(shí)候,比如數(shù)據(jù)庫(kù)應(yīng)用,將會(huì)導(dǎo)致大量 TLB 未命中而影響性能,還是需要通過(guò)多級(jí)頁(yè)表來(lái)轉(zhuǎn)化地址,所以除了 4KB 頁(yè)面之外,Linux 內(nèi)核還通過(guò)大頁(yè)面機(jī)制支持大容量?jī)?nèi)存頁(yè)面。
通過(guò)查看 /proc/meminfo 文件確定具體系統(tǒng)的大頁(yè)大小以及使用情況,大頁(yè)分為 標(biāo)準(zhǔn)大頁(yè)(靜態(tài)大頁(yè))和透明大頁(yè)
靜態(tài)大頁(yè)其核心原理是通過(guò)增大內(nèi)存頁(yè)的尺寸(如2MB或1GB),優(yōu)化虛擬地址到物理地址的轉(zhuǎn)換效率,從而提升系統(tǒng)性能。x86 64 位架構(gòu)支持多種大頁(yè)規(guī)格,比如 4KiB,2MiB 以及 1GiB。Linux 系統(tǒng)默認(rèn)是 2MiB
需要說(shuō)明的是,大頁(yè)配置僅受用語(yǔ)支持大頁(yè)的應(yīng)用程序,對(duì)于不支持大頁(yè)的應(yīng)用程序來(lái)說(shuō)是無(wú)效的,同時(shí)大頁(yè)會(huì)導(dǎo)致內(nèi)存剩余空間變小 后面我們會(huì)介紹幾個(gè)Demo
透明大頁(yè)用于合并傳統(tǒng)內(nèi)存頁(yè)。
┌──[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)大頁(yè)的默認(rèn)大小為 2MB,這里的大頁(yè)是標(biāo)準(zhǔn)大頁(yè),若需使用 1GB 大頁(yè),需修改內(nèi)核參數(shù)配置,前提是需要CPU 支持才行
AnonHugePages: 165888 kB: 透明大頁(yè)(Transparent HugePages) 匿名頁(yè)占用的內(nèi)存總量為 165,888 KB(約 162 MB)
大部分部署數(shù)據(jù)庫(kù)的機(jī)器會(huì)禁用透明大頁(yè)?這是什么原因
透明大頁(yè)
透明大頁(yè)(Transparent Huge Pages,THP)是內(nèi)核提供的一種動(dòng)態(tài)內(nèi)存管理機(jī)制,它通過(guò)自動(dòng)將多個(gè) 4KB 小頁(yè) 合并為 2MB 或 1GB 大頁(yè),減少頁(yè)表項(xiàng)數(shù)量并提升 TLB(地址轉(zhuǎn)換緩存)命中率,從而優(yōu)化內(nèi)存訪問(wèn)性能
與需手動(dòng)預(yù)分配的靜態(tài)大頁(yè)(HugeTLB)不同,THP 對(duì)應(yīng)用程序透明且無(wú)需配置,適用于順序內(nèi)存訪問(wèn)(如大數(shù)據(jù)處理)和低實(shí)時(shí)性場(chǎng)景。但動(dòng)態(tài)合并可能引發(fā) 內(nèi)存碎片 和 性能抖動(dòng),因此對(duì)延遲敏感的數(shù)據(jù)庫(kù)(如 MySQL)或高并發(fā)系統(tǒng)建議關(guān)閉 THP
下面為透明大頁(yè)相關(guān)配置
┌──[root@liruilongs.github.io]-[~]
└─$ls /sys/kernel/mm/transparent_hugepage/
defrag enabled hpage_pmd_size khugepaged/ shmem_enabled use_zero_pageenabled 用于配置是否開(kāi)啟 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]-[~]
└─$禁用透明大頁(yè) 某些場(chǎng)景(如數(shù)據(jù)庫(kù))建議禁用 THP 以穩(wěn)定性能:
# 臨時(shí)禁用
echo never > /sys/kernel/mm/transparent_hugepage/enabled使用 grubby 更新內(nèi)核啟動(dòng)參數(shù),grubby 用于 動(dòng)態(tài)修改內(nèi)核啟動(dòng)參數(shù) 或 設(shè)置默認(rèn)內(nèi)核,無(wú)需手動(dòng)編輯配置文件
# 永久禁用
┌──[root@liruilongs.github.io]-[~]
└─$grubby --update-kernel=ALL --args="transparent_hugepage=never"
┌──[root@liruilongs.github.io]-[~]
└─$reboot確認(rèn)配置
┌──[root@liruilongs.github.io]-[~]
└─$cat /sys/kernel/mm/transparent_hugepage/enabled
always madvise [never]
┌──[root@liruilongs.github.io]-[~]
└─$再次查看透明大頁(yè)使用情況
┌──[root@liruilongs.github.io]-[~]
└─$grep AnonHugePages /proc/meminfo
AnonHugePages: 0 kB
┌──[root@liruilongs.github.io]-[~]
└─$shmem_enabled 用于配置 共享內(nèi)存(如 tmpfs、共享匿名映射)是否啟用透明大頁(yè)(THP)
┌──[root@liruilongs.github.io]-[~]
└─$cat /sys/kernel/mm/transparent_hugepage/shmem_enabled
always within_size advise [never] deny force這里的 never 表示完全禁用共享內(nèi)存的透明大頁(yè)。常用于數(shù)據(jù)庫(kù)(如 Oracle、MySQL)或高延遲敏感型應(yīng)用,避免因動(dòng)態(tài)內(nèi)存合并引發(fā)性能抖動(dòng)
透明大頁(yè)會(huì)涉及到一個(gè)進(jìn)程 khugepaged,khugepaged 是 Linux 內(nèi)核的一部分,負(fù)責(zé)處理透明大頁(yè)(Transparent HugePages, THP)的管理。透明大頁(yè)是內(nèi)核自動(dòng)將小頁(yè)合并為大頁(yè)以提升性能的機(jī)制,而 khugepaged 就是負(fù)責(zé)這個(gè)合并過(guò)程的守護(hù)進(jìn)程。自動(dòng)掃描內(nèi)存區(qū)域,尋找可以合并的小頁(yè),并嘗試將它們轉(zhuǎn)換為透明大頁(yè)。此過(guò)程在后臺(tái)靜默運(yùn)行,無(wú)需應(yīng)用程序顯式請(qǐ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它會(huì)嘗試將多個(gè)常規(guī)小頁(yè)(4KB)合并成 大頁(yè)(2MB 或 1GB),以減少頁(yè)表項(xiàng)數(shù)量,從而提升內(nèi)存訪問(wèn)性能。
控制 khugepaged 的掃描頻率,合并閾值等可以通過(guò)下面的文件修改
┌──[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)大頁(yè)
靜態(tài)大頁(yè)需要單獨(dú)配置,使用 sysctl 修改內(nèi)核參數(shù),可以設(shè)置分配的靜態(tài)大頁(yè)的數(shù)量,大頁(yè)內(nèi)存是系統(tǒng)啟動(dòng)時(shí)或通過(guò) sysctl 預(yù)先分配的,這部分內(nèi)存會(huì)被鎖定,普通進(jìn)程無(wú)法使用,所以配置需要考慮清楚
┌──[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 = 0vm.nr_hugepages:表示系統(tǒng)要預(yù)留的 大頁(yè)數(shù)量,通過(guò) -w 臨時(shí)配置內(nèi)核參數(shù),配置大頁(yè)數(shù)量為 50
┌──[root@liruilongs.github.io]-[~]
└─$sysctl -w vm.nr_hugepages=50
vm.nr_hugepages = 50確認(rèn)配置
┌──[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永久生效將配置寫(xiě)入 /etc/sysctl.conf 并執(zhí)行 sysctl -p
可以通過(guò) grub 修改內(nèi)核參數(shù)來(lái)設(shè)置大頁(yè)的數(shù)量以及大小
/etc/default/grub 是 Linux 系統(tǒng)中用于配置 GRUB(GRand Unified Bootloader) 引導(dǎo)程序的核心文件。GRUB 是大多數(shù) Linux 發(fā)行版默認(rèn)的啟動(dòng)管理器,負(fù)責(zé)在系統(tǒng)啟動(dòng)時(shí)加載內(nèi)核和初始化內(nèi)存盤(pán)(initramfs)。該文件定義了 GRUB 的全局行為和啟動(dòng)菜單的默認(rèn)選項(xiàng)。和 上面 grubby 的方式略有區(qū)別
- hugepages=N : 設(shè)置大頁(yè)的數(shù)量
- hugepagesz=N 或 default_hugepagesz=N 設(shè)置大頁(yè)大小(默認(rèn) 2MiB)
下面是一個(gè)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)核的公共啟動(dòng)參數(shù)(包括默認(rèn)內(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)大頁(yè)大小為 1G,數(shù)量為 10
需要說(shuō)明的是,大頁(yè)需要使用連續(xù)的內(nèi)存空間,盡量設(shè)置永久規(guī)則,在開(kāi)機(jī)時(shí)分配大頁(yè),如果系統(tǒng)已經(jīng)運(yùn)行了很久,大量的內(nèi)存碎片,有可能無(wú)法分配大頁(yè),因?yàn)闆](méi)有足夠的連續(xù)內(nèi)存空間。
配置 1G 的靜態(tài)大頁(yè)需要CPU 支持,檢查是否包含 pdpe1gb 標(biāo)簽
┌──[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確認(rèn)配置,可以看到 Hugepagesize 是1G,但是 nr_hugepages 大小為 5 ,并不是我們配置的 10,這是什么原因,前面我們講,靜態(tài)大頁(yè)會(huì)直接分配內(nèi)存,即只有配置就會(huì)位于常駐內(nèi)存,當(dāng)系統(tǒng)內(nèi)存沒(méi)有配置的靜態(tài)大頁(yè)大時(shí),系統(tǒng)會(huì)自動(dò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)存使用情況驗(yàn)證這一點(diǎn)
┌──[root@liruilongs.github.io]-[~]
└─$free -g
total used free shared buff/cache available
Mem: 7 5 0 0 0 1
Swap: 0 0 0通過(guò)臨時(shí)修改內(nèi)核參數(shù)調(diào)整靜態(tài)大頁(yè)數(shù)目(實(shí)際調(diào)整需要考慮靜態(tài)大頁(yè)是否使用)
┌──[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確認(rèn)配置是否生效
┌──[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]-[~]
└─$為了讓進(jìn)程可以使用大頁(yè),進(jìn)程必須進(jìn)行系統(tǒng)函數(shù)調(diào)用,可以調(diào)用 mmap() 函數(shù),或者 shmat() 函數(shù),又或者是 shmget()函數(shù)。如果進(jìn)程使用的是 mmap()系統(tǒng)函數(shù)調(diào)用,則必須掛載-個(gè) hugetlbfs 文件系統(tǒng)。
┌──[root@liruilongs.github.io]-[~]
└─$mkdir /largepage
┌──[root@liruilongs.github.io]-[~]
└─$mount -t hugetlbfs none /largepage如果在 NUMA 系統(tǒng)上,內(nèi)核將大頁(yè)劃分到所有 NUMA 節(jié)點(diǎn)上,對(duì)應(yīng)的靜態(tài)大頁(yè)參數(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]-[~]
└─$透明大頁(yè) vs 靜態(tài)大頁(yè)簡(jiǎn)單比較
特性 | 透明大頁(yè)(THP) | 靜態(tài)大頁(yè)(Huge Pages) |
配置方式 | 內(nèi)核自動(dòng)管理,無(wú)需用戶干預(yù)。 | 需手動(dòng)預(yù)留(如通過(guò) |
適用場(chǎng)景 | 通用型應(yīng)用(如 Java、Web 服務(wù))。 | 高性能計(jì)算、數(shù)據(jù)庫(kù)(如 Oracle、MySQL)。 |
內(nèi)存碎片化 | 可能因頻繁合并/拆分導(dǎo)致碎片。 | 預(yù)留固定內(nèi)存,無(wú)碎片問(wèn)題。 |
性能穩(wěn)定性 | 可能因動(dòng)態(tài)合并產(chǎn)生性能波動(dòng)。 | 性能更穩(wěn)定可控。 |




























