為什麼 Linux 預設頁大小是 4KB

draveness發表於2020-06-09

我們都知道 Linux 會以頁為單位管理記憶體,無論是將磁碟中的資料載入到記憶體中,還是將記憶體中的資料寫回磁碟,作業系統都會以頁面為單位進行操作,哪怕我們只向磁碟中寫入一個位元組的資料,我們也需要將整個頁面中的全部資料刷入磁碟中。

Linux 同時支援正常大小的記憶體頁和大記憶體頁(Huge Page),絕大多數處理器上的記憶體頁的預設大小都是 4KB,雖然部分處理器會使用 8KB、16KB 或者 64KB 作為預設的頁面大小,但是 4KB 的頁面仍然是作業系統預設記憶體頁配置的主流;除了正常的記憶體頁大小之外,不同的處理器上也包含不同大小的大頁面,我們在 x86 處理器上就可以使用 2MB 的記憶體頁。

4KB 的記憶體頁其實是一個歷史遺留問題,在上個世紀 80 年代確定的 4KB 一直保留到了今天。雖然今天的硬體比過去豐富了很多,但是我們仍然沿用了過去主流的記憶體頁大小。如下圖所示,裝過機的人應該對這裡的記憶體條非常熟悉:

圖 1 – 隨機存取記憶體

在今天,4KB 的記憶體頁大小可能不是最佳的選擇,8KB 或者 16KB 說不定是更好的選擇,但是這是過去在特定場景下做出的權衡。我們在這篇文章中不要過於糾結於 4KB 這個數字,應該更重視決定這個結果的幾個因素,這樣當我們在遇到類似場景時才可以從這些方面考慮當下最佳的選擇,我們在這篇文章中會介紹以下兩個影響記憶體頁大小的因素,它們分別是:

  • 過小的頁面大小會帶來較大的頁表項增加定址時 TLB(Translation lookaside buffer)的查詢速度和額外開銷;
  • 過大的頁面大小會浪費記憶體空間,造成記憶體碎片,降低記憶體的利用率;

上個世紀在設計記憶體頁大小時充分考慮了上述的兩個因素,最終選擇了 4KB 的記憶體頁作為作業系統最常見的頁大小,我們接下來將詳細介紹以上它們對作業系統效能的影響。

頁表項

我們在 為什麼 Linux 需要虛擬記憶體 一文中曾經介紹過 Linux 中的虛擬記憶體,每個程式能夠看到的都是獨立的虛擬記憶體空間,虛擬記憶體空間只是邏輯上的概念,程式仍然需要訪問虛擬記憶體對應的實體記憶體,從虛擬記憶體到實體記憶體的轉換就需要使用每個程式持有頁表。

為了儲存 64 位作業系統中 128 TiB 虛擬記憶體的對映資料,Linux 在 2.6.10 中引入了四層的頁表輔助虛擬地址的轉換,在 4.11 中引入了五層的頁表結構,在未來還可能會引入更多層的頁表結構以支援 64 位的虛擬地址。

圖 2 – 四層頁表結構

在如上圖所示的四層頁表結構中,作業系統會使用最低的 12 位作為頁面的偏移量,剩下的 36 位會分四組分別表示當前層級在上一層中的索引,所有的虛擬地址都可以用上述的多層頁表查詢到對應的實體地址。

因為作業系統的虛擬地址空間大小都是一定的,整片虛擬地址空間被均勻分成了 N 個大小相同的記憶體頁,所以記憶體頁的大小最終會決定每個程式中頁表項的層級結構和具體數量,虛擬頁的大小越小,單個程式中的頁表項和虛擬頁也就越多。

因為目前的虛擬頁大小為 4096 位元組,所以虛擬地址末尾的 12 位可以表示虛擬頁中的地址,如果虛擬頁的大小降到了 512 位元組,那麼原本的四層頁表結構或者五層頁表結構會變成五層或者六層,這不僅會增加記憶體訪問的額外開銷,還會增加每個程式中頁表項佔用的記憶體大小。

碎片化

因為記憶體對映裝置會在記憶體頁的層面工作,所以作業系統認為記憶體分配的最小單元就是虛擬頁。哪怕使用者程式只是申請了 1 位元組的記憶體,作業系統也會為它申請一個虛擬頁,如下圖所示,如果記憶體頁的大小為 24KB,那麼申請 1 位元組的記憶體會浪費 ~99.9939% 的空間。

圖 3 – 大記憶體的碎片化

隨著記憶體頁大小的增加,記憶體的碎片化嚴情況會越來越嚴重,小的記憶體頁會減少記憶體空間中的記憶體碎片,提高記憶體的利用率。上個世紀的記憶體資源還沒有像今天這麼豐富,在大多數情況下,記憶體都不是限制程式執行的資源,多數的線上服務都需要更多的CPU,而不是更多的記憶體。不過在上個世紀記憶體其實也是稀缺資源,所以提高稀缺資源的利用率是我們不得不考慮的事情:

圖 4 – 記憶體的價格

上個世紀八九十年代的記憶體條只有 512KB 或者 2MB,價格也貴得離譜,但是幾 GB 的記憶體在今天卻非常常見5,所以雖然記憶體的利用率仍然十分重要,但是在記憶體的價格大幅降低的今天,碎片化的記憶體不再是需要解決的關鍵問題了。

除了記憶體的利用率之外,較大的記憶體頁也會增加記憶體拷貝時的額外開銷,因為 Linux 上的寫時拷貝機制,在多個程式共享同一塊記憶體時,當其中的一個程式修改了共享的虛擬記憶體會觸發記憶體頁的拷貝,這時作業系統的記憶體頁越小,寫時拷貝帶來的額外開銷也就越小。

總結

就像我們在上面提到的,4KB 的記憶體頁是上個世紀決定的預設設定,從今天的角度來看,這很可能已經是錯誤的選擇了,arm64、ia64 等架構已經可以支援 8KB、16KB 等大小的記憶體頁,隨著記憶體的價格變得越來越低、系統的記憶體變得越來越大,更大的記憶體可能是作業系統更好的選擇,我們重新回顧一下兩個決定記憶體頁大小的要素:

  • 過小的頁面大小會帶來較大的頁表項增加定址時 TLB(Translation lookaside buffer)的查詢速度和額外開銷,但是也會減少程式中的記憶體碎片,提高記憶體的利用率;
  • 過大的頁面大小會浪費記憶體空間,造成記憶體碎片,降低記憶體的利用率,但是可以較少程式中的頁表項以及 TLB 的定址時間;

這種類似的場景在我們做系統設計時也比較常見,舉一個不是特別恰當的例子,當我們想要在叢集上部署服務時,每個節點上的資源是有限的,單個服務佔用的資源可能會影響叢集的資源利用率或者系統的額外開銷。如果我們在叢集中部署 32 個佔用 1 CPU 的服務,那麼可以充分利用叢集中的資源,但是如此多的例項數會帶來較大的額外開銷;如果我們在叢集中部署 4 個佔用 8 CPU 的服務,那麼這些服務的額外開銷雖然很小,但是可能會在節點中留下很多空隙。到最後,我們還是來看一些比較開放的相關問題,有興趣的讀者可以仔細思考一下下面的問題:

  • Linux 中的扇區、塊和頁都有什麼區別和聯絡?
  • Linux 中的塊大小是如何決定的?常見的大小有哪些?

相關文章