1. 程式人生 > >Linux性能優化實戰:Linux內存是怎麽工作的?(15)

Linux性能優化實戰:Linux內存是怎麽工作的?(15)

返回 linux 進程 大量 opp 鏈接 之一 恢復 既然 free

一、內存映射

內存管理也是操作系統最核心的功能之一,內存主要用來存儲系統和應用程序的指令、數據、緩存等

1、我們通說的內存指的是物理內存還是虛擬內存?

我們通常說的內存容量,其實這指的是物理內存,物理內存也稱為主存,大多數計算機用的主存都是動態隨機訪問內存(DRAM)。只有內核才可以直接訪問物理內存。

那麽,進程要訪問內存時,該怎麽辦呢?

2、進程是如何訪問內存的?

Linux 內核給每個進程都提供了一個獨立的虛擬地址空間,並且這個地址空間是連續的。這樣,進程就可以很方便地訪問內存,更確切地說是訪問虛擬內存

3、虛擬內存的內核空間和用戶空間分布圖

技術分享圖片

4、進程是如何訪問內核空間內存的?

進程在用戶態時,只能訪問用戶內存;只有進入內核態後,才可以訪問內核空間內存,雖然每個進程的地址空間都包含了內核空間,但這些內核空間,其實關聯的都是想同的物理內存

這樣、進程切換到內核態後,就可以很方便地訪問內核空間內存

5、並不是所有的虛擬機內存都會分配物理內存

既然每個進程都有一個這麽大的地址空間,那麽所有進程的虛擬內存加起來,自然要比實際的物理內存大得多

所以並不是所有的虛擬機內存都會分配物理內存,只有那些實際使用的虛擬機內存才分配物理內存,並且分配後的物理內存,是通過內存映射來管理的

6、什麽是內存映射?

內存映射,其實就是將虛擬內存地址映射到物理內存地址,為了完成內存映射,內核為每個進程都維護了一張頁表,

記錄虛擬地址與物理地址的映射關系,如下圖所示:

技術分享圖片

頁表實際上存儲在 CPU 的內存管理單元 MMU 中,這樣,正常情況下,處理器就可以直接通過硬件,找出要訪問的內存

7、進程訪問的虛擬地址在頁表中查不到,怎麽辦?

當進程訪問的虛擬地址在頁表中查不到時,系統會產生一個缺頁異常,進入內核空間分配物理內存、更新進程頁表,最後再返回用戶空間、恢復進程運行

8、什麽是TLB?TLB的作用是什麽

TLB 其實就是 MMU 中頁表的高速緩存,由於進程的虛擬地址空間是獨立的,而 TLB 的訪問速度又比MMU 快得多

所以,通過減少進程的上下文切換,減少 TLB 的刷新次數,就可以提高 TLB 緩存的使用率,進而提高 CPU 的內存訪問性能。

9、MMU 是以什麽為單位來管理內存?

不過要註意,MMU 並不以字節為單位來管理內存,而是規定了一個內存映射的最小單位也就是頁,通常是 4 KB大小,

這樣,每一次內存映射,都需要關聯 4 KB 或者 4KB 整數倍的內存空間

10、如何減少頁表的項數

頁的大小只有 4 KB ,導致的另一個問題就是,整個頁表會變得非常大。比方說,僅 32 位系統就需要 100 多萬個頁表項(4GB/4KB),才可以實現整個地址空間的映射。

為了解決頁表項過多的問題,Linux 提供了兩種機制,也就是多級頁表和大頁(HugePage)。

11、多級頁

多級頁表就是把內存分成區塊來管理,將原來的映射關系改成區塊索引和區塊內的偏移。由於虛擬內存空間通常只用了很少一部分,

那麽多級頁表就只保存這些使用中的區塊,這樣就可以大大地減少頁表的項數

Linux 用的正是四級頁表來管理內存頁,如下圖所示,虛擬地址被分為 5 個部分,前 4 個表項用於選擇頁,而最後一個索引表示頁內偏移。

技術分享圖片

12、大頁

再看大頁,顧名思義,就是比普通頁更大的內存塊,常見的大小有 2MB 和 1GB。大頁通常用在使用大量內存的的進程上,比如 Oracle、DPDK 等

通過這些機制,在頁表的映射下,進程就可以通過虛擬地址來訪問物理內存了。那麽具體到一個 Linux 進程中,這些內存又是怎麽使用的呢

二、虛擬內存空間分布

1、虛擬內存空間分布圖

技術分享圖片

通過這張圖你可以看到,用戶空間內存,從低到高分別是五種不同的的內存段

2、虛擬內存空間分布詳解

技術分享圖片

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

2、其實 64 位系統的內存分布也類似,只不過內存空間要大得多,那麽,更重要的問題來了,內存究竟是怎麽分配的呢?

三、內存分配與回收

malloc() 是 C 標準庫提供的內存分配函數,對應到系統調用上,有兩種實現方式,即 brk() 和 mmap()。

1、內存分配

技術分享圖片

都只在首次訪問時才分配,也就是通過缺頁異常進入內核中,再由內內核來分配內存。

1、如何減少內存碎片?

整體來說,Linux 使用夥伴系統來管理內存分配。前面我們提到過,這些內存在 MMU 中以頁為單位進行管理,夥伴系統也一系統也一樣,以頁為單位來管理內存,並且會通過相鄰頁的合並,
減少內存碎片化(比如 brk 方式造成的內存碎片)。

2、比頁更小的內存(不到 1K ),該怎麽分配內存呢?

實際系統運行中,確實有大量比頁還小的對象如果為它們也分配單獨的頁,那就太浪費內存了。所以,在用戶空間,malloc 通過 brk() 分配的內存,在釋放時並不立即歸還系統,

而是緩存起來重復利用。在內核空間,Linux 則通過 slab 分配器來管理小內存。你可以把slab 看成構建在夥伴系統上的一個緩存,主要作用就是分配並分配並釋放內核中的小對象。

2、內存回收

對內存來說,如果只分配而不釋放,就會造成內存泄漏,甚至會耗盡系統內存。所以,在應用程序用完內存後,還需要調用free() 或 unmap() ,來釋放這些不用的內存。

當然,系統也不會任由某個進程用完所有內存,在發現內存緊張時,系統就會通過一系列機制來回收內存,比如下面下面這三種方式:

技術分享圖片

第三種方式提到的 OOM(Out of Memory),其實是內核的一種保護機制它監控進程的內存使用情況,並且使用 oom_score 為每個進程的內存使用情況進行評分:

一個進程消耗的內存越大,oom_score 就越大;

一個進程運行占用的 CPU 越多,oom_score 就越小

這樣,進程的 oom_score 越大,代表消耗的內存越多,也就越容易被 OOM 殺死,從而可以更好保護系統。

當然,為了實際工作的需要,管理員可以通過 /proc 文件系統,手動設置進程的 oom_adj ,從而調整進程的 oom_score。


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

比如用下面的命令,你就可以把 sshd 進程的調小為 -16,這樣, sshd 進程就不容易被 OOM 殺死

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

四、如何查看內存使用情況

1、free圖解

[[email protected] ~]# free
total used free shared buff/cache available
Mem: 8010968 935180 5968260 16668 1107528 6835728
Swap: 4194300 0 4194300

技術分享圖片

這裏尤其註意一下,最後一列的可用內存 available,available 不僅包含未使用內存,還包括了可回收的緩存,所以一般會比未使用內存更大。

不過,並不是所有緩存都可以回收,所以一般會比未使用內存更大

2、top圖解

top - 11:52:35 up 16 days, 55 min, 1 user, load average: 0.00, 0.01, 0.05
Tasks: 101 total, 1 running, 100 sleeping, 0 stopped, 0 zombie
%Cpu(s): 0.1 us, 0.1 sy, 0.0 ni, 99.8 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 14.7/8010968 [||||||||||||||| ]
KiB Swap: 0.0/4194300 [ ]

PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1 root 20 0 41348 3580 2332 S 0.0 0.0 0:36.59 systemd
2 root 20 0 0 0 0 S 0.0 0.0 0:00.00 kthreadd
3 root 20 0 0 0 0 S 0.0 0.0 0:00.00 ksoftirqd/0

技術分享圖片

VIRT 是進程虛擬內存的大小:只要是進程申請過的內存,即便還沒有真正分配物理內存,也會計算在內

RES 是常駐內存的大小:也就是進程實際使用的物理內存大小,但不包括 Swap 和共享內存。

SHR 是共享內存的大小:比如與其他進程共同使用的共享內存、加載的動態鏈接庫以及程序的代碼段等。

%MEM 是進程使用物理內存占系統總內存的百分比。

3、需要註意兩點

第一:虛擬內存通常並不會全部分配物理內存。從上面的輸出,你可以發現每個進程的虛擬內存都比常駐內存大得多

第二:共享內存 SHR 並不一定是共享的,比方說,程序的代碼、非共享的動態鏈接庫,也都算在 SHR 裏。當然,

SHR 也包括了進程間真正共享的內存。所以在計算多個進程的內存使用時,不要把所有進程的 SHR 直接相加得出結果。

Linux性能優化實戰:Linux內存是怎麽工作的?(15)