為什么 Linux 默認(rèn)頁大小是 4KB
為什么這么設(shè)計(jì)(Why’s THE Design)是一系列關(guān)于計(jì)算機(jī)領(lǐng)域中程序設(shè)計(jì)決策的文章,我們?cè)谶@個(gè)系列的每一篇文章中都會(huì)提出一個(gè)具體的問題并從不同的角度討論這種設(shè)計(jì)的優(yōu)缺點(diǎn)、對(duì)具體實(shí)現(xiàn)造成的影響。如果你有想要了解的問題,可以在文章下面留言。
我們都知道 Linux 會(huì)以頁為單位管理內(nèi)存,無論是將磁盤中的數(shù)據(jù)加載到內(nèi)存中,還是將內(nèi)存中的數(shù)據(jù)寫回磁盤,操作系統(tǒng)都會(huì)以頁面為單位進(jìn)行操作,哪怕我們只向磁盤中寫入一個(gè)字節(jié)的數(shù)據(jù),我們也需要將整個(gè)頁面中的全部數(shù)據(jù)刷入磁盤中。
Linux 同時(shí)支持正常大小的內(nèi)存頁和大內(nèi)存頁(Huge Page)[^1],絕大多數(shù)處理器上的內(nèi)存頁的默認(rèn)大小都是 4KB,雖然部分處理器會(huì)使用 8KB、16KB 或者 64KB 作為默認(rèn)的頁面大小,但是 4KB 的頁面仍然是操作系統(tǒng)默認(rèn)內(nèi)存頁配置的主流;除了正常的內(nèi)存頁大小之外,不同的處理器上也包含不同大小的大頁面,我們?cè)?x86 處理器上就可以使用 2MB 的內(nèi)存頁。
4KB 的內(nèi)存頁其實(shí)是一個(gè)歷史遺留問題,在上個(gè)世紀(jì) 80 年代確定的 4KB 一直保留到了今天。雖然今天的硬件比過去豐富了很多,但是我們?nèi)匀谎赜昧诉^去主流的內(nèi)存頁大小。如下圖所示,裝過機(jī)的人應(yīng)該對(duì)這里的內(nèi)存條非常熟悉:

在今天,4KB 的內(nèi)存頁大小可能不是最佳的選擇,8KB 或者 16KB 說不定是更好的選擇,但是這是過去在特定場(chǎng)景下做出的權(quán)衡。我們?cè)谶@篇文章中不要過于糾結(jié)于 4KB 這個(gè)數(shù)字,應(yīng)該更重視決定這個(gè)結(jié)果的幾個(gè)因素,這樣當(dāng)我們?cè)谟龅筋愃茍?chǎng)景時(shí)才可以從這些方面考慮當(dāng)下最佳的選擇,我們?cè)谶@篇文章中會(huì)介紹以下兩個(gè)影響內(nèi)存頁大小的因素,它們分別是:
過小的頁面大小會(huì)帶來較大的頁表項(xiàng)增加尋址時(shí) TLB(Translation lookaside buffer)的查找速度和額外開銷; 過大的頁面大小會(huì)浪費(fèi)內(nèi)存空間,造成內(nèi)存碎片,降低內(nèi)存的利用率;
上個(gè)世紀(jì)在設(shè)計(jì)內(nèi)存頁大小時(shí)充分考慮了上述的兩個(gè)因素,最終選擇了 4KB 的內(nèi)存頁作為操作系統(tǒng)最常見的頁大小,我們接下來將詳細(xì)介紹以上它們對(duì)操作系統(tǒng)性能的影響。
頁表項(xiàng)
我們?cè)?為什么 Linux 需要虛擬內(nèi)存 一文中曾經(jīng)介紹過 Linux 中的虛擬內(nèi)存,每個(gè)進(jìn)程能夠看到的都是獨(dú)立的虛擬內(nèi)存空間,虛擬內(nèi)存空間只是邏輯上的概念,進(jìn)程仍然需要訪問虛擬內(nèi)存對(duì)應(yīng)的物理內(nèi)存,從虛擬內(nèi)存到物理內(nèi)存的轉(zhuǎn)換就需要使用每個(gè)進(jìn)程持有頁表。
為了存儲(chǔ) 64 位操作系統(tǒng)中 128 TiB 虛擬內(nèi)存的映射數(shù)據(jù),Linux 在 2.6.10 中引入了四層的頁表輔助虛擬地址的轉(zhuǎn)換[^2],在 4.11 中引入了五層的頁表結(jié)構(gòu)[^3],在未來還可能會(huì)引入更多層的頁表結(jié)構(gòu)以支持 64 位的虛擬地址。

在如上圖所示的四層頁表結(jié)構(gòu)中,操作系統(tǒng)會(huì)使用最低的 12 位作為頁面的偏移量,剩下的 36 位會(huì)分四組分別表示當(dāng)前層級(jí)在上一層中的索引,所有的虛擬地址都可以用上述的多層頁表查找到對(duì)應(yīng)的物理地址[^4]。
因?yàn)椴僮飨到y(tǒng)的虛擬地址空間大小都是一定的,整片虛擬地址空間被均勻分成了 N 個(gè)大小相同的內(nèi)存頁,所以內(nèi)存頁的大小最終會(huì)決定每個(gè)進(jìn)程中頁表項(xiàng)的層級(jí)結(jié)構(gòu)和具體數(shù)量,虛擬頁的大小越小,單個(gè)進(jìn)程中的頁表項(xiàng)和虛擬頁也就越多。
因?yàn)槟壳暗奶摂M頁大小為 4096 字節(jié),所以虛擬地址末尾的 12 位可以表示虛擬頁中的地址,如果虛擬頁的大小降到了 512 字節(jié),那么原本的四層頁表結(jié)構(gòu)或者五層頁表結(jié)構(gòu)會(huì)變成五層或者六層,這不僅會(huì)增加內(nèi)存訪問的額外開銷,還會(huì)增加每個(gè)進(jìn)程中頁表項(xiàng)占用的內(nèi)存大小。
碎片化
因?yàn)閮?nèi)存映射設(shè)備會(huì)在內(nèi)存頁的層面工作,所以操作系統(tǒng)認(rèn)為內(nèi)存分配的最小單元就是虛擬頁。哪怕用戶程序只是申請(qǐng)了 1 字節(jié)的內(nèi)存,操作系統(tǒng)也會(huì)為它申請(qǐng)一個(gè)虛擬頁,如下圖所示,如果內(nèi)存頁的大小為 24KB,那么申請(qǐng) 1 字節(jié)的內(nèi)存會(huì)浪費(fèi) ~99.9939% 的空間。

隨著內(nèi)存頁大小的增加,內(nèi)存的碎片化情況會(huì)越來越嚴(yán)重,小的內(nèi)存頁會(huì)減少內(nèi)存空間中的內(nèi)存碎片,提高內(nèi)存的利用率。上個(gè)世紀(jì)的內(nèi)存資源還沒有像今天這么豐富,在大多數(shù)情況下,內(nèi)存都不是限制程序運(yùn)行的資源,多數(shù)的在線服務(wù)都需要更多的CPU,而不是更多的內(nèi)存。不過在上個(gè)世紀(jì)內(nèi)存其實(shí)也是稀缺資源,所以提高稀缺資源的利用率是我們不得不考慮的事情:

上個(gè)世紀(jì)八九十年代的內(nèi)存條只有 512KB 或者 2MB,價(jià)格也貴得離譜,但是幾 GB 的內(nèi)存在今天卻非常常見[^8],所以雖然內(nèi)存的利用率仍然十分重要,但是在內(nèi)存的價(jià)格大幅降低的今天,碎片化的內(nèi)存不再是需要解決的關(guān)鍵問題了。
除了內(nèi)存的利用率之外,較大的內(nèi)存頁也會(huì)增加內(nèi)存拷貝時(shí)的額外開銷,因?yàn)?Linux 上的寫時(shí)拷貝機(jī)制,在多個(gè)進(jìn)程共享同一塊內(nèi)存時(shí),當(dāng)其中的一個(gè)進(jìn)程修改了共享的虛擬內(nèi)存會(huì)觸發(fā)內(nèi)存頁的拷貝,這時(shí)操作系統(tǒng)的內(nèi)存頁越小,寫時(shí)拷貝帶來的額外開銷也就越小。
總結(jié)
就像我們?cè)谏厦嫣岬降模?KB 的內(nèi)存頁是上個(gè)世紀(jì)決定的默認(rèn)設(shè)置,從今天的角度來看,這很可能已經(jīng)是錯(cuò)誤的選擇了,arm64、ia64 等架構(gòu)已經(jīng)可以支持 8KB、16KB 等大小的內(nèi)存頁,隨著內(nèi)存的價(jià)格變得越來越低、系統(tǒng)的內(nèi)存變得越來越大,更大的內(nèi)存可能是操作系統(tǒng)更好的選擇,我們重新回顧一下兩個(gè)決定內(nèi)存頁大小的要素:
過小的頁面大小會(huì)帶來較大的頁表項(xiàng)增加尋址時(shí) TLB(Translation lookaside buffer)的查找速度和額外開銷,但是也會(huì)減少程序中的內(nèi)存碎片,提高內(nèi)存的利用率; 過大的頁面大小會(huì)浪費(fèi)內(nèi)存空間,造成內(nèi)存碎片,降低內(nèi)存的利用率,但是可以較少進(jìn)程中的頁表項(xiàng)以及 TLB 的尋址時(shí)間;
這種類似的場(chǎng)景在我們做系統(tǒng)設(shè)計(jì)時(shí)也比較常見,舉一個(gè)不是特別恰當(dāng)?shù)睦樱?dāng)我們想要在集群上部署服務(wù)時(shí),每個(gè)節(jié)點(diǎn)上的資源是有限的,單個(gè)服務(wù)占用的資源可能會(huì)影響集群的資源利用率或者系統(tǒng)的額外開銷。如果我們?cè)诩褐胁渴?32 個(gè)占用 1 CPU 的服務(wù),那么可以充分利用集群中的資源,但是如此多的實(shí)例數(shù)會(huì)帶來較大的額外開銷;如果我們?cè)诩褐胁渴?4 個(gè)占用 8 CPU 的服務(wù),那么這些服務(wù)的額外開銷雖然很小,但是可能會(huì)在節(jié)點(diǎn)中留下很多空隙。到最后,我們還是來看一些比較開放的相關(guān)問題,有興趣的讀者可以仔細(xì)思考一下下面的問題:
Linux 中的扇區(qū)、塊和頁都有什么區(qū)別和聯(lián)系? Linux 中的塊大小是如何決定的?常見的大小有哪些?
如果對(duì)文章中的內(nèi)容有疑問或者想要了解更多軟件工程上一些設(shè)計(jì)決策背后的原因,可以在博客下面留言,作者會(huì)及時(shí)回復(fù)本文相關(guān)的疑問并選擇其中合適的主題作為后續(xù)的內(nèi)容。
推薦閱讀
站長(zhǎng) polarisxu
自己的原創(chuàng)文章
不限于 Go 技術(shù)
職場(chǎng)和創(chuàng)業(yè)經(jīng)驗(yàn)
Go語言中文網(wǎng)
每天為你
分享 Go 知識(shí)
Go愛好者值得關(guān)注
