首頁  >  文章  >  運維  >  Linux的記憶體管理介紹

Linux的記憶體管理介紹

青灯夜游
青灯夜游轉載
2019-03-28 13:52:213559瀏覽

這篇文章帶給大家的內容是介紹Linux的記憶體管理,讓大家了解Linux記憶體管理的相關知識。有一定的參考價值,有需要的朋友可以參考一下,希望對你有幫助。

在linux下,使用top,vmstat,free等指令查看系統或進程的記憶體使用情況時,經常看到buff/cache memeory,swap,avail Mem等,他們都代表什麼意思呢?這篇文章將來聊聊Linux下的記憶體管理並解答這個問題。

討論Linux下的記憶體管理其實就是討論Linux下虛擬記憶體的實作方式,本人不是核心專家,所以這篇文章只會介紹一些概念性的東西,不會深入實作細節,有些地方描述的有可能不精確。

在早些時候,物理記憶體比較有限,人們希望程式可以使用的記憶體空間能超過實際實體內存,於是出現了虛擬記憶體的概念,不過隨著時間的推移,虛擬記憶體的意義已經遠遠的超過了最初的想法。

一、虛擬記憶體

虛擬記憶體是Linux管理記憶體的技術。它使得每個應用程式都認為自己擁有獨立且連續的可用的記憶體空間(連續完整的位址空間),而實際上,它通常是被映射到多個物理記憶體段,還有部分暫時儲存在外部磁碟記憶體上,在需要時再載入到記憶體中。

每個行程所能使用的虛擬位址大小和CPU位數有關,在32位元的系統上,虛擬位址空間大小是4G,在64位元系統上,是2^64=? (算不過來了)。而實際的實體記憶體可能遠小於虛擬位址空間的大小。

虛擬位址和進程息息相關,不同進程裡的同一個虛擬位址指向的實體位址不一定一樣,所以離開進程談虛擬位址沒有任何意義。

注意:網路上很多文章將虛擬記憶體等同於交換空間,其實描述不夠嚴謹,交換空間只是虛擬記憶體這張大藍圖中的一部分。

二、虛擬記憶體和實體記憶體的關係

下面這張表很直觀的表述了它們之間的關係

  进程X                                                                      进程Y
+-------+                                                                  +-------+
| VPFN7 |--+                                                               | VPFN7 |
+-------+  |       进程X的                                 进程Y的           +-------+
| VPFN6 |  |      Page Table                              Page Table     +-| VPFN6 |
+-------+  |      +------+                                +------+       | +-------+
| VPFN5 |  +----->| .... |---+                    +-------| .... |<---+  | | VPFN5 |
+-------+         +------+   |        +------+    |       +------+    |  | +-------+
| VPFN4 |    +--->| .... |---+-+      | PFN4 |    |       | .... |    |  | | VPFN4 |
+-------+    |    +------+   | |      +------+    |       +------+    |  | +-------+
| VPFN3 |--+ |    | .... |   | | +--->| PFN3 |<---+  +----| .... |<---+--+ | VPFN3 |
+-------+  | |    +------+   | | |    +------+       |    +------+    |    +-------+
| VPFN2 |  +-+--->| .... |---+-+-+    | PFN2 |<------+    | .... |    |    | VPFN2 |
+-------+    |    +------+   | |      +------+            +------+    |    +-------+
| VPFN1 |    |               | +----->| FPN1 |                        +----| VPFN1 |
+-------+    |               |        +------+                             +-------+
| VPFN0 |----+               +------->| PFN0 |                             | VPFN0 |
+-------+                             +------+                             +-------+
 虚拟内存                               物理内存                               虚拟内存


PFN(the page frame number): 页编号

當進程執行一個程式時,需要先從先記憶體中讀取該進程的指令,然後執行,獲取指令時用到的就是虛擬位址,這個位址是程式連結時確定的(核心載入並初始化進程時會調整動態庫的位址範圍),為了取得到實際的數據,CPU需要將虛擬位址轉換成物理位址,CPU轉換位址時需要用到進程的page table,而page table裡面的資料則由作業系統維護。

注意:Linux核心程式碼存取記憶體時用的都是實際的實體位址,所以不存在虛擬位址到實體位址的轉換,只有應用層程式才需要。

為了轉換方便,Linux將虛擬記憶體和實體記憶體都拆分為固定大小的頁,x86的系統一般記憶體頁大小是4K,每個頁都會分配一個唯一的編號,這就是頁編號(PFN).

從上面的圖中可以看出,虛擬記憶體和實體記憶體的page之間透過page table映射。進程X和Y的虛擬記憶體是相互獨立的,且page table也是獨立的,它們之間共享實體記憶體。進程可以隨便存取自己的虛擬位址空間,而page table和實體記憶體則由核心維護。當進程需要存取記憶體時,CPU會根據進程的page table將虛擬位址翻譯成實體位址,然後進行存取。

注意:並不是每個虛擬位址空間的page都有對應的Page Table相關聯,只有虛擬位址被分配給進程後,也即進程呼叫類似malloc函數之後,系統才會為對應的虛擬位址在Page Table中新增記錄,如果進程存取一個沒有和Page Table關聯的虛擬位址,系統將會拋出SIGSEGV訊號,導致進程退出,這也是為什麼我們存取野指標時會經常出現segmentfault的原因。換句話說,雖然每個行程都有4G(32位元系統)的虛擬位址空間,但只有向系統申請了的那些位址空間才能用,存取未分配的位址空間將會出segmentfault錯誤。 Linux會將虛擬位址0不對應到任何地方,這樣我們存取空指標就一定會報segmentfault錯誤。

三、虛擬記憶體的優點

# ● 更大的位址空間:並且是連續的,使得程式編寫、鏈接更簡單

 ● 進程隔離:不同進程的虛擬位址之間沒有關係,所以一個進程的操作不會對其它進程造成影響

 ● 資料保護:每塊虛擬記憶體都有對應的讀寫屬性,這樣就能保護程式的程式碼段不被修改,資料塊不能被執行等,增加了系統的安全性

 ● 記憶體映射:有了虛擬記憶體之後,可以直接映射磁碟上的檔案(可執行檔或動態庫)到虛擬位址空間,這樣可以做到實體記憶體延時分配,只有在需要讀取對應的文件的時候,才將它真正的從磁碟上加載到內存中來,而在內存吃緊的時候又可以將這部分內存清空掉,提高物理內存利用效率,並且所有這些對應用程序來說是都透明的

 ● 共享記憶體:例如動態庫,只要在記憶體中儲存一份就可以了,然後將它映射到不同進程的虛擬位址空間中,讓進程覺得自己獨佔了這個檔案。進程間的記憶體共享也可以透過映射同一塊實體記憶體到進程的不同虛擬位址空間來實現共享

 ● 物理記憶體管理:物理位址空間全部由作業系統管理,進程無法直接分配和回收,從而係統可以更好的利用內存,平衡進程間對內存的需求

 ● 其它:有了虛擬地址空間後,交換空間和COW(copy on write)等功能都能很方便的實現

四、page table

page table可以簡單的理解為一個memory mapping的鍊錶(當然實際結構很複雜),裡面的每個memory mapping都將一塊虛擬位址對應到一個特定的資源(實體記憶體或外部儲存空間)。每個進程擁有自己的page table,和其它進程的page table都沒有關係。

五、memory mapping

#每個memory mapping就是一段虛擬記憶體的描述,包括虛擬位址的起始位置,長度,權限(例如這段記憶體裡的資料是否可讀、寫入、執行), 以及關聯的資源(如實體記憶體page,swap空間上的page,磁碟上的檔案內容等)。

當進程申請記憶體時,系統將傳回虛擬記憶體位址,同時為對應的虛擬記憶體建立memory mapping並將它放入page table,但這時系統不一定會分配對應的實體內存,系統一般會在進程真正存取這段記憶體的時候才會分配實體記憶體並關聯到對應的memory mapping,這就是所謂的延時分配/按需分配。

每個memory mapping都有一個標記,用來表示所關聯的物理資源類型,一般分兩大類,那就是anonymous和file backed,在這兩大類中,又分了一些小類,例如anonymous下面有更具體的shared和copy on write類型, file backed下面有更具體的device backed類型。以下是每個類型所代表的意思:

file backed

這種類型表示memory mapping對應的物理資源存放在磁碟上的檔案中,它所包含的資訊包括檔案的位置、offset、rwx權限等。

當進程第一次存取對應的虛擬page的時候,由於在memory mapping中找不到對應的物理內存,CPU會報page fault中斷,然後操作系統就會處理這個中斷並將文件的內容載入到實體記憶體中,然後更新memory mapping,這樣下次CPU就能存取這塊虛擬位址了。以這種方式載入到記憶體的資料一般都會放到page cache中,關於page cache會在後面介紹到.

一般程式的可執行文件,動態函式庫都是以這種方式映射到進程的虛擬位址空間的。

device backed

和file backed類似,只是後端映射到了磁碟的物理位址,例如當物理記憶體被swap out後,將被標記為device backed 。

anonymous

程式自己用到的資料段和堆疊空間,以及透過mmap分配的共享內存,它們在磁碟上找不到對應的文件,所以這部分內存頁被叫做anonymous page。 anonymous page和file backed最大的差異是當內存吃緊時,系統會直接刪除掉file backed對應的物理內存,因為下次需要的時候還能從磁碟加載到內存,但anonymous page不能被刪除,只能被swap out。

shared

不同行程的Page Table裡面的多個memory mapping可以對應到相同的實體位址,透過虛擬位址(不同行程裡的虛擬位址可能不一樣)可以存取相同的內容,當一個進程裡面修改記憶體的內容後,在另一個進程中可以立即讀取。這種方式一般用來實現進程間高速的共享資料(如mmap)。當標記為shared的memory mapping刪除回收時,需要更新實體page上的參考計數,以便於實體page的計數變0後回收。

copy on write

copy on write基於shared技術,當讀這種類型的記憶體時,系統不需要做任何特殊的操作,而當要寫這塊記憶體時,系統將會產生一塊新的記憶體並拷貝原來記憶體中的資料到新記憶體中,然後將新記憶體關聯到對應的memory mapping,然後執行寫入操作。 Linux下許多功能都依賴copy on write技術來提升效能,像是fork等。

透過上面的介紹,我們可以簡單的將記憶體的使用過程總結如下:

1、進程向系統發出記憶體申請請求

2、系統會檢查進程的虛擬位址空間是否被用完,如果有剩餘,給進程分配虛擬位址

3、系統為這塊虛擬位址建立對應的memory mapping(可能多個),並將它放進該行程的page table

4、系統傳回虛擬位址給進程,進程開始存取該虛擬位址

5、CPU根據虛擬位址在該進程的page table中找到了對應的memory mapping,但是該mapping沒有和實體記憶體關聯,於是產生缺頁中斷

6、作業系統收到缺頁中斷後,分配真正的實體記憶體並將它關聯到對應的memory mapping

7、中斷處理完成後,CPU就可以存取該記憶體了

當然缺頁中斷不是每次都會發生,只有系統覺得有必要延遲分配記憶體的時候才用的著,也即很多時候在上面的步驟3系統會分配真正的實體記憶體並和memory mapping關聯。

六、其它概念

作業系統只要實現了虛擬記憶體和實體記憶體之間的映射關係,就能正常運作了,但要讓記憶體存取更有效率,還有很多東西需要考慮,在這裡我們可以看看跟記憶體有關的一些其它概念以及它們的作用。

MMU(Memory Management Unit)

#MMU是CPU的一個用來將行程的虛擬位址轉換成實體位址的模組,簡單點說,這個模組的輸入是進程的page table和虛擬位址,輸出是實體位址。將虛擬位址轉換成實體位址的速度直接影響系統的速度,所以CPU包含了這個模組用來加速。

TLB(Translation Lookaside Buffer)

#上面介紹到,MMU的輸入是page table,而page table又有記憶體裡面,跟CPU的cache相比,記憶體的速度很慢,所以為了進一步加快虛擬位址到實體位址的轉換速度,Linux發明了TLB,它存在於CPU的L1 cache裡面,用來快取已經找到的虛擬位址到實體位址的映射,這樣下次轉換前先查一下TLB,如果已經在裡面了就不需要調用MMU了.

按需分配物理頁

#由於實際情況下物理內存要比虛擬內存少很多,所以操作系統必須很小心的分配物理內存,以使內存的使用率達到最大化。一個節約實體記憶體的辦法就是只載入目前正在使用的虛擬page對應的資料到記憶體。比如,一個很大的資料庫程序,如果你只是用了查詢操作,那麼負責插入刪除等部分的代碼段就沒必要加載到內存中,這樣就能節約很多物理內存,這種方法就叫做物理內存頁按需分配,也可以稱作延遲載入。

其實作原理很簡單,就是當CPU存取一個虛擬記憶體頁的時候,如果這個虛擬記憶體頁對應的資料還沒載入到實體記憶體中,則CPU就會通知作業系統發生了page fault ,然後由作業系統負責將資料載入進實體記憶體。由於將資料載入進記憶體比較耗時,所以CPU不會等在那裡,而是去調度其它進程,當它下次再調度到該進程時,資料已經在物理記憶體上了。

Linux主要使用這種方式來載入可執行檔和動態庫,當程式被核心開始調度執行時,核心將進程的可執行檔和動態庫映射到進程的虛擬位址空間,並且只載入馬上要用到的那小部分資料到實體記憶體中,其它的部分只有當CPU存取到它們時才去載入。

交換空間

當一個行程需要載入資料到實體記憶體中,但實際的實體記憶體已經被用完時,作業系統需要回收一些實體記憶體中的page以滿足當前進程的需要。

對於file backed的內存數據,即物理內存裡面的數據來自於磁碟上的文件,那麼內核將直接將該部分數據從內存中移除掉來釋放更多的內存,當下一次有進程需要存取這部分資料時,再將它從磁碟上載入記憶體。但是,如果這部分數據被修改過且沒被寫入文件,那這部分數據就變成了髒數據,髒數據不能被直接刪掉,只能被移動到交換空間上去。 (可執行檔和動態函式庫檔案不會被修改,但透過mmap private的方式對應到記憶體的磁碟檔案有可能被修改,這種方式對應的記憶體比較特殊,沒修改之前是file backed,修改後但沒有寫回磁碟之前就變成了anonymous的)

对于anonymous的内存数据,在磁盘上没有对应的文件,这部分数据不能直接被删除,而是被系统移到交换空间上去。交换空间就是磁盘上预留的一块特殊空间,被系统用来临时存放内存中不常被访问的数据,当下次有进程需要访问交换空间上的数据时,系统再将数据加载到内存中。由于交换空间在磁盘上,所以访问速度要比内存慢很多,频繁的读写交换空间会带来性能问题。

关于swap空间的详细介绍请参考Linux交换空间

共享内存

有了虚拟内存之后,进程间共享内存变得特别的方便。进程所有的内存访问都通过虚拟地址来实现,而每个进程都有自己的page tables。当两个进程共享一块物理内存时,只要将物理内存的页号映射到两个进程的page table中就可以了,这样两个进程就可以通过不同的虚拟地址来访问同一块物理内存。

从上面的那个图中可以看出,进程X和进程Y共享了物理内存页PFN3,在进程X中,PFN3被映射到了VPFN3,而在进程Y中,PFN3被映射到了VPFN1,但两个进程通过不同的虚拟地址访问到的物理内存是同一块。

访问控制

page table里面的每条虚拟内存到物理内存的映射记录(memory mapping)都包含一份控制信息,当进程要访问一块虚拟内存时,系统可以根据这份控制信息来检查当前的操作是否是合法的。

为什么需要做这个检查呢?比如有些内存里面放的是程序的可执行代码,那么就不应该去修改它;有些内存里面存放的是程序运行时用到的数据,那么这部分内存只能被读写,不应该被执行;有些内存里面存放的是内核的代码,那么在用户态就不应该去执行它;有了这些检查之后会大大增强系统的安全性。

huge pages

由于CPU的cache有限,所以TLB里面缓存的数据也有限,而采用了huge page后,由于每页的内存变大(比如由原来的4K变成了4M),虽然TLB里面的纪录数没变,但这些纪录所能覆盖的地址空间变大,相当于同样大小的TLB里面能缓存的映射范围变大,从而减少了调用MMU的次数,加快了虚拟地址到物理地址的转换速度。

Caches

为了提高系统性能,Linux使用了一些跟内存管理相关的cache,并且尽量将空闲的内存用于这些cache。这些cache都是系统全局共享的:

  • Buffer Cache
    用来缓冲块设备上的数据,比如磁盘,当读写块设备时,系统会将相应的数据存放到这个cache中,等下次再访问时,可以直接从cache中拿数据,从而提高系统效率。它里面的数据结构是一个块设备ID和block编号到具体数据的映射,只要根据块设备ID和块的编号,就能找到相应的数据。

  • Page Cache
    这个cache主要用来加快读写磁盘上文件的速度。它里面的数据结构是文件ID和offset到文件内容的映射,根据文件ID和offset就能找到相应的数据(这里文件ID可能是inode或者path,本人没有仔细去研究)。

从上面的定义可以看出,page cache和buffer cache有重叠的地方,不过实际情况是buffer cache只缓存page cache不缓存的那部分内容,比如磁盘上文件的元数据。所以一般情况下和page cache相比,Buffer Cache的大小基本可以忽略不计。

当然,使用cache也有一些不好的地方,比如需要时间和空间去维护cache,cache一旦出错,整个系统就挂了。

七、总结

有了上面介绍的知识,再来看看我们刚开始提出来的问题,以top命令的输出为例:

KiB Mem :   500192 total,   349264 free,    36328 used,   114600 buff/cache
KiB Swap:   524284 total,   524284 free,        0 used.   433732 avail Mem

KiB Mem代表物理内存,KiB Swap代表交换空间,它们的单位都是KiB。

total、used和free没什么好介绍的,就是总共多少,然后用了多少,还剩多少。

buff/cached代表了buff和cache总共用了多少,buff代表buffer cache占了多少空间,由于它主要用来缓存磁盘上文件的元数据,所以一般都比较小,跟cache比可以忽略不计;cache代表page cache和其它一些占用空间比较小且大小比较固定的cache的总和,基本上cache就约等于page cache,page cache的准确值可以通过查看/proc/meminf中的Cached得到。由于page cache是用来缓存磁盘上文件内容的,所以占有空间很大,Linux一般会尽可能多的将空闲物理内存用于page cache。

avail Mem表示可用於進程下一次分配的實體記憶體數量,這個大小一般比free大一點,因為除了free的空間外,系統還能立即釋放出一些空間來。

那麼怎麼判斷目前記憶體使用情況出現了異常呢?有下面幾點供參考:

● Mem free的值比較小,並且buff/cache的值也小
free的值比較少並不一定代表有問題,因為Linux會盡可能多的將記憶體用於page cache,但是如果buff/cache的值也小,就表示記憶體吃緊了,系統沒有足夠多的記憶體用於cache,如果目前伺服器部署是一個需要頻繁的讀寫磁碟的應用,如FTP伺服器,那麼對效能的影響將會非常大。

● Swap used的值比較大,
這種情況比上面的更嚴重,正常情況下swap應該很少被使用,used值比較大說明交換空間被使用的比較多,如果透過vmstat指令看到swap in/out的比較頻繁的話,說明系統記憶體嚴重不足,整體效能已經受到嚴重影響

相關影片教學推薦:《Linux教學

以上就是本篇文章的全部內容,希望能對大家的學習有所幫助。更多精彩內容大家可以追蹤php中文網相關教學欄位! ! !

以上是Linux的記憶體管理介紹的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:segmentfault.com。如有侵權,請聯絡admin@php.cn刪除