虛擬記憶體與實體記憶體
計算機系統把記憶體組織成固定大小的頁( page),頁的大小是基於處理器架構的,例如在 x86_64 上標準的頁為 4K。實體記憶體被劃分為頁幀(frames),一個頁幀包含一頁資料。
程序不會直接定址實體記憶體,每個程序都有一個虛擬地址空間,當程序請求記憶體時,核心透過查詢頁表將頁幀的實體地址對映到程序地址空間中的虛擬地址。例如:
1、程序先使用 45bit 去定址頁幀(物理頁)
2、核心將私有虛擬頁號(45bit)轉換為帶有資料的物理頁。
3、對於物理頁面地址,程序使用低 12bit 作為偏移量來訪問頁面中的資料。
使用 ps和 top 指令可以檢視到虛擬記憶體和實體記憶體。例如
頁面錯誤(page faults )
每個程序都維護自己的虛擬地址空間,每個程序都擁有一張頁表(page table),它用來跟蹤虛擬頁面到物理頁面的對映。
頁面錯誤:
Minor page fault:要訪問的記憶體不在虛擬地址空間,但是在實體記憶體中,只需要建立對映關係即可,然後從 RAM 分配新的物理頁面。
Major page fault:要訪問的物理頁不虛擬地址空間,也不在實體記憶體中,需要從慢速裝置(磁碟)中載入。這種事件嚴重影響效能。
透過使用 ps指令檢視 page faults,例如:
或者透過 perf 採用收集 page faults 事件,例如
TLB
TLB 是CPU 中的一個快取記憶體,它記錄最近使用的頁面對映來加速地址轉換。因為總是查頁表效率低下。
管理大頁
TLB是CPU 中的一塊空間極小的快取區域,快取的內容是虛擬地址到實體地址的對映關係,為了提供TLB 的 hit 率,應在 TLB 中儘量多的增加快取數量,從而提供效能。
透過增加頁面的大小(預設為 4K),可以大大減少TLB的 miss率,在x86 64 位中,可以支援 2M甚至 1G 的頁面大小。預設是使用 2M。可以透過在/proc/meminfo 中檢視到。
可以透過使用 vm.nr_hugepages 來設定所需的大頁數量,例如:。
注意:分配大頁記憶體的前提是必須有足夠多的且連續的記憶體。為了能夠使用大頁,需要掛載 hugetlbfs 檔案系統,例如:
使用透明大頁
從 RHEL6.2 版本後,核心支援了透明大頁(THP)功能。透過在/sys/kernel/mm/transparent_hugepage,目錄下的設定,可以進行設定
Always:開啟THP
Never:禁止THP
Madvise:禁止自動 THP 分配,希望使用THP 的程序必須同 madvise()系統呼叫通知核心。
注意:THP 不適合在資料庫服務中使用。
配置頁回收
Pagecache:核心使用未分配的記憶體作為快取,當下次有程序需要訪問資料時,直接從快取中獲得,提供了訪問速度。
Anonymous pages:匿名頁面是與磁碟上的資料無關聯的,是用於儲存器工作資料的頁面。使用 free 命令檢視,例如:
回收匿名頁面
當記憶體不足時,核心可以選擇從 pagecache中回收頁面或回收匿名頁面,沒有檔案背景的匿名頁面可以被移動到交換分割槽中(swap),這些被回收的頁面可以用於其他程序。
使用 vmstat 命令可以檢視到 swap 資訊,例如:
回收頁面和配置 swappiness
當核心需要釋放記憶體時,有兩種方式:
-
將程序的匿名頁換出到 swap 空間。
-
從 pagecache 中丟棄頁,因為 pagecache 中的頁面來自磁碟,因此當資料傳送變換時(髒頁)系統將其寫回磁碟並回收頁面。
透過使用 vm.swappiness,核心引數來影響核心的選擇。該引數的數值為 0-100。
-
增大該值,系統會更加傾向使用 swap,核心更多保留頁面快取,提高了I/O 效能。
-
減小該值,系統儘量可能少的進行交換。提高了響應速度。
調整髒頁清理
在/proc/meminfo 中可以檢視到頁面的不同狀態
-
Free:該頁面可以立即分配
-
Active:正在積極使用中的頁面
-
Inactive clean:與磁碟中的內容一致,並且未被使用
-
Inactive dirty:內容已經被修改,還沒被寫回,回收inactive clean 型別的頁面比較方便,但回收inactive dirty 型別的頁面比較麻煩。
由於記憶體是易失性的,因此需要及時把記憶體中的資料寫回磁碟。系統透過使用 flush 核心執行緒來處理髒頁寫回每個塊裝置上都會執行該執行緒。
透過一組 sysctl 引數控制 fush 執行緒的動作:
-
vm.dirty_expire_centisecs:指定髒頁能夠存活的最大時間(1/100秒),防止太過頻繁的寫回動作。
-
vm.dirty_writeback _centisecs:多久喚醒一次 flush 執行緒。
-
vm.dirty_background_ratio:髒頁佔總記憶體的百分比。
-
vm.dirty_ratio:髒頁填充的絕對最大記憶體量,達到該值時,阻塞所有I/O,所有髒頁必須寫回。
注意:vm.dirty_background_bytes 和 vm.dirty_bytes 可以替代vm.dirty_background_ratio 和Vm.dirty_ratio。
管理記憶體不足
當出現記憶體不足時,一個名為 Out-memory killer(OOM)的核心子系統隨機選擇殺死一個程序來釋放記憶體,如果條件仍然存在,系統會再次出發 OOM。
這樣隨機殺程序會造成程式有損害,因此可以設定vm.panic_on_oom,引數,將其值調節為 1。該值代表當記憶體不足時,系統 linux 進入崩潰,而不是隨機殺程序。
每個程序都有一個維護“壞值”的檔案,可以在/proc/PID/oom_score,中檢視。數值越高越有可能被殺死。而核心和 systemd 程序對 OOM 免疫。可以手工調節“壞值”,範圍是-1000 到 1000。預設為0,如果設定為-1000,則該程序免疫。
使用 systemd 調整記憶體不足評分
要對某個程式進行調節,可以在[Service]段落中設定 OOMScoreAdjust 引數,例如:
NUMA 架構
在老的多處理器系統中使用 UMA 架構,所有記憶體都可以被 CPU 平等訪問。
現代的多處理器系統使用 NUMA架構,每個CPU 組都可以訪問一組記憶體,並透過高速鏈路將CPU互連。整個記憶體仍然可以被其他 CPU 訪問,但訪問本地記憶體要比透過互連訪問遠端記憶體快。
使用 numactl --hardware命令檢視NUMA拓撲,例如:
**管理記憶體 overcommit **
核心允許給程序分配超過可用記憶體的記憶體量。因為當程序請求記憶體時,核心只分配虛擬記憶體,並沒有分配真實的實體記憶體。直到程序開始使用物理頁面。
透過調整 vm.overcommit_memory 引數對過多分配進行調整:
- 0:預設值(從RHEL6),允許overcommit。過於嚴重的申請將會失敗。
- 1:允許 overcommit,申請時不檢查,永遠分配成功。
- 2:禁止 overcommit,只能申請 swap+RAM*50%的大小。透過調整vm.overcommit_ratio