效能測試必備知識(10)- Linux 是怎麼管理記憶體的?

小菠蘿測試筆記發表於2020-08-14

做效能測試的必備知識系列,可以看下面連結的文章哦

https://www.cnblogs.com/poloyy/category/1806772.html

 

記憶體對映

日常生活常說的記憶體是什麼

  • 比方說,我的膝上型電腦記憶體就是 8GB 的
  • 這個記憶體其實是實體記憶體
  • 實體記憶體也稱為主存,大多數計算機用的主存都是動態隨機訪問記憶體(DRAM)

 

靈魂拷問

只有核心才可以直接訪問實體記憶體,那麼程式要訪問記憶體時,怎麼辦?

 

虛擬地址空間

  • 為了解決上面的問題,Linux 核心給每個程式都提供了一個獨立的虛擬地址空間,並且這個地址空間是連續
  • 這樣,程式就可以很方便地訪問記憶體,更確切地說是訪問虛擬記憶體

 

內部

  • 虛擬地址空間的內部又被分為核心空間使用者空間兩部分
  • 不同字長(單個 CPU 指令可以處理資料的最大長度)的處理器,地址空間的範圍也不同

 

最常見的 32 位和64 位系統的虛擬地址空間

  • 32 位系統的核心空間佔用 1G,位於最高處,剩下的 3G 是使用者空間
  • 而 64 位系統的核心空間和使用者空間都是 128T,分別佔據整個記憶體空間的最高和最低處,剩下的中間部分是未定義的

 

程式的使用者態和核心態

  • 程式在使用者態時,只能訪問使用者空間記憶體
  • 只有進入核心態後,才可以訪問核心空間記憶體
  • 雖然每個程式的地址空間都包含了核心空間,但這些核心空間,其實關聯的都是相同的實體記憶體
  • 這樣,程式切換到核心態後,就可以很方便地訪問核心空間記憶體

 

為什麼會有記憶體對映

  • 既然每個程式都有一個這麼大的地址空間,那麼所有程式的虛擬記憶體加起來,自然要比實際的實體記憶體大得多
  • 所以,並不是所有的虛擬記憶體都會分配實體記憶體,只有那些實際使用的虛擬記憶體才分配實體記憶體
  • 並且分配後的實體記憶體,是通過記憶體對映來管理的

 

什麼是記憶體對映

  • 記憶體對映,其實就是將虛擬記憶體地址對映到實體記憶體地址
  • 為了完成記憶體對映,核心為每個程式都維護了一張頁表,記錄虛擬地址與實體地址的對映關係

  • 頁表實際上儲存在 CPU 的記憶體管理單元 MMU
  • 正常情況下,處理器就可以直接通過硬體,找出要訪問的記憶體
  • 在頁表的對映下,程式就可以通過虛擬地址來訪問實體記憶體了

 

靈魂拷問

麼具體到 一個 Linux 程式中,這些記憶體又是怎麼使用的呢?

 

 

虛擬記憶體空間分佈

回答上面的問題,需要進一步瞭解虛擬記憶體空間的分佈情況

使用者空間記憶體,其實又被分成了多個不同的段

這是 32 位系統,使用者空間記憶體,從低到高分別是五種不同的記憶體段

  1. 只讀段:包括程式碼和常量等
  2. 資料段:包括全域性變數等
  3. 堆:包括動態分配的記憶體,從低地址開始向上增長
  4. 檔案對映段:包括動態庫、共享記憶體等,從高地址開始向下增長
  5. 棧:包括區域性變數和函式呼叫的上下文等。棧的大小是固定的,一般是 8 MB

 

在這五個記憶體段中,堆和檔案對映段的記憶體是動態分配

比如說,使用 C 標準庫的 malloc()  或者 mmap() ,就可以分別在堆和檔案對映段動態分配記憶體

 

其實 64 位系統的記憶體分佈也類似,只不過記憶體空間要大得多

 

靈魂拷問

記憶體究竟是怎麼分配的呢?

 

記憶體分配與回收

分配

 malloc() 是 C 標準庫提供的記憶體分配函式,對應到系統呼叫上,有兩種實現方式,即 brk() 和 mmap() 

 

brk()

  • 對小塊記憶體(小於 128K),C 標準庫使用 brk() 來分配
  • 也就是通過移動堆頂的位置來分配記憶體
  • 這些記憶體釋放後並不會立刻歸還系統,而是被快取起來,這樣就可以重複使用
  • 優點:快取可以減少缺頁異常的發生,提高記憶體訪問效率
  • 缺點:由於這些記憶體沒有歸還系統,在記憶體工作繁忙時,頻繁的記憶體分配和釋放會造成記憶體碎片

 

mmap()

  • 大塊記憶體(大於 128K),則直接使用記憶體對映 mmap() 來分配,也就是在檔案對映段找一塊空閒記憶體分配出去
  • 缺點:分配的記憶體,會在釋放時直接歸還系統,所以每次 mmap 都會發生缺頁異常;在記憶體工作繁忙時,頻繁的記憶體分配會導致大量的缺頁異常,使核心的管理負擔增大, 這也是 malloc 只對大塊記憶體使用 mmap 的原因

 

總結

  • 當這兩種呼叫發生後,其實並沒有真正分配記憶體
  • 這些記憶體,都只在首次訪問時才分配,也就是通過缺頁異常進入核心中,再由核心來分配記憶體

 

Linux 使用夥伴系統來管理記憶體分配

  • 這些記憶體在 MMU 中以頁為單位進行管理,夥伴系統也一樣,以頁為單位來管理記憶體,並且會通過相鄰頁的合併,減少記憶體碎片化
  • 在使用者空間,malloc 通過 brk() 分配的記憶體,在釋放時並不立即歸還系統,而是快取起來重複利用
  • 在核心空間,Linux 則通過 slab 分配器來管理小記憶體
  • 你可以把 slab 看成構建在夥伴系統上的一個快取,主要作用就是分配並釋放核心中的小物件

 

釋放記憶體

  • 對記憶體來說,如果只分配而不釋放,就會造成記憶體洩露,甚至會耗盡系統記憶體
  • 所以,在應用程式用完記憶體後,還需要呼叫 free() 或 unmap() ,來釋放這些不用的記憶體

 

回收

系統不會任由某個程式用完所有記憶體,在發現記憶體緊張時,系統就會通過一系列機制來回收記憶體

  1. 回收快取:比如使用 LRU(Least Recently Used)演算法,回收最近使用最少的記憶體頁面
  2. 回收不常訪問的記憶體:把不常用的記憶體通過交換分割槽直接寫到磁碟中
  3. 殺死程式:記憶體緊張時系統還會通過 OOM(Out of Memory),直接殺掉佔用大量記憶體的程式

 

回收不常訪問的記憶體

  • 會用到交換分割槽(以下簡稱 Swap
  • Swap 其實就是把一塊磁碟空間當成記憶體來用
  • 它可以把程式暫時不用的資料儲存到磁碟中(這個過程稱為換出),當程式訪問這些記憶體時,再從磁碟讀取這些資料到記憶體中(這個過程稱為換入
  • 通常只在記憶體不足時, 才會發生 Swap 交換
  • 優點:Swap 把系統的可用記憶體變大了
  • 缺點:由於磁碟讀寫的速度遠比記憶體慢,所以 Swap 會導致嚴重的記憶體效能問題

 

OOM

是核心的一種保護機制

監控程式的記憶體使用情況,並且使用 oom_score 為每個程式的記憶體使用情況進行評分:

  • 一個程式消耗的記憶體越大,oom_score 就越大,越容易被 OOM 殺死,從而保護系統
  • 一個程式執行佔用的 CPU 越多,oom_score 就越小

 

可以通過 /proc 檔案系統,手動設定程式的  oom_adj ,從而調整程式的 oom_score 

 oom_adj  的範圍是 [-17, 15] ,數值越大,表示程式越容易被 OOM 殺死;數值越小,表示程式越不容易被 OOM 殺死,其中 -17 表示禁止 OOM

 

調整 oom_score 的栗子

把 sshd 程式的 oom_adj 調小為 -16,這樣, sshd 程式就 不容易被 OOM 殺死

echo -16 > /proc/$(pidof sshd)/oom_adj 

 

如何檢視記憶體使用情況

free

顯示的是整個系統的記憶體使用情況

https://www.cnblogs.com/poloyy/p/13503203.html

 

top

可以檢視系統記憶體使用情況,也可以看程式的,具體可以看下面的部落格哦

https://www.cnblogs.com/poloyy/p/12552041.html

 

 

相關文章