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