內(nèi)存那點(diǎn)事:讓我們一點(diǎn)點(diǎn)的搞懂它
內(nèi)存是計(jì)算機(jī)系統(tǒng)中至關(guān)重要的組成部分,它不僅儲(chǔ)存了運(yùn)行中的程序和數(shù)據(jù),還直接關(guān)系到系統(tǒng)的性能和穩(wěn)定性。讓我們一起深入探討Linux系統(tǒng)下內(nèi)存管理的核心原理,揭開它的神秘面紗。
基礎(chǔ)概念
物理地址
- 概念:物理地址是指計(jì)算機(jī)內(nèi)存中實(shí)際的硬件地址,它對(duì)應(yīng)著計(jì)算機(jī)中的物理存儲(chǔ)單元(如RAM),物理地址是唯一的。內(nèi)存的一個(gè)地址的容量是一個(gè)字節(jié)(Byte)
- 特點(diǎn): 物理地址是唯一的,每個(gè)物理存儲(chǔ)單元都有一個(gè)對(duì)應(yīng)的物理地址。
虛擬地址
- 概念:虛擬地址是在程序執(zhí)行過(guò)程中由操作系統(tǒng)提供的地址空間,它不直接對(duì)應(yīng)物理硬件,而是經(jīng)過(guò)虛擬內(nèi)存系統(tǒng)的映射,最終映射到物理地址上。每個(gè)運(yùn)行的進(jìn)程都有自己的虛擬地址空間,這使得每個(gè)進(jìn)程認(rèn)為它擁有整個(gè)系統(tǒng)的內(nèi)存。
- 特點(diǎn): 虛擬地址具有抽象性,它使得程序無(wú)需關(guān)心實(shí)際的硬件細(xì)節(jié),而是可以使用一個(gè)相對(duì)于程序自身的地址空間。
內(nèi)存布局
- 32位操作系統(tǒng):支持32位的地址空間,最多可以尋址2^32個(gè)地址,即4GB的內(nèi)存。
- 64位操作系統(tǒng): 支持64位的地址空間,最多可以尋址的地址數(shù)量為2^64,即128TB。
- 不同位寬的操作系統(tǒng)地址空間的范圍也不同,下面的兩張圖來(lái)分別表示它們的虛擬地址空間:
- 每個(gè)進(jìn)程的虛擬內(nèi)存空間都包括用戶空間和內(nèi)核空間,每個(gè)進(jìn)程都認(rèn)為它擁有整個(gè)系統(tǒng)的內(nèi)存資源。
- 每個(gè)進(jìn)程的內(nèi)核空間,其實(shí)關(guān)聯(lián)的都是相同的物理內(nèi)存(公用的)。
內(nèi)存映射
既然每個(gè)進(jìn)程都有一個(gè)這么大的地址空間,那么所有進(jìn)程的虛擬內(nèi)存加起來(lái),自然要比實(shí)際的物理內(nèi)存大得多。所以,并不是所有的虛擬內(nèi)存都會(huì)分配物理內(nèi)存,只有那些實(shí)際使用的虛擬內(nèi)存才分配物理內(nèi)存,內(nèi)存分配的機(jī)制是通過(guò)內(nèi)存映射來(lái)管理的,內(nèi)存映射支持按段分配和按頁(yè)分配。
按段分配
分段是比較早提出的,它將整個(gè)物理內(nèi)存劃分為若干個(gè)不同用途的段,每個(gè)段用于存放特定類型的數(shù)據(jù),這些邏輯分段包括只讀段、數(shù)據(jù)段、堆段、棧段組成。
- 只讀段:包括代碼和常量等。
- 數(shù)據(jù)段:包括全局變量等。
- 堆段:包括動(dòng)態(tài)分配的內(nèi)存,從低地址開始向上增長(zhǎng)。
- 文件映射段: 包括動(dòng)態(tài)庫(kù)、共享內(nèi)存等,從高地址開始向下增長(zhǎng)。
- 棧段:包括局部變量和函數(shù)調(diào)用的上下文等。棧的大小是固定的,一般是 8 MB
存在的問(wèn)題:
- 外部?jī)?nèi)存碎片:因?yàn)榉侄螜C(jī)制分配的是連續(xù)的內(nèi)存空間,假設(shè)有 1G 的物理內(nèi)存,A程序占用了512MB,B程序占用了128MB,C程序占用了256MB,空閑128MB,B程序關(guān)閉了,因?yàn)閮?nèi)存不連續(xù),導(dǎo)致沒(méi)有足夠空間在打開一個(gè)200MB的程序,就會(huì)交換到磁盤,從磁盤換入、喚出效率低下(多個(gè)不連續(xù)的物理內(nèi)存空間)。
- 復(fù)雜性: 程序員需要管理多個(gè)內(nèi)存段,增加了編程的復(fù)雜性。
- 不同段的交叉訪問(wèn): 由于段之間的獨(dú)立性,跨越多個(gè)段的訪問(wèn)會(huì)更加復(fù)雜。
按頁(yè)分配
- 將物理內(nèi)存和虛擬內(nèi)存劃分為固定大小的頁(yè)(通常為4KB)
- 操作系統(tǒng)維護(hù)一個(gè)頁(yè)表,將虛擬內(nèi)存的頁(yè)映射到物理內(nèi)存的頁(yè)上。
- 頁(yè)表(快速、高效)。
- MMU:頁(yè)表實(shí)際上存儲(chǔ)在 CPU 的內(nèi)存管理單元 MMU 中。
- TLB 是MMU 中頁(yè)表的高速緩存,加速虛擬地址到物理地址的轉(zhuǎn)換,減少對(duì)主存(RAM)的訪問(wèn)次數(shù),提高系統(tǒng)性能。
- 多級(jí)頁(yè)表:頁(yè)的大小是4K,隨著內(nèi)存的增大,頁(yè)表記錄會(huì)特別多,為了解決頁(yè)表項(xiàng)過(guò)多的問(wèn)題,Linux 提供了兩種機(jī)制,也就是多級(jí)頁(yè)表和大頁(yè)(HugePage)。
優(yōu)點(diǎn)
- 消除外部碎片: 由于頁(yè)是固定大小的,減少了外部碎片的產(chǎn)生。
- 簡(jiǎn)化內(nèi)存管理: 操作系統(tǒng)負(fù)責(zé)頁(yè)的映射,程序員無(wú)需關(guān)心具體的內(nèi)存分配和釋放。
- 更好的內(nèi)存共享: 易于實(shí)現(xiàn)頁(yè)面的共享,不同進(jìn)程可以共享相同的頁(yè)。
內(nèi)存分配與回收
內(nèi)存分配
進(jìn)程可以通過(guò)調(diào)用malloc等函數(shù)在堆上動(dòng)態(tài)分配內(nèi)存。這些內(nèi)存塊的管理由C庫(kù)提供,但最終涉及到系統(tǒng)調(diào)用,如brk和mmap
brk
- 作用:用于調(diào)整進(jìn)程的數(shù)據(jù)段的結(jié)束地址,即擴(kuò)展或縮小堆的大小。
- 操作對(duì)象:操作的是堆空間,對(duì)整個(gè)數(shù)據(jù)段的結(jié)束地址進(jìn)行調(diào)整。
- 分配粒度:分配的內(nèi)存是以頁(yè)為單位的,較大的內(nèi)存請(qǐng)求可能會(huì)導(dǎo)致內(nèi)部碎片。
- 適用場(chǎng)景:適用于較小的內(nèi)存分配,比如動(dòng)態(tài)內(nèi)存分配。
mmap
- 作用:用于在進(jìn)程的地址空間中映射文件或匿名內(nèi)存區(qū)域。
- 操作對(duì)象:可以操作文件映射,也可以用于匿名內(nèi)存映射,即映射到無(wú)關(guān)聯(lián)文件的內(nèi)存。
- 分配粒度:可以以頁(yè)為單位進(jìn)行內(nèi)存分配,也支持更細(xì)粒度的映射。
- 適用場(chǎng)景:適用于大塊的內(nèi)存分配,比如映射大文件、共享內(nèi)存、內(nèi)存映射 I/O 等。
內(nèi)存回收
- 手動(dòng)回收:調(diào)用 free() 或 unmap() 來(lái)釋放這些不用的內(nèi)存。
- 自動(dòng)回收(內(nèi)存緊張時(shí)系統(tǒng)觸發(fā))。
- 回收緩存:比如使用 LRU(Least Recently Used)算法,回收最近使用最少的內(nèi)存頁(yè)面。
- 回收不常訪問(wèn)的內(nèi)存:把不常用的內(nèi)存通過(guò)交換分區(qū)直接寫到磁盤中(Swap)。
- 殺死進(jìn)程:內(nèi)存緊張時(shí)系統(tǒng)還會(huì)通過(guò)OOM(Out of Memory)直接殺掉占用大量?jī)?nèi)存的進(jìn)程。
- 一個(gè)進(jìn)程消耗的內(nèi)存越大,oom_score 就越大。
- 一個(gè)進(jìn)程運(yùn)行占用的 CPU 越多,oom_score 就越小。
# oom_adj 的范圍是 [-17, 15],數(shù)值越大,表示進(jìn)程越容易被 OOM 殺死
# -17 表示禁止 OOM
echo -16 > /proc/$(pidof sshd)/oom_adj
結(jié)束語(yǔ)
今天我們從概念開始,一點(diǎn)點(diǎn)的展開講了關(guān)于內(nèi)存映射的兩種內(nèi)存分配機(jī)制,以及特點(diǎn),講了內(nèi)存分配和回收,留下幾個(gè)問(wèn)題,系統(tǒng)大家一起討論學(xué)習(xí):
- 按頁(yè)分配下會(huì)存在內(nèi)存碎片嗎?為什么?
- Linux 操作系統(tǒng)采用了哪種方式來(lái)管理內(nèi)存呢?