記憶體資源控制器¶
注意
本文件已嚴重過時,需要完全重寫。它仍然包含一些有用的資訊,因此我們將其保留在此處,但如果您需要更深入的理解,請務必檢視當前程式碼。
注意
本文件中,記憶體資源控制器通常被稱為記憶體控制器。請勿將此處使用的記憶體控制器與硬體中使用的記憶體控制器混淆。
提示
當我們提及帶記憶體控制器的 cgroup (cgroupfs 的目錄) 時,我們稱之為“記憶體 cgroup”。當您檢視 git-log 和原始碼時,會發現補丁標題和函式名稱傾向於使用“memcg”。在本文件中,我們避免使用它。
記憶體控制器的優點和目的¶
記憶體控制器將一組任務的記憶體行為與系統的其餘部分隔離開來。LWN 上的一篇文章 [12] 提到了記憶體控制器的一些可能用途。記憶體控制器可用於:
隔離一個應用程式或一組應用程式。記憶體密集型應用程式可以被隔離並限制在較小的記憶體量內。
建立一個具有有限記憶體量的 cgroup;這可以作為使用 mem=XXXX 啟動的良好替代方案。
虛擬化解決方案可以控制分配給虛擬機器例項的記憶體量。
CD/DVD 燒錄機可以控制系統其餘部分使用的記憶體量,以確保燒錄不會因可用記憶體不足而失敗。
還有其他幾種用例;您可以自行發現,或者僅僅為了好玩(學習和修改 VM 子系統)而使用該控制器。
當前狀態:linux-2.6.34-mmotm(2010 年 4 月的開發版本)
特性
匿名頁、檔案快取、交換快取的使用統計與限制。
頁面專門連結到每個 memcg 的 LRU,不存在全域性 LRU。
可選地,可以對記憶體+交換使用進行統計和限制。
分層統計
軟限制
移動任務時,(重新)計費是可選的。
使用閾值通知器
記憶體壓力通知器
OOM-killer 停用旋鈕和 OOM 通知器
根 cgroup 沒有限制控制。
核心記憶體支援正在開發中,當前版本基本提供了功能。(參見 第 2.7 節)
控制檔案簡要摘要。
任務 |
附加任務(執行緒)並顯示執行緒列表 |
cgroup.procs |
顯示程序列表 |
cgroup.event_control |
一個用於 event_fd() 的介面。此旋鈕在 CONFIG_PREEMPT_RT 系統上不可用。 |
memory.usage_in_bytes |
顯示當前記憶體使用量(詳見 5.5) |
memory.memsw.usage_in_bytes |
顯示當前記憶體+交換使用量(詳見 5.5) |
memory.limit_in_bytes |
設定/顯示記憶體使用限制 |
memory.memsw.limit_in_bytes |
設定/顯示記憶體+交換使用限制 |
memory.failcnt |
顯示記憶體使用量達到限制的次數 |
memory.memsw.failcnt |
顯示記憶體+交換達到限制的次數 |
memory.max_usage_in_bytes |
顯示記錄的最大記憶體使用量 |
memory.memsw.max_usage_in_bytes |
顯示記錄的最大記憶體+交換使用量 |
memory.soft_limit_in_bytes |
設定/顯示記憶體使用軟限制。此旋鈕在 CONFIG_PREEMPT_RT 系統上不可用。此旋鈕已棄用,不應使用。 |
memory.stat |
顯示各種統計資訊 |
memory.use_hierarchy |
設定/顯示分層賬戶是否啟用。此旋鈕已棄用,不應使用。 |
memory.force_empty |
觸發強制頁面回收 |
memory.pressure_level |
設定記憶體壓力通知。此旋鈕已棄用,不應使用。 |
memory.swappiness |
設定/顯示 vmscan 的 swappiness 引數(參見 sysctl 的 vm.swappiness)。在 cgroup v2 中不存在每個 memcg 的旋鈕。 |
memory.move_charge_at_immigrate |
此旋鈕已棄用。 |
memory.oom_control |
設定/顯示 OOM 控制。此旋鈕已棄用,不應使用。 |
memory.numa_stat |
顯示每個 NUMA 節點的記憶體使用量 |
memory.kmem.limit_in_bytes |
已棄用的旋鈕,用於設定和讀取核心記憶體硬限制。自 5.16 版本以來不再支援核心硬限制。向該檔案寫入任何值都不會產生任何效果,就像指定了 nokmem 核心引數一樣。核心記憶體仍會透過 memory.kmem.usage_in_bytes 進行計費和報告。 |
memory.kmem.usage_in_bytes |
顯示當前核心記憶體分配 |
memory.kmem.failcnt |
顯示核心記憶體使用量達到限制的次數 |
memory.kmem.max_usage_in_bytes |
顯示記錄的最大核心記憶體使用量 |
memory.kmem.tcp.limit_in_bytes |
設定/顯示 TCP 緩衝區記憶體的硬限制。此旋鈕已棄用,不應使用。 |
memory.kmem.tcp.usage_in_bytes |
顯示當前 TCP 緩衝區記憶體分配。此旋鈕已棄用,不應使用。 |
memory.kmem.tcp.failcnt |
顯示 TCP 緩衝區記憶體使用量達到限制的次數。此旋鈕已棄用,不應使用。 |
memory.kmem.tcp.max_usage_in_bytes |
顯示記錄的最大 TCP 緩衝區記憶體使用量。此旋鈕已棄用,不應使用。 |
1. 歷史¶
記憶體控制器歷史悠久。Balbir Singh [1] 釋出了記憶體控制器的評論請求(RFC)。當時釋出 RFC 時,已經存在幾種記憶體控制的實現。RFC 的目標是就記憶體控制所需的最小功能達成共識和一致意見。Balbir Singh [2] 於 2007 年 2 月釋出了第一個 RSS 控制器。此後,Pavel Emelianov [3] [4] [5] 釋出了三個版本的 RSS 控制器。在 OLS 的資源管理 BoF 上,每個人都建議我們同時處理頁面快取和 RSS。還提出了允許使用者空間處理 OOM 的請求。當前的記憶體控制器是版本 6;它結合了對映(RSS)和未對映頁面快取控制 [11]。
2. 記憶體控制¶
記憶體是一種獨特的資源,因為它以有限的數量存在。如果一個任務需要大量的 CPU 處理,該任務可以將其處理分散到數小時、數天、數月或數年,但對於記憶體,需要重複使用相同的物理記憶體來完成任務。
記憶體控制器的實現分為幾個階段。它們是:
記憶體控制器
mlock(2) 控制器
核心使用者記憶體記賬和 slab 控制
使用者對映長度控制器
記憶體控制器是第一個開發的控制器。
2.1. 設計¶
設計的核心是一個名為 page_counter 的計數器。page_counter 跟蹤與控制器關聯的程序組的當前記憶體使用量和限制。每個 cgroup 都有一個與其關聯的記憶體控制器特定資料結構 (mem_cgroup)。
2.2. 記賬¶
+--------------------+
| mem_cgroup |
| (page_counter) |
+--------------------+
/ ^ \
/ | \
+---------------+ | +---------------+
| mm_struct | |.... | mm_struct |
| | | | |
+---------------+ | +---------------+
|
+ --------------+
|
+---------------+ +------+--------+
| page +----------> page_cgroup|
| | | |
+---------------+ +---------------+
圖 1 展示了控制器重要的幾個方面:
記賬按 cgroup 進行
每個 mm_struct 都知道它屬於哪個 cgroup
每個頁面都有一個指向 page_cgroup 的指標,而 page_cgroup 又知道它屬於哪個 cgroup
記賬過程如下:呼叫 mem_cgroup_charge_common() 來設定必要的資料結構,並檢查正在記賬的 cgroup 是否超過其限制。如果超過,則對 cgroup 呼叫回收操作。更多細節可以在本文件的回收部分找到。如果一切順利,一個名為 page_cgroup 的頁面元資料結構將被更新。page_cgroup 在 cgroup 上有自己的 LRU。(*) page_cgroup 結構在啟動/記憶體熱插拔時分配。
2.2.1 記賬詳情¶
所有對映的匿名頁 (RSS) 和快取頁 (Page Cache) 都被記賬。一些不可回收且不會在 LRU 上的頁面不被記賬。我們只記賬在常規 VM 管理下的頁面。
除非 RSS 頁面之前已被記賬,否則會在 page_fault 時進行記賬。檔案頁面在插入 inode (xarray) 時將作為頁面快取進行記賬。當它對映到程序的頁表時,會仔細避免重複記賬。
當 RSS 頁面完全解除對映時,將取消記賬。當 PageCache 頁面從 xarray 中移除時,將取消記賬。即使 RSS 頁面被完全解除對映(透過 kswapd),它們也可能作為 SwapCache 存在於系統中,直到它們真正被釋放。此類 SwapCache 也被記賬。換入的頁面在新增到 swapcache 後進行記賬。
注意:核心會進行 swapin-readahead 並一次讀取多個交換。由於頁面的 memcg 無論 memsw 是否啟用都會記錄到交換區,因此頁面將在換入後被記賬。
在頁面遷移時,記賬資訊會保留。
注意:我們只記賬 LRU 上的頁面,因為我們的目的是控制已用頁面的數量;不在 LRU 上的頁面在 VM 看來往往是失控的。
2.4 交換擴充套件¶
交換使用量始終為每個 cgroup 記錄。交換擴充套件允許您讀取和限制它。
當 CONFIG_SWAP 啟用時,將新增以下檔案。
memory.memsw.usage_in_bytes。
memory.memsw.limit_in_bytes。
memsw 表示記憶體+交換。記憶體+交換的使用受到 memsw.limit_in_bytes 的限制。
示例:假設一個系統有 4G 交換空間。一個在 2G 記憶體限制下分配 6G 記憶體(錯誤地)的任務將使用所有交換空間。在這種情況下,設定 memsw.limit_in_bytes=3G 將阻止對交換空間的錯誤使用。透過使用 memsw 限制,您可以避免因交換空間不足而導致的系統 OOM。
2.4.1 為什麼是“記憶體+交換”而不是“交換”¶
全域性 LRU(kswapd) 可以隨意換出頁面。換出意味著將記賬從記憶體移動到交換...記憶體+交換的使用量沒有變化。換句話說,當我們要限制交換使用量而不影響全域性 LRU 時,從作業系統的角度來看,記憶體+交換限制優於僅僅限制交換。
2.4.2. 當 cgroup 達到 memory.memsw.limit_in_bytes 時會發生什麼¶
當 cgroup 達到 memory.memsw.limit_in_bytes 時,在該 cgroup 中進行換出操作是無用的。此時,cgroup 例程將不再執行換出,並且檔案快取將被丟棄。但如上所述,全域性 LRU 可以從其中換出記憶體,以保持系統記憶體管理狀態的健全。您無法透過 cgroup 阻止它。
2.5 回收¶
每個 cgroup 都維護一個與全域性 VM 具有相同結構的 LRU。當 cgroup 超出其限制時,我們首先嚐試從該 cgroup 回收記憶體,以便為 cgroup 新觸及的頁面騰出空間。如果回收不成功,則會呼叫 OOM 例程來選擇並終止 cgroup 中佔用記憶體最多的任務。(參見下文 10. OOM 控制。)
除了選擇用於回收的頁面來自每個 cgroup 的 LRU 列表之外,回收演算法沒有針對 cgroup 進行修改。
注意
回收功能不適用於根 cgroup,因為我們無法對根 cgroup 設定任何限制。
注意
當 panic_on_oom 設定為“2”時,整個系統將崩潰。
當註冊 OOM 事件通知器時,事件將被傳遞。(參見 oom_control 節)
2.6 鎖定¶
鎖定順序如下:
folio_lock
mm->page_table_lock or split pte_lock
folio_memcg_lock (memcg->move_lock)
mapping->i_pages lock
lruvec->lru_lock.
每個節點每個 memcgroup 的 LRU(cgroup 的私有 LRU)由 lruvec->lru_lock 保護;在 lruvec->lru_lock 下將頁面從其 LRU 隔離之前,會清除 folio LRU 標誌。
2.7 核心記憶體擴充套件¶
透過核心記憶體擴充套件,記憶體控制器能夠限制系統使用的核心記憶體量。核心記憶體與使用者記憶體根本不同,因為它不能被交換出去,這使得透過消耗過多這種寶貴的資源來發起 DoS 攻擊成為可能。
預設情況下,所有記憶體 cgroup 都啟用核心記憶體記賬。但可以透過在啟動時向核心傳遞 cgroup.memory=nokmem 來停用系統範圍的記賬。在這種情況下,將完全不記賬核心記憶體。
根 cgroup 不施加核心記憶體限制。根 cgroup 的使用量可能計費也可能不計費。使用的記憶體會累積到 memory.kmem.usage_in_bytes 中,或者在有意義的情況下累積到單獨的計數器中(目前僅適用於 TCP)。
主“kmem”計數器的資料會輸入主計數器,因此 kmem 計費也會在使用者計數器中可見。
目前尚未對核心記憶體實現軟限制。當達到這些限制時觸發 slab 回收是未來的工作。
2.7.1 當前記賬的核心記憶體資源¶
- 棧頁
每個程序都會消耗一些棧頁。透過記賬到核心記憶體中,我們可以在核心記憶體使用過高時阻止新程序的建立。
- Slab 頁
由 SLAB 或 SLUB 分配器分配的頁面會被跟蹤。每次快取第一次從 memcg 內部被觸及時,都會建立一個 kmem_cache 的副本。建立是惰性完成的,因此在快取建立期間,某些物件仍然可能被跳過。slab 頁中的所有物件都應屬於同一個 memcg。這僅在任務在快取分配頁面期間遷移到不同的 memcg 時才會失敗。
- 套接字記憶體壓力
一些套接字協議具有記憶體壓力閾值。記憶體控制器允許它們按每個 cgroup 單獨控制,而不是全域性控制。
- TCP 記憶體壓力
TCP 協議的套接字記憶體壓力。
2.7.2 常見用例¶
由於“kmem”計數器會輸入到主使用者計數器,因此核心記憶體永遠不能完全獨立於使用者記憶體進行限制。假設“U”是使用者限制,“K”是核心限制。有三種可能的限制設定方式:
- U != 0, K = 無限
這是在 kmem 記賬之前就已存在的標準 memcg 限制機制。核心記憶體被完全忽略。
- U != 0, K < U
核心記憶體是使用者記憶體的一個子集。這種設定在每個 cgroup 的總記憶體被超額分配的部署中非常有用。超額分配核心記憶體限制絕對不推薦,因為系統仍然可能耗盡不可回收的記憶體。在這種情況下,管理員可以設定 K,使得所有組的總和永遠不會大於總記憶體,並自由設定 U,但會犧牲其 QoS。
警告
在當前實現中,當 cgroup 達到 K 但仍低於 U 時,不會觸發記憶體回收,這使得此設定不實用。
- U != 0, K >= U
由於 kmem 費用也將計入使用者計數器,並且兩種記憶體都會觸發 cgroup 的回收。此設定為管理員提供了記憶體的統一檢視,對於只想跟蹤核心記憶體使用情況的人也很有用。
3. 使用者介面¶
要使用使用者介面:
啟用 CONFIG_CGROUPS 和 CONFIG_MEMCG 選項
準備 cgroup(有關背景資訊,請參閱 為什麼需要 cgroup?)
# mount -t tmpfs none /sys/fs/cgroup # mkdir /sys/fs/cgroup/memory # mount -t cgroup none /sys/fs/cgroup/memory -o memory
建立新組並將 bash 移入其中
# mkdir /sys/fs/cgroup/memory/0 # echo $$ > /sys/fs/cgroup/memory/0/tasks
既然我們處於 0 cgroup 中,我們可以修改記憶體限制
# echo 4M > /sys/fs/cgroup/memory/0/memory.limit_in_bytes
現在可以查詢限制
# cat /sys/fs/cgroup/memory/0/memory.limit_in_bytes 4194304
注意
我們可以使用字尾(k、K、m、M、g 或 G)來表示千位元組、兆位元組或吉位元組的值。(這裡,Kilo、Mega、Giga 是 Kibibytes、Mebibytes、Gibibytes。)
注意
我們可以寫入“-1”來重置 *.limit_in_bytes(unlimited)。
注意
我們不能再對根 cgroup 設定限制。
我們可以檢查使用情況
# cat /sys/fs/cgroup/memory/0/memory.usage_in_bytes
1216512
成功寫入此檔案並不能保證成功將此限制設定為寫入檔案中的值。這可能由於多種因素造成,例如向上舍入到頁面邊界或系統上記憶體的總可用性。使用者需要在寫入後重新讀取此檔案,以保證核心提交的值
# echo 1 > memory.limit_in_bytes
# cat memory.limit_in_bytes
4096
memory.failcnt 欄位表示 cgroup 限制被超過的次數。
memory.stat 檔案提供記賬資訊。現在,顯示了快取、RSS 以及活躍頁/非活躍頁的數量。
4. 測試¶
有關測試功能和實現,請參閱 記憶體資源控制器 (Memcg) 實現備忘錄。
效能測試也很重要。為了檢視純記憶體控制器的開銷,在 tmpfs 上進行測試將為您提供少量開銷的良好資料。示例:在 tmpfs 上執行核心 make。
頁面故障可伸縮性也很重要。在測量並行頁面故障測試時,多程序測試可能優於多執行緒測試,因為它具有共享物件/狀態的噪聲。
但以上兩種都屬於極端情況測試。在記憶體控制器下進行常規測試總是有幫助的。
4.1 故障排除¶
有時使用者可能會發現 cgroup 下的應用程式被 OOM killer 終止。這有幾個原因:
cgroup 限制太低(低到無法進行任何有用的操作)
使用者正在使用匿名記憶體,並且交換已關閉或太低
執行 sync 後再執行 echo 1 > /proc/sys/vm/drop_caches 將有助於清除 cgroup 中快取的一些頁面(頁面快取頁)。
要了解發生了什麼,可以按照 “10. OOM 控制”(下文)停用 OOM_Kill 並觀察結果,這將很有幫助。
4.2 任務遷移¶
當一個任務從一個 cgroup 遷移到另一個 cgroup 時,其費用預設不會被帶走。從原始 cgroup 分配的頁面仍然由其計費,當頁面被釋放或回收時,費用才會被取消。
您可以將任務的費用隨任務遷移一同移動。參見 8. “任務遷移時移動費用”
4.3 移除 cgroup¶
cgroup 可以透過 rmdir 移除,但如 第 4.1 節 和 第 4.2 節 所述,即使所有任務都已從中遷移,cgroup 可能仍然與某些費用相關聯。(因為我們是按頁面計費,而不是按任務計費。)
我們將統計資訊移動到父級,除了取消子級的計費外,計費沒有變化。
cgroup 移除時,交換資訊中記錄的費用不會更新。記錄的資訊將被丟棄,使用交換(swapcache)的 cgroup 將被記為新的所有者。
5. 其他介面¶
5.1 force_empty¶
提供了 memory.force_empty 介面以清空 cgroup 的記憶體使用量。當向此寫入任何內容時,
# echo 0 > memory.force_emptycgroup 將被回收,並儘可能多地回收頁面。
此介面的典型用例是在呼叫 rmdir() 之前。儘管 rmdir() 會下線 memcg,但由於已計費的檔案快取,memcg 仍可能存在。一些不用的頁面快取可能會保持計費狀態,直到發生記憶體壓力。如果您想避免這種情況,force_empty 將會很有用。
5.2 stat 檔案¶
memory.stat 檔案包含以下統計資訊:
每個記憶體 cgroup 的本地狀態
快取 (cache)
頁面快取記憶體的位元組數。
常駐集大小 (rss)
匿名和交換快取記憶體的位元組數(包括透明大頁)。
rss_huge
匿名透明大頁的位元組數。
對映檔案 (mapped_file)
對映檔案的位元組數(包括 tmpfs/shmem)
pgpgin
記憶體 cgroup 的計費事件次數。每次頁面被記為對映匿名頁 (RSS) 或快取頁 (Page Cache) 到 cgroup 時,都會發生計費事件。
pgpgout
記憶體 cgroup 的解除計費事件次數。每次頁面從 cgroup 中解除計費時,都會發生解除計費事件。
交換 (swap)
交換使用量的位元組數
swapcached
記憶體中快取的交換頁位元組數
髒頁 (dirty)
等待寫回磁碟的位元組數。
回寫 (writeback)
排隊等待同步到磁碟的檔案/匿名快取的位元組數。
inactive_anon
非活躍 LRU 列表上的匿名和交換快取記憶體的位元組數。
active_anon
活躍 LRU 列表上的匿名和交換快取記憶體的位元組數。
inactive_file
非活躍 LRU 列表上的檔案支援記憶體和 MADV_FREE 匿名記憶體(LazyFree 頁)的位元組數。
active_file
活躍 LRU 列表上的檔案支援記憶體的位元組數。
不可回收 (unevictable)
無法回收的記憶體位元組數(mlocked 等)。
考慮層次結構的狀態(參見 memory.use_hierarchy 設定)
hierarchical_memory_limit
記憶體 cgroup 所屬層次結構下的記憶體限制位元組數
hierarchical_memsw_limit
記憶體 cgroup 所屬層次結構下的記憶體+交換限制位元組數。
total_<counter>
是
的分層版本,除了 cgroup 自己的值外,還包括所有分層子 cgroup 的 值之和,例如 total_cache 附加 VM 引數(取決於 CONFIG_DEBUG_VM)
recent_rotated_anon
VM 內部引數。(參見 mm/vmscan.c)
recent_rotated_file
VM 內部引數。(參見 mm/vmscan.c)
recent_scanned_anon
VM 內部引數。(參見 mm/vmscan.c)
recent_scanned_file
VM 內部引數。(參見 mm/vmscan.c)
提示
recent_rotated 表示 LRU 輪換的最近頻率。recent_scanned 表示最近對 LRU 的掃描次數。顯示這些是為了更好的除錯,請參閱程式碼以瞭解其含義。
注意
只有匿名和交換快取記憶體被列為“rss”統計的一部分。這不應與真正的“常駐集大小”或 cgroup 使用的物理記憶體量混淆。
“rss + mapped_file”將給出 cgroup 的常駐集大小。
請注意,某些核心配置可能會將完整的較大分配(例如 THP)計入“rss”和“mapped_file”,即使只有部分而非全部記憶體被對映。
(注意:檔案和共享記憶體可能在其他 cgroup 之間共享。在這種情況下,只有當記憶體 cgroup 是頁面快取的所有者時,才會記賬 mapped_file。)
5.3 swappiness¶
覆蓋特定組的 /proc/sys/vm/swappiness。根 cgroup 中的可調引數對應於全域性 swappiness 設定。
請注意,與全域性回收不同,限制回收強制規定 0 swappiness 確實能阻止任何交換,即使存在交換儲存。如果檔案頁無可回收,這可能導致 memcg OOM killer。
5.4 failcnt¶
記憶體 cgroup 提供 memory.failcnt 和 memory.memsw.failcnt 檔案。此 failcnt(== 失敗計數)顯示使用計數器達到其限制的次數。當記憶體 cgroup 達到限制時,failcnt 會增加,並且其下的記憶體將被回收。
您可以透過向 failcnt 檔案寫入 0 來重置 failcnt
# echo 0 > .../memory.failcnt
5.5 usage_in_bytes¶
為了效率,與其他核心元件一樣,記憶體 cgroup 使用了一些最佳化來避免不必要的快取行錯誤共享。usage_in_bytes 受此方法影響,不會顯示記憶體(和交換)使用量的“精確”值,它是一個用於高效訪問的模糊值。(當然,必要時會同步。)如果您想了解更精確的記憶體使用量,應該使用 memory.stat 中的 RSS+CACHE(+SWAP) 值(參見 5.2)。
5.6 numa_stat¶
這與 numa_maps 類似,但按每個 memcg 操作。這對於提供 memcg 內的 numa 區域性性資訊可見性很有用,因為頁面可以從任何物理節點分配。其中一個用例是透過將此資訊與應用程式的 CPU 分配結合起來評估應用程式效能。
每個 memcg 的 numa_stat 檔案包括每個節點的“total”、“file”、“anon”和“unevictable”頁面計數,以及“hierarchical_<counter>”,後者除了 memcg 自己的值外,還彙總了所有層次子節點的值。
memory.numa_stat 的輸出格式是
total=<total pages> N0=<node 0 pages> N1=<node 1 pages> ...
file=<total file pages> N0=<node 0 pages> N1=<node 1 pages> ...
anon=<total anon pages> N0=<node 0 pages> N1=<node 1 pages> ...
unevictable=<total anon pages> N0=<node 0 pages> N1=<node 1 pages> ...
hierarchical_<counter>=<counter pages> N0=<node 0 pages> N1=<node 1 pages> ...
“total”計數是 file + anon + unevictable 的總和。
6. 層次結構支援¶
記憶體控制器支援深層層次結構和分層記賬。層次結構是透過在 cgroup 檔案系統中建立適當的 cgroup 來建立的。例如,考慮以下 cgroup 檔案系統層次結構:
root
/ | \
/ | \
a b c
| \
| \
d e
在上圖中,啟用分層記賬後,e 的所有記憶體使用量都會記賬到其所有祖先(即 c 和根)。如果其中一個祖先超過其限制,回收演算法會從祖先及其子任務中回收記憶體。
6.1 分層記賬和回收¶
分層記賬預設啟用。停用分層記賬已棄用。嘗試停用將導致失敗,並向 dmesg 列印警告。
出於相容性原因,向 memory.use_hierarchy 寫入 1 始終會透過
# echo 1 > memory.use_hierarchy
7. 軟限制(已棄用)¶
此功能已棄用!
軟限制允許更大程度地共享記憶體。軟限制背後的理念是允許控制組根據需要使用盡可能多的記憶體,前提是:
沒有記憶體爭用
它們不超過其硬限制
當系統檢測到記憶體爭用或記憶體不足時,控制組將被推回到它們的軟限制。如果每個控制組的軟限制非常高,它們將被儘可能地推回,以確保一個控制組不會使其他控制組的記憶體餓死。
請注意,軟限制是一個盡力而為的功能;它不提供任何保證,但它會盡力確保在記憶體嚴重爭用時,根據軟限制提示/設定分配記憶體。目前,基於軟限制的回收設定為從 balance_pgdat (kswapd) 呼叫。
7.1 介面¶
軟限制可以透過以下命令設定(在此示例中,我們假設軟限制為 256 MiB):
# echo 256M > memory.soft_limit_in_bytes
如果我們想將其更改為 1G,我們可以隨時使用:
# echo 1G > memory.soft_limit_in_bytes
注意
軟限制會在很長一段時間內生效,因為它們涉及回收記憶體以在記憶體 cgroup 之間進行平衡
注意
建議將軟限制始終設定在硬限制之下,否則硬限制將優先。
8. 任務遷移時移動費用(已棄用!)¶
此功能已棄用!
讀取 memory.move_charge_at_immigrate 將始終返回 0,寫入它將始終返回 -EINVAL。
9. 記憶體閾值¶
記憶體 cgroup 使用 cgroup 通知 API(參見 控制組)實現記憶體閾值。它允許註冊多個記憶體和 memsw 閾值,並在超過閾值時獲取通知。
要註冊閾值,應用程式必須:
使用 eventfd(2) 建立一個 eventfd;
開啟 memory.usage_in_bytes 或 memory.memsw.usage_in_bytes;
向 cgroup.event_control 寫入字串,如 “<event_fd> <fd of memory.usage_in_bytes> <threshold>”。
當記憶體使用量向任何方向超過閾值時,應用程式將透過 eventfd 收到通知。
它適用於根 cgroup 和非根 cgroup。
10. OOM 控制(已棄用)¶
此功能已棄用!
memory.oom_control 檔案用於 OOM 通知和其他控制。
記憶體 cgroup 使用 cgroup 通知 API(參見 控制組)實現 OOM 通知器。它允許註冊多個 OOM 通知傳遞,並在 OOM 發生時獲取通知。
要註冊通知器,應用程式必須:
使用 eventfd(2) 建立一個 eventfd
開啟 memory.oom_control 檔案
向 cgroup.event_control 寫入字串,如 “<event_fd> <fd of memory.oom_control>”
當 OOM 發生時,應用程式將透過 eventfd 收到通知。OOM 通知不適用於根 cgroup。
您可以透過向 memory.oom_control 檔案寫入“1”來停用 OOM-killer,如下所示:
#echo 1 > memory.oom_control
如果 OOM-killer 被停用,cgroup 下的任務在請求可記賬記憶體時將掛起/休眠在記憶體 cgroup 的 OOM-waitqueue 中。
要執行它們,您必須透過以下方式緩解記憶體 cgroup 的 OOM 狀態:
增大限制或減少使用量。
要減少使用量:
殺死一些任務。
透過賬戶遷移將一些任務移動到其他組。
刪除一些檔案(在 tmpfs 上?)
然後,停止的任務將再次工作。
讀取時,將顯示 OOM 的當前狀態。
oom_kill_disable 0 或 1(如果為 1,則 OOM-killer 已停用)
under_oom 0 或 1(如果為 1,則記憶體 cgroup 處於 OOM 狀態,任務可能會停止。)
oom_kill 整數計數器 被任何 OOM killer 殺死的屬於此 cgroup 的程序數量。
11. 記憶體壓力(已棄用)¶
此功能已棄用!
壓力級別通知可用於監控記憶體分配成本;應用程式可以根據壓力實現不同的記憶體資源管理策略。壓力級別定義如下:
“低”級別表示系統正在為新分配回收記憶體。監控這種回收活動可能有助於維持快取級別。收到通知後,程式(通常是“Activity Manager”)可能會分析 vmstat 並提前採取行動(即提前關閉不重要的服務)。
“中”級別表示系統正在經歷中等記憶體壓力,系統可能正在進行交換、將活躍檔案快取分頁出等。在此事件發生時,應用程式可以決定進一步分析 vmstat/zoneinfo/memcg 或內部記憶體使用統計資訊,並釋放任何可以輕鬆重建或從磁碟重新讀取的資源。
“臨界”級別表示系統正在積極地抖動,即將記憶體不足 (OOM),甚至核心中的 OOM killer 正在準備觸發。應用程式應該盡其所能幫助系統。此時諮詢 vmstat 或任何其他統計資訊可能為時已晚,因此建議立即採取行動。
預設情況下,事件會向上傳播,直到事件被處理,即事件不是直通的。例如,您有三個 cgroup:A->B->C。現在您在 cgroup A、B 和 C 上設定了一個事件監聽器,並且假設 cgroup C 遇到了一些壓力。在這種情況下,只有 cgroup C 會收到通知,即 cgroup A 和 B 不會收到。這樣做是為了避免過多的訊息“廣播”,這會干擾系統,尤其是在記憶體不足或抖動時更糟糕。只有當 cgroup C 沒有事件監聽器時,cgroup B 才會收到通知。
有三種可選模式,用於指定不同的傳播行為:
“default”:“預設”:這是上面指定的預設行為。此模式與省略可選模式引數相同,以保持向後相容性。
“hierarchy”:“層次結構”:事件總是傳播到根目錄,類似於預設行為,不同之處在於,無論每個級別是否存在事件監聽器,傳播都會繼續,採用“hierarchy”模式。在上述示例中,組 A、B 和 C 將收到記憶體壓力通知。
“local”:“本地”:事件是直通的,即只有當註冊了通知的 memcg 中出現記憶體壓力時,它們才會收到通知。在上述示例中,如果組 C 註冊了“本地”通知並且該組遇到記憶體壓力,則組 C 將收到通知。但是,無論組 C 是否有事件監聽器,如果組 B 註冊了本地通知,組 B 將永遠不會收到通知。
級別和事件通知模式(如果需要,可以是“hierarchy”或“local”)由逗號分隔的字串指定,例如“low,hierarchy”指定所有祖先 memcg 的分層、直通通知。預設的、非直通行為的通知不指定模式。“medium,local”指定中等級別的直通通知。
檔案 memory.pressure_level 僅用於設定 eventfd。要註冊通知,應用程式必須:
使用 eventfd(2) 建立一個 eventfd;
開啟 memory.pressure_level;
向 cgroup.event_control 寫入字串,例如 “<event_fd> <fd of memory.pressure_level> <level[,mode]>”。
當記憶體壓力達到特定級別(或更高)時,應用程式將透過 eventfd 收到通知。對 memory.pressure_level 的讀/寫操作未實現。
測試
這是一個小指令碼示例,它建立一個新的 cgroup,設定記憶體限制,在 cgroup 中設定通知,然後使子 cgroup 經歷臨界壓力:
# cd /sys/fs/cgroup/memory/ # mkdir foo # cd foo # cgroup_event_listener memory.pressure_level low,hierarchy & # echo 8000000 > memory.limit_in_bytes # echo 8000000 > memory.memsw.limit_in_bytes # echo $$ > tasks # dd if=/dev/zero | read x(預期會收到大量通知,最終會觸發 OOM-killer。)
12. 待辦事項¶
使每個 cgroup 掃描器首先回收非共享頁面
教控制器記賬共享頁面
當尚未達到限制但使用量正在接近時,在後臺啟動回收
總結¶
總的來說,記憶體控制器一直是一個穩定的控制器,並且在社群中得到了廣泛的評論和討論。
參考資料¶
Menage, Paul. 控制組 v10, http://lwn.net/Articles/236032/
Vaidyanathan, Srinivasan. 控制組:頁面快取記賬和控制子系統 (v3), http://lwn.net/Articles/235534/
Singh, Balbir. RSS 控制器 v2 測試結果 (lmbench), https://lore.kernel.org/r/464C95D4.7070806@linux.vnet.ibm.com
Singh, Balbir. RSS 控制器 v2 AIM9 結果 https://lore.kernel.org/r/464D267A.50107@linux.vnet.ibm.com
Singh, Balbir. 記憶體控制器 v6 測試結果, https://lore.kernel.org/r/20070819094658.654.84837.sendpatchset@balbir-laptop
Singh, Balbir. 記憶體控制器介紹 (v6), https://lore.kernel.org/r/20070817084228.26003.12568.sendpatchset@balbir-laptop
Corbet, Jonathan. 控制 cgroup 中的記憶體使用, http://lwn.net/Articles/243795/