核心同頁合併

概述

KSM 是一種節省記憶體的去重功能,透過 CONFIG_KSM=y 啟用,在 2.6.32 版本中新增到 Linux 核心。有關其實現,請參見 mm/ksm.c,以及 http://lwn.net/Articles/306704/https://lwn.net/Articles/330589/

KSM 最初是為 KVM 開發的(在那裡它被稱為核心共享記憶體),透過共享虛擬機器之間常見的資料,將更多的虛擬機器裝入物理記憶體中。但它對生成相同資料的許多例項的任何應用程式都有用。

KSM 守護程式 ksmd 定期掃描已向其註冊的使用者記憶體區域,尋找內容相同的頁面,這些頁面可以被單個防寫頁面替換(如果程序稍後想要更新其內容,該頁面會自動複製)。KSM 守護程式在一次掃描中掃描的頁面數量以及掃描之間的時間間隔透過 sysfs 介面配置

KSM 只合並匿名(私有)頁面,從不合並頁面快取(檔案)頁面。KSM 合併的頁面最初被鎖定到核心記憶體中,但現在可以像其他使用者頁面一樣被交換出去(但在交換回來時共享會被破壞:ksmd 必須重新發現它們的身份並再次合併)。

使用 madvise 控制 KSM

KSM 只對應用程式已透過 madvise(2) 系統呼叫建議可能合併的地址空間區域進行操作

int madvise(addr, length, MADV_MERGEABLE)

應用程式可以呼叫

int madvise(addr, length, MADV_UNMERGEABLE)

取消該建議並恢復未共享的頁面:之後 KSM 會取消合併它在該範圍內合併的任何內容。注意:此取消合併呼叫可能突然需要比可用記憶體更多的記憶體 - 可能因 EAGAIN 而失敗,但更可能引發 Out-Of-Memory killer。

如果 KSM 沒有配置到執行中的核心中,madvise MADV_MERGEABLE 和 MADV_UNMERGEABLE 只會因 EINVAL 而失敗。如果執行中的核心是用 CONFIG_KSM=y 構建的,這些呼叫通常會成功:即使 KSM 守護程式當前沒有執行,MADV_MERGEABLE 仍然會註冊該範圍,以便在 KSM 守護程式啟動時使用;即使該範圍不能包含 KSM 實際上可以合併的任何頁面;即使 MADV_UNMERGEABLE 應用於從未 MADV_MERGEABLE 的範圍。

如果必須將記憶體區域拆分為至少一個新的 MADV_MERGEABLE 或 MADV_UNMERGEABLE 區域,如果程序將超過 vm.max_map_count,madvise 可能會返回 ENOMEM(請參閱 /proc/sys/vm/ 的文件)。

與其他 madvise 呼叫一樣,它們旨在用於使用者地址空間的對映區域:如果指定的範圍包括未對映的間隙,它們將報告 ENOMEM(儘管在中間的對映區域上工作),並且如果沒有足夠的記憶體用於內部結構,可能會因 EAGAIN 而失敗。

應用程式應謹慎使用 MADV_MERGEABLE,將其使用限制在可能受益的區域。KSM 的掃描可能會消耗大量的處理能力:一些安裝可能會因此而停用 KSM。

KSM 守護程式 sysfs 介面

KSM 守護程式由 /sys/kernel/mm/ksm/ 中的 sysfs 檔案控制,所有人都可以讀取,但只有 root 使用者可以寫入

pages_to_scan

ksmd 進入休眠狀態之前要掃描多少頁,例如 echo 100 > /sys/kernel/mm/ksm/pages_to_scan

如果 advisor_mode 已設定為 scan-time,則無法更改 pages_to_scan 值。

預設值:100(出於演示目的而選擇)

sleep_millisecs

ksmd 在下次掃描之前應休眠多少毫秒,例如 echo 20 > /sys/kernel/mm/ksm/sleep_millisecs

預設值:20(出於演示目的而選擇)

merge_across_nodes

指定是否可以合併來自不同 NUMA 節點的頁面。設定為 0 時,ksm 只合並物理上位於同一 NUMA 節點記憶體區域的頁面。這會降低對共享頁面的訪問延遲。具有更多節點且 NUMA 距離顯著的系統可能會受益於設定 0 帶來的較低延遲。需要最大限度地減少記憶體使用量的小型系統可能會受益於設定 1(預設值)帶來的更大的共享。在決定使用哪個設定之前,您可能希望比較系統在每個設定下的效能。merge_across_nodes 設定只能在系統中沒有 ksm 共享頁面時更改:首先設定 run 2 以取消合併頁面,然後在更改 merge_across_nodes 後設置為 1,以根據新設定重新合併。

預設值:1(像早期版本一樣跨節點合併)

run
  • 設定為 0 以停止 ksmd 執行但保留合併的頁面,

  • 設定為 1 以執行 ksmd,例如 echo 1 > /sys/kernel/mm/ksm/run

  • 設定為 2 以停止 ksmd 並取消合併當前合併的所有頁面,但保留可合併區域以供下次執行註冊。

預設值:0(必須更改為 1 才能啟用 KSM,除非停用 CONFIG_SYSFS)

use_zero_pages

指定是否應特殊處理空頁面(即僅包含零的已分配頁面)。設定為 1 時,空頁面將與核心零頁面合併,而不是像通常那樣彼此合併。這可以提高具有彩色零頁面的架構的效能,具體取決於工作負載。啟用此設定時應小心,因為它可能會降低某些工作負載的 KSM 效能,例如,如果候選合併頁面的校驗和與空頁面的校驗和匹配。此設定可以隨時更改,它僅對更改後合併的頁面有效。

預設值:0(與早期版本中相同的正常 KSM 行為)

max_page_sharing

允許每個 KSM 頁面的最大共享數。這會強制執行去重限制,以避免虛擬記憶體操作的高延遲,這些操作涉及遍歷共享 KSM 頁面的虛擬對映。最小值為 2,因為新建立的 KSM 頁面將至少有兩個共享者。此值越高,KSM 合併記憶體的速度越快,去重因子越高,但對於任何給定的 KSM 頁面,最壞情況下的虛擬對映遍歷可能會越慢。降低此遍歷速度意味著某些虛擬記憶體操作在交換、壓縮、NUMA 平衡和頁面遷移期間會產生更高的延遲,從而降低這些虛擬記憶體操作呼叫者的響應能力。其他未參與執行虛擬對映遍歷的 VM 操作的任務的排程器延遲不受此引數的影響,因為這些遍歷本身始終是排程器友好的。

stable_node_chains_prune_millisecs

指定 KSM 檢查達到去重限制的頁面的元資料中是否存在陳舊資訊的頻率。較小的 milllisecs 值將以較低的延遲釋放 KSM 元資料,但它們會使 ksmd 在掃描期間使用更多 CPU。如果還沒有單個 KSM 頁面達到 max_page_sharing,則它是空操作。

smart_scan

從歷史上看,KSM 每次掃描都會檢查每個候選頁面。它沒有考慮歷史資訊。啟用智慧掃描後,以前未去重的頁面將被跳過。跳過這些頁面的頻率取決於已經嘗試並失敗去重的頻率。預設情況下,此最佳化已啟用。pages_skipped 指標顯示了該設定的有效性。

advisor_mode

advisor_mode 選擇當前的顧問。支援兩種模式:none 和 scan-time。預設值為 none。透過將 advisor_mode 設定為 scan-time,啟用掃描時間顧問。有關 advisor 的部分詳細解釋了掃描時間顧問的工作原理。

adivsor_max_cpu

指定 ksmd 後臺執行緒的 cpu 百分比使用率的上限。預設值為 70。

advisor_target_scan_time

指定掃描所有候選頁面的目標掃描時間(以秒為單位)。預設值為 200 秒。

advisor_min_pages_to_scan

指定掃描時間顧問的 pages_to_scan 引數的下限。預設值為 500。

adivsor_max_pages_to_scan

指定掃描時間顧問的 pages_to_scan 引數的上限。預設值為 30000。

KSM 和 MADV_MERGEABLE 的有效性顯示在 /sys/kernel/mm/ksm/

general_profit

KSM 的有效性。計算方法如下所述。

pages_scanned

正在掃描多少頁以供 ksm 使用

pages_shared

正在使用多少共享頁

pages_sharing

還有多少個站點共享它們,即節省了多少

pages_unshared

有多少頁是唯一的,但重複檢查以進行合併

pages_volatile

有多少頁更改太快而無法放入樹中

pages_skipped

“智慧”頁面掃描演算法跳過了多少頁

full_scans

已掃描所有可合併區域的次數

stable_node_chains

達到 max_page_sharing 限制的 KSM 頁面的數量

stable_node_dups

重複 KSM 頁面的數量

ksm_zero_pages

在去重時,有多少仍然對映到程序中的零頁面由 KSM 對映。

啟用 use_zero_pages 時,pages_sharing + ksm_zero_pages 的總和表示 KSM 實際儲存的頁數。如果從未啟用 use_zero_pages,則 ksm_zero_pages 為 0。

pages_sharingpages_shared 的高比率表示良好的共享,但 pages_unsharedpages_sharing 的高比率表示浪費精力。pages_volatile 包含幾種不同的活動,但那裡的高比例也表明 madvise MADV_MERGEABLE 的使用不當。

pages_sharing/pages_shared 比率的最大可能值受 max_page_sharing 可調引數的限制。要增加該比率,必須相應地增加 max_page_sharing

監控 KSM 利潤

KSM 可以透過合併相同的頁面來節省記憶體,但也會消耗額外的記憶體,因為它需要生成許多 rmap_items 來儲存每個掃描頁面的簡短 rmap 資訊。其中一些頁面可能會被合併,但有些頁面在多次檢查後可能無法合併,這些都是無利可圖的記憶體消耗。

  1. 如何在系統範圍內確定 KSM 是節省記憶體還是消耗記憶體?以下是一個簡單的近似計算,供參考

    general_profit =~ ksm_saved_pages * sizeof(page) - (all_rmap_items) *
                      sizeof(rmap_item);
    

    其中 ksm_saved_pages 等於系統的 pages_sharing + ksm_zero_pages 的總和,並且可以透過將 pages_sharingpages_sharedpages_unsharedpages_volatile 相加來輕鬆獲得 all_rmap_items。

  2. 可以透過以下近似計算類似地獲得單個程序中的 KSM 利潤

    process_profit =~ ksm_saved_pages * sizeof(page) -
                      ksm_rmap_items * sizeof(rmap_item).
    

    其中 ksm_saved_pages 等於 ksm_merging_pagesksm_zero_pages 的總和,它們都顯示在目錄 /proc/<pid>/ksm_stat 下,並且 ksm_rmap_items 也顯示在 /proc/<pid>/ksm_stat 中。程序利潤也顯示在 /proc/<pid>/ksm_stat 中,作為 ksm_process_profit。

從應用程式的角度來看,ksm_rmap_itemsksm_merging_pages 的高比率意味著糟糕的 madvise 應用策略,因此開發人員或管理員必須重新考慮如何更改 madvise 策略。給出一個參考示例,頁面的大小通常為 4K,rmap_item 的大小在 32 位 CPU 架構上單獨為 32B,在 64 位 CPU 架構上為 64B。因此,如果 ksm_rmap_items/ksm_merging_pages 比率在 64 位 CPU 上超過 64,或在 32 位 CPU 上超過 128,則應刪除該應用程式的 madvise 策略,因為 ksm 利潤大約為零或為負。

監控 KSM 事件

/proc/vmstat 中有一些計數器可用於監控 KSM 事件。KSM 可能有助於節省記憶體,這是一種權衡,可能會導致 KSM COW 或交換複製時的延遲。這些事件可以幫助使用者評估是否或如何使用 KSM。例如,如果 cow_ksm 增加得太快,使用者可能會減少 madvise(, , MADV_MERGEABLE) 的範圍。

cow_ksm

每次 KSM 頁面觸發寫時複製 (COW) 時都會遞增,當用戶嘗試寫入 KSM 頁面時,我們必須建立一個副本。

ksm_swpin_copy

每次交換時複製 KSM 頁面時都會遞增,請注意,在交換時可能會複製 KSM 頁面,因為 do_swap_page() 無法執行重新構造跨 anon_vma KSM 頁面所需的所有鎖定。

顧問

KSM 的候選頁面數量是動態的。通常可以觀察到,在應用程式啟動期間,需要處理更多的候選頁面。如果沒有顧問,則需要為最大數量的候選頁面設定 pages_to_scan 引數的大小。掃描時間顧問可以根據需要更改 pages_to_scan 引數。

可以啟用顧問,以便 KSM 可以自動適應要掃描的候選頁面數量的變化。實現了兩個顧問:none 和 scan-time。使用 none 時,不啟用任何顧問。預設值為 none。

掃描時間顧問根據觀察到的掃描時間更改 pages_to_scan 引數。可能的 pages_to_scan 引數值受 advisor_max_cpu 引數的限制。此外,還有 advisor_target_scan_time 引數。此引數設定掃描所有 KSM 候選頁面的目標時間。advisor_target_scan_time 引數決定了掃描時間顧問掃描候選頁面的積極程度。較低的值使掃描時間顧問更積極地掃描。這是掃描時間顧問配置的最重要的引數。

可以使用 advisor_min_pages_to_scanadvisor_max_pages_to_scan 更改初始值和最大值。預設值足以滿足大多數工作負載和用例。

掃描完成後,會重新計算 pages_to_scan 引數。

-- Izik Eidus, Hugh Dickins, 2009 年 11 月 17 日