Linux內存結構

Linux內存結構

Node

首先, 內存被劃分爲結點. 每個結點關聯到系統中的一個處理器,內核中表示爲pg_data_t的
實例. 系統中每個節點被鏈接到一個以NULL結尾的pgdat_list鏈表中<而其中的每個節點利用pg_data_tnode_next字段鏈接到下一節.而對於PC這種UMA結構的機器來說, 只使用了一個成爲contig_page_data的靜態pg_data_t結構。

Zone

Node又被分成不同的Zone,主要用來管理不同的內存區域,那爲什麼要把Node分成Zone呢?

  1. ISA總線的直接內存存儲DMA處理器有一個嚴格的限制 :他們只能對RAM的前16MB進行尋址。
  2. 在具有大容量RAM的現代32位計算機中, CPU不能直接訪問所有的物理地址, 因爲線性地址空間太小, 內核不可能直接映射所有物理內存到線性地址空間。
    Zone的內存範圍劃分:
    Zone內存劃分
高端內存

因爲在32位機器上,內存可訪問的最大容量按理來說只有2^32=4G,但是實際上,32位的機器的內存有的並不止4G。在linux上,用戶態可訪問的內存是0-3G,內核態可訪問的內存是3G-4G,因爲邏輯地址只有4G而物理內存又超過了4G,所以就有了高端內存。在高端內存上,有128M的邏輯地址映射的物理地址是不固定的,這樣,利用這128M的不固定的邏輯地址就可以訪問到所有的物理地址。因爲在64位的機器上不存在高端內存,所以這個就不詳細說了。

Page

頁是用來存儲數據的基本單位,內存中的數據都是以頁爲單位來存儲的。

CPU模型

UMA模型

定義:均勻訪存模型(Uniform Memory Access)通常簡稱UMA,指所有的物理存儲器被均勻共享,即處理器訪問它們的時間是一樣的。UMA亦稱作統一尋址技術或統一內存存取。

這裏寫圖片描述
SMP模型中的資源(CPU、IO、內存)都是共享的,所以SMP中的任何一個環節都有可能成爲瓶頸,而最受限制的則是內存。由於每個CPU必須通過相同的內存總線訪問相同的內存資源,因此隨着CPU數量的增加,內存訪問衝突將迅速增加,最終會造成CPU資源的浪費,使 CPU性能的有效性大大降低。實驗證明,SMP服務器CPU利用率最好的情況是2至4個CPU。SMP是UMA模型的一種實現。

NUMA模型

定義:非統一內存訪問架構(Non-uniform memory access,簡稱NUMA)是一種爲多處理器的電腦設計的內存,內存訪問時間取決於內存相對於處理器的位置。在NUMA下,處理器訪問它自己的本地內存的速度比非本地內存快一些。

這裏寫圖片描述
NUMA 服務器的基本特徵是具有多個 CPU 模塊,每個 CPU 模塊由多個 CPU( 如 4 個 ) 組成,並且具有獨立的本地內存、 I/O 槽口等。由於其節點之間可以通過互聯模塊 ( 如稱爲 Crossbar Switch) 進行連接和信息交互,因此每個 CPU 可以訪問整個系統的內存 ( 這是 NUMA 系統與 MPP 系統的重要差別 ) 。顯然,訪問本地內存的速度將遠遠高於訪問遠地內存 ( 系統內其它節點的內存 ) 的速度,這也是非一致存儲訪問 NUMA 的由來。由於這個特點,爲了更好地發揮系統性能,開發應用程序時需要儘量減少不同 CPU 模塊之間的信息交互。每個CPU和一塊內存組成一個Node。

NUMA模型和Mysql的問題

由於Mysql的數據是存儲在磁盤上的,訪問磁盤的速度很慢,所以Innodb有一個很大的緩存區域叫做buffer-pool,用來緩存一些常用的熱點數據,這個區域一般都比較大,會佔用系統的至少1G的內存,當然這個可以配置,不過buffer-pool越大性能也就越好。NUMA的CPU一般會使用臨近的Node的內存,這樣速度會快一點,但是當臨近的Node的內存耗盡時,NUMA默認的策略不是去使用其他Node的內存,而是選擇swap區域,把內存的數據換出到磁盤,這樣以來mysql訪問內存的數據時就會比較慢。

分段機制

在8086處理器誕生之前,內存尋址方式就是直接訪問物理地址。8086處理器爲了尋址1M的內存空間,把地址總線擴展到了20位。但是,一個尷尬的問題出現了,ALU的寬度只有16位,也就是說,ALU不能計算20位的地址。爲了解決這個問題,分段機制被引入,登上了歷史舞臺。
爲了支持分段,8086處理器設置了四個段寄存器:CS, DS, SS, ES.每個段寄存器都是16的,同時訪問內存的指令中的地址也是16位的。但是,在送入地址總線之前,CPU先把它與某個段寄存器內的值相加。這裏要注意:段寄存器的值對應於20位地址總線的中的高16位,所以相加時實際上是16位內存地址(即段內偏移值)的高12位與段寄存器中的16位相加,而低4位保留不變,這樣就形成一個20位的實際地址,也就實現了從16位內存地址到20位實際地址的轉換,或者叫“映射”。
分段機制
實際分段機制尋址過程:
尋址過程
實際尋址過程是CPU拿到一個32位的邏輯地址,CPU會判斷這個邏輯地址是在局部段描述符表(LDT還是GDT中),從而選擇拿到寄存器中的段選擇符,再根據偏移量拿到該段的物理地址的首地址。因爲分段機制在linux也可以說沒有用到,所以頁簡單帶過。

分頁機制

分頁機制是linux的內存管理機制,所以會詳細的介紹。
分頁的基本方法是將地址空間人爲地等分成某一個固定大小的頁;每一頁大小由硬件來決定,或者是由操作系統來決定(如果硬件支持多種大小的頁)。目前,以大小爲4KB的分頁是絕大多數PC操作系統的選擇.

頁:爲了更高效和更經濟的管理內存,線性地址被分爲以固定長度爲單位的組,成爲頁。頁內部連續的線性地址空間被映射到連續的物理地址中。這樣,內核可以指定一個頁的物理地址和對應的存取權限,而不用指定全部線性地址的存取權限。這裏說頁,同時指一組線性地址以及這組地址包含的數據。
####頁框
頁框:分頁單元把所有的 RAM 分成固定長度的頁框(page frame)(有時叫做物理頁)。每一個頁框包含一個頁(page),也就是說一個頁框的長度與一個頁的長度一致。頁框是主存的一部分,因此也是一個存儲區域。區分一頁和一個頁框是很重要的,前者只是一個數據塊,可以存放在任何頁框或磁盤中。

頁表

頁表:把線性地址映射到物理地址的數據結構稱爲頁表(page table)。

尋址過程

在32位的機器上,一般會把32位劃分成三個段,就是目錄、頁表、偏移量。
在實際尋址的時候,CPU接受到一個32位的邏輯地址,根據然後從CR3寄存器中拿到Page Directory在內存中的首地址,再根據邏輯地址中的目錄拿到Page Table在內存中的首地址,跟根據邏輯地址中的Table拿到Offset Table中的首地址,最後根據偏移量拿到最終的物理地址。說了這麼多其實就說了一個重點,就是從寄存器中拿到一個首地址,再根據相應的偏移量拿到下一個表的首地址。所以目錄、頁表、偏移量都是相對的偏移量,不用死記。
尋址過程

那麼問題來了,爲什麼尋址要用三級頁表呢?爲什麼不直接用一個頁表尋址呢?
因爲如果只使用一級頁表,那麼每個頁的大小是212=4K,一級頁表的個數就是220,每個條目(也叫PDE)4個字節,那麼維護一個頁表就需要4M,每個進程都需要一個頁表來維護,如果計算機有100個進程,就需要400M的大小。如果採用多級頁表可以把20分爲兩個10位,也就是10+10+12,這樣當二級頁表裏面整個頁表都沒有使用到時,就不用建立二級頁表和三級頁表,這樣就大大節省了頁表佔用的內存空間。

64位分頁

64位機器尋址過程:
64位尋址過程
其實64位的尋址過程和32位的相比,只不過是多了幾級頁表。

頁面置換算法

https://www.cnblogs.com/fkissx/p/4712959.html

總結

在分頁機制中,我們能看出來系統採用了時間換空間的方法,因爲採用了多級頁表節省了內存空間導致每次尋址至少要進行三次,因爲每次尋址都會在內存中找到相應的頁表,最後尋址到物理內存。

PAE技術

前面提到了,32位的機器最大的能支持的內存只有2^32=4G,但是我們會常常看到一些32的機器可以加內存到8G、16G,這就是PAE技術的出現。
早期Intel處理器從80386到Pentium使用32位物理地址,理論上,這樣可以訪問4GB的RAM。然而,大型服務器需要大於4GB的RAM來同時運行數以千計的進程,近幾年來這對Intel造成了壓力,所以必須擴展32位80x86所支持的RAM容量。
Intel通過在它的處理器上把管腳數從32增加到36已經滿足了這些需求,可以尋址64GB。同時引入了一種新的分頁機制PAE(Physical Address Extension,物理地址擴展)把32位線性地址轉換爲36位物理地址才能使用所增加的物理內存,通過設置CR4的第5位來開啓對PAE的支持。引入PAE就是爲了訪問大於4GB的RAM,線性地址仍然是32位,而物理地址是36位。
PAE尋址每頁4K大小:
PAE尋址
PAE尋址每頁2M大小:
PAE尋址

爲什麼32位的邏輯地址可以映射到36位的物理地址?
因爲開啓PAE分頁的話,每個進程會保存多套頁表,這樣一來,進程可以根據需要改變CR3中的值去訪問不同的頁表。也就是說,進程在運行過程中會修改CR3的值從而指向不同的頁表來訪問不同的物理地址。

TLB技術

https://blog.csdn.net/hnwyllmm/article/details/77051135

PCID技術

因爲每個CPU有一個TLB,所以每次不同的進程被CPU選中執行的時候,TLB裏面的數據都要被刷新掉,所以剛開始進程運行的時候TLB裏面的數據都是空的,等運行時間長了,TLB纔會慢慢被填滿,TLB的命中率纔會變高。這樣一來TLB的性能就不是很好,因爲每次都要重新開始添加數據,所以就有了PCID技術。
每個進程都要一個自己的PCID來唯一表示該進程,這樣一來,TLB存儲數據的時候會加上PCID這個標識來表示該條數據是哪個進程的,這樣一來就不需要每次切換進程的時候都刷新TLB,但PCID技術在linux很後面的版本纔開始支持的,因爲PCID技術同樣存在問題。
當我們更新一個進程的TLB緩存的時候,相應的其他CPU裏面的TLB也需要做更新,這樣一來性能並沒有很好的提升。

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