當前位置: 妍妍網 > 碼農

Linux中記憶體管理詳解

2024-04-25碼農

點選上方Linux開源社群」,選擇「設為星標

優質文章,及時送達

來源:

https://blog.csdn.net/qq_40276626/article/details/120477263

Linux中記憶體管理

記憶體管理的主要工作就是對實體記憶體進行組織,然後對實體記憶體的分配和回收。但是Linux引入了虛擬地址的概念。

虛擬地址的作用

如果使用者行程直接操作實體位址會有以下的壞處:

1、 使用者行程可以直接操作內核對應的記憶體,破壞內核執行。

2、 使用者行程也會破壞其他行程的執行

CPU 中寄存器中儲存的是邏輯地址,需要進行對映才能轉化為對應的實體位址,然後獲取對應的記憶體。

透過引入邏輯地址,每個行程都擁有單獨的邏輯地址範圍。

當行程申請記憶體的時候,會為其分配邏輯地址和實體位址,並將邏輯地址和實體位址做一個對映。

所以,Linux記憶體管理涉及到了以下三個部份:

1、實體記憶體

實體記憶體的組織

Linux 中記憶體分為 3 個級別,從下到上依次為:

1 > Page: 一個 page 的大小為 4k , Page 是記憶體的一個最基本的單位。

2 > Zone: Zone 中提供了 多個佇列 來管理 page。

Zone分為 3 種

2.1、 ZONE_DMA:用來存放 DMA 讀取 IO 裝置的數據,內核專用

2.2、 ZONE_NORMAL:用來存放內核的相關數據,內核專用

2.3、 ZONE_HIGHMEM:高端記憶體,用來存放使用者行程數據

3 > Node 節點,一個 CPU 對應著一個 Node,一個 Node 包括一個 Zone_DMA、 ZONE_NORMAL、ZONE_HIGHMEM。

同時當一個 CPU 對應的記憶體用光後,可以申請其他 CPU 對應的記憶體。

實體記憶體的分配

Linux將記憶體分配分為兩種:

1 > 、大記憶體

大記憶體 利用 夥伴系統 分配。

夥伴系統的做法是將 ZONE 中的 Page 分組,然後組裝為多個連結串列。連結串列中存放的是 頁塊 的集合。頁塊對應著有不同的大小,分別為 1、2、4、8 … 1024個頁。

當請求 (2i-1 ,2i] 大小的 page 的時候,會直接請求 2i 個頁, 如果對應的連結串列中有對應的頁塊,就直接分配。如果對應的連結串列沒有,就往上找 2i+1,如果 2i+1 存在,就將其分為 2 個 2i 頁塊,將其中 1 個 2i 加入到對應的連結串列中,將另外一個分配出去。

例如,要請求一個 128 個頁的頁塊時,先檢查 128 個頁的頁塊連結串列是否有空閑塊。如果沒有,則查 256 個

的頁塊連結串列;如果有空閑塊的話,則將 256 個頁的頁塊分成兩份,一份使用,一份插入 128 個頁的頁塊連結串列中。如果還是沒有,就查 512 個頁的頁塊連結串列;如果有的話,就分裂為 128、128、256 三個頁塊,一個 128 的使用,剩余兩個插入對應頁塊連結串列。

2 > 、小記憶體分配

小記憶體分配利用 slub 分配,比如物件等數據 slub 就是 將幾個頁單獨拎出來作為緩存,裏面維護了連結串列。每次直接從連結串列中獲取對應的記憶體,用完之後也不用清空,就直接掛到連結串列上,然後等待下次利用。

2、如何組織虛擬地址

虛擬地址對應的是虛擬空間,虛擬空間只不過是一個虛擬地址的集合,用來對映實體記憶體。


虛擬空間分為 使用者態 內核態

32位元系統中 將虛擬空間按照 1:3 的比例分配給 內核態 使用者態。

64位元系統中 分別給 內核態 和 使用者態 分配了 128T。

使用者態結構

每個行程 都會 對應一個 使用者態虛擬空間, 裏面存放了 Text(程式碼)的記憶體虛擬地址範圍、 Data(數據)的記憶體虛擬地址範圍、BSS(全域變量)的記憶體虛擬地址範圍、堆的虛擬地址範圍、棧的虛擬地址範圍,以及mmap 記憶體對映區。

其中 mmap 用於申請動態記憶體的時候的對映,堆和棧都是動態變化的。

一個行程對應的使用者態中的 各個方面的虛擬地址資訊都透過一個 struct 來儲存在記憶體中,當建立行程的時候會為其分配記憶體儲存對應的虛擬地址資訊。

內核態結構

Linux 的內核程式共用一個內核態虛擬空間。其中分為了以下幾部份:

1、直接對映區

896M,內核空間直接對映到對應的ZONE_DMA和ZONE_NORMAL中。為什麽叫做直接對映呢?邏輯地址 直接 減去對應的差值就可以得到對應的實體位址。固定死了。微信搜尋公眾號:架構師指南,回復:架構師 領取資料 。

2、動態對映

為什麽要引入動態對映呢?因為所有實體記憶體的分配都需要內核程式進行申請,使用者行程沒有這個許可權。所以內核空間一定要能對映到所有的實體記憶體地址。

那麽如果都采用直接對映的話,1G大小邏輯地址的內核空間只能對映1G大小的實體記憶體。

所以引入了動態對映,動態對映就是 內核空間的邏輯地址可以對映到 實體記憶體中的ZONE_HIGHMEM(高端記憶體)中的任何一個地址,並且在對應的實體記憶體使用完之後,可以再對映其他實體記憶體地址。

動態對映分為三種:

1 > 、動態記憶體對映: 使用完對應的實體記憶體後,就可以對映其他實體記憶體了。

2 > 、永久記憶體對映: 一個虛擬地址只能對映一個實體位址。如果需要對映其他實體位址,需要解綁。

3 > 、固定記憶體對映: 只能被某些特定的函式來呼叫參照實體位址。

動態記憶體對映和直接對映的區別

動態對映和直接對映的區別就是邏輯地址到實體位址的轉化規則。

直接對映

直接對映的規則是死的,一個邏輯地址對應的實體位址是固定的。透過邏輯地址加或者減去一個數,就可以得到對應的實體位址。

動態對映

動態對映是動態的繫結,每個邏輯地址對應的實體位址是動態的,透過頁表進行查詢。

使用者空間對映:

使用者空間采用 動態對映 ,每個虛擬地址可以被對映到一個實體位址,對映到ZONE_HIGHMEM。

為什麽使用者空間不采用 直接對映 呢?

因為實體記憶體是多個行程所有的,每個行程都有一個使用者空間。如果采用直接對映的話,對應的實體位址是會沖突的。其使用者空間的邏輯地址大小都為 3G,所以存在邏輯地址相同,但是對應的實體位址不同。需要透過頁表來轉化,一個行程會對應一個頁表。

3、如何將虛擬地址對映到實體記憶體

虛擬地址透過 頁表 虛擬地址 轉化為 實體位址, 每個行程都對應著一個頁表, 內核只有一個頁表。

虛擬空間 和 實體記憶體 都按照 4k 來分頁,一個虛擬空間中的頁 和 實體記憶體中頁 是 一一對應的。

頁表對映

如上圖所示,將虛擬地址中的頁號 透過頁表轉化為 對應的物理頁號,然後透過頁內偏移量 就可以得到對應的 實體位址了。

但是 1 個行程就需要一個頁表,一個 4G 的記憶體條,就需要 1M 個頁表記錄來描述,假如 1 個 頁表記錄需要 4個字節,那麽就需要 4MB。而且頁表記錄是透過下標來對應的,透過虛擬頁號來乘以對應的頁表項大小來計算得到對應的地址的。

所以 Linux 將 4M 分為 1K 個 4K, 一個 4K 對應著一個 page,用來儲存對應的真正的頁表記錄。將 1K 個 page 分開存放,就不要求連續的 4M 了。

如果將 4M 分成 1K 個離散的 page 的話,怎麽虛擬地址對應的頁表號呢?

利用指標,儲存 1K 個地址,分別指向這 1K 個 page, 地址的大小為 4 個字節,也就是32位元,完全可以表示整個記憶體的地址範圍。

1K * 4個字節,正好是一個 page 4k,所以 也就是利用 1 個 page來儲存對應的頁表記錄索引。

所以 我們的虛擬地址尋找過程如下:

1>、找到對應的頁表記錄索引位置,因為有 1K 個索引,所以用 10 位就可以表示了

2 > 、透過索引可以找到對應的真正的頁表地址,對應的有 1K 個頁表記錄,所以用 10 位就可以表示了

3 > 、1個頁有 4K,透過 12 位就可以表示其頁內偏移量了。

所以虛擬地址被分為了三部份:

1 > 、10位 表示索引偏移

2 > 、10位 表示頁表記錄偏移

3 > 、 12位元 表示頁內偏移

雖然這種方式增加了索引項,進一步增加了記憶體,但是減少了連續記憶體的使用,透過離散的記憶體就可以儲存頁表。

這是對於32位元系統,而 64 位系統采用了5級頁表。

對映流程圖

使用者態申請記憶體時,只會申請對應的虛擬地址,不會直接為其分配實體記憶體,而是等到真正存取記憶體的時候,產生缺頁中斷,然後內核才會為其分配,然後為其建立對映,也就是建立對應的頁表項。

TLB

TLB 就是一個緩存,放在 CPU 中。用來將虛擬地址和對應的實體位址進行緩存。當查詢對應的實體位址的時候,首先查詢 TLB,如果TLB中存在對應的記錄,就直接返回。如果不存在,就再去查詢頁表。

虛擬記憶體

虛擬記憶體 指的是 將硬碟中劃出一段 swap 分區 當作 虛擬的記憶體,用來存放記憶體中暫時用不到的記憶體頁,等到需要的時候再從 swap 分區中 將對應的 記憶體頁 調入到 記憶體中。硬碟此時相當於一個虛擬的記憶體。

從邏輯上能夠執行更大記憶體的程式,因為程式執行的時候並不需要把所有數據都載入到記憶體中,只需要將當前執行必要的相關程式和數據載入到記憶體中就可以了,當需要其他數據和程式的時候,再將其調入。

相較於真正的記憶體載入,虛擬記憶體需要將數據在記憶體和磁盤中不斷切換,這是一個耗時的操作,所以速度比不上真正的記憶體載入。

總結

虛擬空間 和 實體記憶體 都分為 內核空間 和 使用者空間。

虛擬地址需要透過頁表轉化為實體位址,然後才能存取。

使用者虛擬空間 只能對映 實體記憶體中的使用者記憶體,無法對映到實體記憶體中的內核記憶體,也就是說,使用者行程只能操作使用者記憶體。

內核空間 只能被 內核 申請使用,使用者行程只能操作使用者空間的實體記憶體和虛擬空間。

當使用者行程 呼叫系統呼叫的時候,會將其對應的程式碼和數據執行在內核空間中。

所以當呼叫 內核空間 讀取檔或者網路數據的時候,首先會將數據拷貝到記憶體空間,然後在將數據從內核空間拷貝到使用者空間。因為 使用者行程不能存取內核空間。

-End-

讀到這裏說明你喜歡本公眾號的文章,歡迎 置頂(標星)本公眾號 Linux技術迷,這樣就可以第一時間獲取推播了~

本公眾號,後台回復:Linux,領取2T學習資料 !

1. 

2. 

3.

4.