linux 內(nèi)存管理

內(nèi)存工作原理

內(nèi)存管理模塊是Linux系統(tǒng)最主要的模塊,系統(tǒng)和應用程序的指令、數(shù)據(jù)和緩存等都是存儲在內(nèi)存。常說的32位或64位系統(tǒng),其中32位/64位指的是單個進程可尋址的虛擬內(nèi)存大小,進程在創(chuàng)建時,系統(tǒng)為進程分配了獨立的連續(xù)的虛擬內(nèi)存空間。在linux 的內(nèi)存管理中,只有內(nèi)核才能訪問物理內(nèi)存,所有的進程需要訪問物理內(nèi)存時都需要先切換到內(nèi)核態(tài)才能進行訪問。

不同位數(shù)的系統(tǒng)的虛擬地址空間:

# 32 位
+++++++++++++++++++++ 0xFFFF FFFF  
+                   +
+     內(nèi)核空間1G     +
+———————————————————+ 0xC000 0000
+                   +
+                   +
+   用戶空間 3G      +
+                   +
+                   +
+++++++++++++++++++++ 0x0000 0000


# 64 位
+++++++++++++++++++++ 0xFFFF FFFF FFFF FFFF
+                   +
+     內(nèi)核空間128T   +
+———————————————————+ 0xFFFF 8000 0000 0000
+                   +
+    未定義          +
+-------------------+ 0x0000 7FFF FFFF F000
+                   +
+   用戶空間128T     +
+++++++++++++++++++++ 0x0000 0000 0000 0000


虛擬內(nèi)存空間分布


|_____________________|
|                     |
|  內(nèi)核空間 1G         |
|_____________________|
|   棧空間8M           |
|_____________________|
|                     |
|   文件映射           |
|                     |
|—————————————————————|
|    堆               |
|—————————————————————|
|  數(shù)據(jù)段              |
|—————————————————————|
|  只讀段              |
|_____________________|

用戶空間內(nèi)存,從低到高分別是五種不同的內(nèi)存段。

  • 只讀段,包括代碼和常量等。
  • 數(shù)據(jù)段,包括全局變量等。
  • 堆,包括動態(tài)分配的內(nèi)存,從低地址開始向上增長。
  • 文件映射段,包括動態(tài)庫、共享內(nèi)存等,從高地址開始向下增長。
  • 棧,包括局部變量和函數(shù)調(diào)用的上下文等。棧的大小是固定的,一般是 8 MB。

在這五個內(nèi)存段中,堆和文件映射段的內(nèi)存是動態(tài)分配的。比如說,使用 C 標準庫的 malloc() 或者 mmap() ,就可以分別在堆和文件映射段動態(tài)分配內(nèi)存。

內(nèi)存的分配和回收

malloc() 是 C 標準庫提供的內(nèi)存分配函數(shù),對應到系統(tǒng)調(diào)用上,有兩種實現(xiàn)方式,即 brk() 和 mmap()。

  • 對小塊內(nèi)存(小于 128K),C 標準庫使用 brk() 來分配,也就是通過移動堆頂?shù)奈恢脕矸峙鋬?nèi)存。這些內(nèi)存釋放后并不會立刻歸還系統(tǒng),而是被緩存起來,這樣就可以重復使用。
  • 而大塊內(nèi)存(大于 128K),則直接使用內(nèi)存映射 mmap() 來分配,也就是在文件映射段找一塊空閑內(nèi)存分配出去。

brk() 方式的緩存,可以減少缺頁異常的發(fā)生,提高內(nèi)存訪問效率。不過,由于這些內(nèi)存沒有歸還系統(tǒng),在內(nèi)存工作繁忙時,頻繁的內(nèi)存分配和釋放會造成內(nèi)存碎片。

而 mmap() 方式分配的內(nèi)存,會在釋放時直接歸還系統(tǒng),所以每次 mmap 都會發(fā)生缺頁異常。在內(nèi)存工作繁忙時,頻繁的內(nèi)存分配會導致大量的缺頁異常,使內(nèi)核的管理負擔增大。這也是 malloc 只對大塊內(nèi)存使用 mmap 的原因。

同時,當這兩種調(diào)用發(fā)生后,其實并沒有真正分配內(nèi)存。這些內(nèi)存,都只在首次訪問時才分配,也就是通過缺頁異常進入內(nèi)核中,再由內(nèi)核來分配內(nèi)存。

對內(nèi)存來說,如果只分配而不釋放,就會造成內(nèi)存泄漏,甚至會耗盡系統(tǒng)內(nèi)存。所以,在應用程序用完內(nèi)存后,還需要調(diào)用 free() 或 unmap() ,來釋放這些不用的內(nèi)存。當然,系統(tǒng)也不會任由某個進程用完所有內(nèi)存。在發(fā)現(xiàn)內(nèi)存緊張時,系統(tǒng)就會通過一系列機制來回收內(nèi)存,比如下面這三種方式:

  • 回收緩存,比如使用 LRU(Least Recently Used)算法,回收最近使用最少的內(nèi)存頁面;
  • 回收不常訪問的內(nèi)存,把不常用的內(nèi)存通過交換分區(qū)直接寫到磁盤中(swap 交換可能會導致嚴重的內(nèi)存性能問題);
  • 殺死進程,內(nèi)存緊張時系統(tǒng)還會通過 OOM(Out of Memory),直接殺掉占用大量內(nèi)存的進程。

內(nèi)存查看


# 注意不同版本的free輸出可能會有所不同
$ free
              total        used        free      shared  buff/cache   available
Mem:        8169348      263524     6875352         668     1030472     7611064
Swap:             0           0           0
  • 第一列,total 是總內(nèi)存大小;
  • 第二列,used 是已使用內(nèi)存的大小,包含了共享內(nèi)存;
  • 第三列,free 是未使用內(nèi)存的大??;
  • 第四列,shared 是共享內(nèi)存的大?。?/li>
  • 第五列,buff/cache 是緩存和緩沖區(qū)的大小;
  • 最后一列,available 是新進程可用內(nèi)存的大小。

available 不僅包含未使用內(nèi)存,還包括了可回收的緩存,所以一般會比未使用內(nèi)存更大。不過,并不是所有緩存都可以回收,因為有些緩存可能正在使用中。

# top 查看

# 按下M切換到內(nèi)存排序
$ top
...
KiB Mem :  8169348 total,  6871440 free,   267096 used,  1030812 buff/cache
KiB Swap:        0 total,        0 free,        0 used.  7607492 avail Mem


  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
  430 root      19  -1  122360  35588  23748 S   0.0  0.4   0:32.17 systemd-journal
 1075 root      20   0  771860  22744  11368 S   0.0  0.3   0:38.89 snapd
 1048 root      20   0  170904  17292   9488 S   0.0  0.2   0:00.24 networkd-dispat
    1 root      20   0   78020   9156   6644 S   0.0  0.1   0:22.92 systemd
12376 azure     20   0   76632   7456   6420 S   0.0  0.1   0:00.01 systemd
12374 root      20   0  107984   7312   6304 S   0.0  0.1   0:00.00 sshd
...
  • VIRT 是進程虛擬內(nèi)存的大小,只要是進程申請過的內(nèi)存,即便還沒有真正分配物理內(nèi)存,也會計算在內(nèi)。
  • RES 是常駐內(nèi)存的大小,也就是進程實際使用的物理內(nèi)存大小,但不包括 Swap 和共享內(nèi)存。
  • SHR 是共享內(nèi)存的大小,比如與其他進程共同使用的共享內(nèi)存、加載的動態(tài)鏈接庫以及程序的代碼段等。
  • %MEM 是進程使用物理內(nèi)存占系統(tǒng)總內(nèi)存的百分比。

虛擬內(nèi)存通常并不會全部分配物理內(nèi)存。從上面的輸出,可以發(fā)現(xiàn)每個進程的虛擬內(nèi)存都比常駐內(nèi)存大得多。同時,共享內(nèi)存 SHR 并不一定是共享的,比方說,程序的代碼段、非共享的動態(tài)鏈接庫,也都算在 SHR 里。當然,SHR 也包括了進程間真正共享的內(nèi)存。所以在計算多個進程的內(nèi)存使用時,不要把所有進程的 SHR 直接相加得出結果。

小結

  1. 對普通進程來說,它能看到的其實是內(nèi)核提供的虛擬內(nèi)存,這些虛擬內(nèi)存還需要通過頁表,由系統(tǒng)映射為物理內(nèi)存。

  2. 當進程通過 malloc() 申請內(nèi)存后,內(nèi)存并不會立即分配,而是在首次訪問時,才通過缺頁異常陷入內(nèi)核中分配內(nèi)存。

  3. 由于進程的虛擬地址空間比物理內(nèi)存大很多,Linux 還提供了一系列的機制,應對內(nèi)存不足的問題,比如緩存的回收、交換分區(qū) Swap 以及 OOM 等。

  4. 當需要了解系統(tǒng)或者進程的內(nèi)存使用情況時,可以用 free 和 top 、ps 等性能工具。它們都是分析性能問題時最常用的性能工具,要能熟練使用并真正理解各個指標的含義。

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • 物理內(nèi)存和虛擬內(nèi)存到底有什么區(qū)別? 提到內(nèi)存,我們會想到經(jīng)常接觸的三個詞:虛擬內(nèi)存、物理內(nèi)存、共享內(nèi)存。它們分別...
    Mr蘿卜閱讀 7,599評論 6 10
  • 【引言】 最近在生產(chǎn)環(huán)境遇到一個奇怪的現(xiàn)象,nginx占用的虛擬內(nèi)存和物理內(nèi)存都很高,并且一直不會下降。 因...
    Mr蘿卜閱讀 7,621評論 0 4
  • 虛擬內(nèi)存1.1. 為什么要使用虛擬內(nèi)存技術1.2. 理論前提1.3. 虛擬內(nèi)存實現(xiàn)1.4. 頁機制1.5. MMU...
    墨染書閱讀 12,436評論 3 33
  • 前言 內(nèi)存管理一向是所有操作系統(tǒng)書籍不惜筆墨重點討論的內(nèi)容,無論市面上或是網(wǎng)上都充斥著大量涉及內(nèi)存管理的教材和資料...
    木有sky閱讀 961評論 0 1
  • 久違的晴天,家長會。 家長大會開好到教室時,離放學已經(jīng)沒多少時間了。班主任說已經(jīng)安排了三個家長分享經(jīng)驗。 放學鈴聲...
    飄雪兒5閱讀 7,788評論 16 22

友情鏈接更多精彩內(nèi)容