內(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 直接相加得出結果。
小結
對普通進程來說,它能看到的其實是內(nèi)核提供的虛擬內(nèi)存,這些虛擬內(nèi)存還需要通過頁表,由系統(tǒng)映射為物理內(nèi)存。
當進程通過 malloc() 申請內(nèi)存后,內(nèi)存并不會立即分配,而是在首次訪問時,才通過缺頁異常陷入內(nèi)核中分配內(nèi)存。
由于進程的虛擬地址空間比物理內(nèi)存大很多,Linux 還提供了一系列的機制,應對內(nèi)存不足的問題,比如緩存的回收、交換分區(qū) Swap 以及 OOM 等。
當需要了解系統(tǒng)或者進程的內(nèi)存使用情況時,可以用 free 和 top 、ps 等性能工具。它們都是分析性能問題時最常用的性能工具,要能熟練使用并真正理解各個指標的含義。