KSM的使用

使能KSM

KSM只會處理通過madvise系統調用顯式指定的用戶進程地址空間,因此用戶程序想使用這個功能就必須在分配地址空間時顯式地調用madvise(addr,length,MADV_MERGEA BLE)。如果用戶想在KSM中取消某一個用戶進程地址空間的合併功能,也需要顯式地調用madvise(addr,length,MADV_UNMERGEABLE)。 下面是測試KSM的test.c程序的代碼片段,使用mmap():來創建一個文件的私有映射,並且調用memset()寫入這些私有映射的內容緩存頁面中。

《測試KSM的test.c程序的代碼片段》
#include <stdio.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
int main(int argc,char *argv[])
{
    char *buf;
    char filename[64]="";
    struct stat stat;
    int size =100*4096;
    int fd =0;

    strcpy(filename, argv[1]);

    fd = open(filename,O_RDWR | O_CREAT,0664);

    fstat(fd, &stat);

    buf = mmap (NULL,stat.st_size,PROT_WRITE,MAP_PRIVATE,fd,0);

    memset(buf,0x55,stat.st_size);

    madvise(buf,stat.st_size, MADV_MERGEABLE);

    while (1)
        sleep(1);
}

編譯上述test.c程序。

gcc test.c -o test

使用dd命令創建一個ksm.dat文件,即創建100MB大小的文件。

echo 1 >/sys/kernel/mm/ksm/run

運行test.c程序。

#./test ksm.dat

過一段時間之後,查看系統有多少頁面合併了。

root@benshushu#cat /sys/kernel/mm/ksm/pages_sharing
25500
root@benshushu#cat /sys/kernel/mm/ksm/pages_shared
100
root@benshushu:/home#cat /sys/kernel/mm/ksm/pages_unshared
0

可以看到pages_shared爲100說明系統有100個共享的頁面。若有100個頁面的內同,它們可以合併成一個頁面,這時pages_shared爲1。 pages_sharing 爲25500說明有25500個頁面合併了。 100MB的內存可存放25600個頁面。因此,我們可以看到,KSM把這25600個頁面分別合併成1共享的頁面,每一個共享頁面裏共享了其他的255個頁面,爲什麼會這樣?我們稍後詳細解析。 pages_unshared表示當前未合併頁面的數量。

rootebenshushut cat /sys/kernel/mn/ksm/stable_node_chains
1
rootebenshushut cat /sys/kernel/mm/ksm/stable_node_dups
100

stable_node_chains表示包含了鏈式的穩定節點的個數,當前系統中爲1,說明只有一個鏈式的穩定節點,但是這個穩定的節點裏包含了鏈表。 stable_node_dups表示穩定的節點所在的鏈表包含的元素總數。 KSM的sysfs節點在/sys/kernel/mm/ksm/目錄下,其主要節點的描述如下所示。

  • run:可以設置爲0~2。若設置0,暫停ksmd內核線程;若設置1,啓動ksmd內核線程;若設置2,取消所有已經合併好的頁面
  • full_scans: 完整掃描和合並區域的次數
  • pages_volatile: 表示還沒掃描和合並的頁面數量。若由於頁面內容更改過快導致兩次計算的校驗值不相等,那麼這些頁面是無法添加到紅黑樹裏的
  • sleep_millisecs: ksmd內核線程掃描一次的時間間隔
  • pages_to_scan: 單次掃描頁面的數量
  • pages_shared: 合併後的頁面數。如果100個頁面的內容相同,那麼可以把它們合併成一個頁面,這時pages_shared的值爲1
  • pages_sharing: 共享的頁面數。如果兩個頁面的內容相同,它們可以合併成一個頁面,那麼有一個頁面要作爲穩定的節點,這時pages_shared的值爲1,pages_sharing也爲1。第3個頁面也合併進來後,pages_sharing的值爲2,表示兩個頁面共享同一個穩定的節點
  • pages_unshared: 當前未合併頁面數量
  • max_page_sharing: 這是在Linux4.3內核中新增的參數,表示一個穩定的節點最多可以合併的頁面數量。這個值默認是256
  • stable_node_chains: 鏈表類型的穩定節點的個數。每個鏈式的穩定節點代表頁面內容相同的KM頁面。這個鏈式的穩定節點可以包含多個dup成員,每個dup成員最多包含256個共享的頁面
  • stable_node_dups: 鏈表中dup成員的個數。這些dup成員會連接到鏈式的穩定節點的hlist鏈表中

KSM在初始化時會創建一個名爲ksmd的內核線程。

<mm/ksm.c>
static int __init ksm_init(void)
{
    ksm_thread = kthread_run(ksm_scan_thread, NULL, "ksmd");
}
subsys_initcall(ksm_init);

在tes.c程序中創建私有映射(MAP_PRIVATE)之後,顯式地調用madvise系統調用把用戶進程地址空間添加到 Linux內核的KSM系統中。

<madvise()->ksm_madvise()-> ksm_enter()>

int __ksm_enter(struct mm_struct *mm)
{
    mm_slot = alloc_mm_slot();
    insert_to_mm_slots_hash(mm, mm_slot);
    list_add_tail(&mm slot->mm_list, &ksm_scan.mm_slot->mm list);
    set_bit(MME_VM_MERGEABLE, &mm->flags);
}

ksm_enter()函數會把當前的 mm_struct數據結構添加到 mm_slots_hash哈希表中。另外把 mm_slot添加到 ksm_scan.mm_slot->mm_list 鏈表中。最後,設置mm->flags中的 MMF_VM_MERGEABLE標誌位,表示這個進程已經被添加到KSM系統中.

<ksm內核線程>

static int ksm_scan_thread(void *nothing)
{
    while (!kthread_should_stop())
        if (ksmd_should_run())
            ksm_do_scan(ksm_thread_pages_to_scan)
    if (ksmd_should_run()) {
        sleep_ms =READ_ONCE(ksm_thread_sleep_millisecs);
        wait_event_interruptible_timeout(ksm_iter_wait,
        sleep_ms != READ_ONCE(ksm_thread_sleep_millisecs),
        secs_to_jiffies(sleep_ms));
    }
    return 0;
}

ksm_scan_thread()是ksmd內核線程的主幹,它運行 ksm_do_scan()函數,掃描和合並100個頁面,見 ksm_thread_pages_to_scan參數,然後等待20ms,見 ksm_thread_sleep_millisecs參數,這兩個參數可以在/sys/kernel/mm/ksm目錄下設置和修改。

<ksmd內核線程>

static void ksm_do_scan(unsignd int scan_npages)
{
    while(scan_npages-- && likely(!freezing(current))) {
        cond_resched();
        rmap_item = scan_get_next_rmap_item(&page);
        if (!rmap_item)
            return;
        cmp_and_merge_page(page, rmap_item);
        put_page(page)
    }
}

ksm_do_scan()函數在while循環中嘗試合併scan_npages個頁面, scan_get_next_rmap_item()獲取一個合適的匿名頁面。 cmp_and_merge_page()函數會讓頁面在KSM中穩定和不穩定的兩棵紅黑樹中查找是否有可以合併的對象,並且嘗試合併他們。

KSM基本實現

爲了讓讀者先有一個初步的認識,本節先介紹Lnux4.13內核之前的KSM實現,後文會介紹Linux5.0內核中的實現。

KSM機制下采用兩棵紅黑樹來管理掃描的頁面和己經合併的頁面。第一棵紅黑樹稱爲不穩定紅黑樹,裏面存放了還沒有合併的頁面;第二棵紅黑樹稱爲穩定紅黑樹,已經合併的頁面會生成一個節點,這個節點爲穩定節點。如兩個頁面的內容是一樣的,KSM掃描並發現了它們,因此這兩個頁面就可以合併成一個頁面。對於這個合併後的頁面,會設置只讀屬性,其中一個頁面會作爲穩定的節點掛載到穩定的紅黑樹中之後,另外一個頁面就會被釋放了。但是這兩個頁面的 rmap_item數據結構會被添如到穩定節點中的 hist 鏈表中,如下圖所示。

我們假設有3個VMA(表示進程地址空間,VMA的大小正好是一個頁面的大小,分別有3個頁面映射這3個VMA。這3個頁面準備通過KSM來掃描和合並,這3個頁面的內容是相同的。具體步驟如下。

  • 3個頁面會被添加到KSM中,第一輪掃描中分別給這3個頁面分配 rmap_item數據結構來描述它們,並且分別給它計算校驗和,如圖(a)所示
  • 第二輪掃描中,先掃描page0,若當前穩定的紅黑樹沒有成員,那麼不能比較和加入穩定的紅黑樹。接着,第二次計算校驗值,如果 page0的校驗值沒有發生變化,那麼把page0的rmap_item()添加到不穩定的紅黑樹中,如圖(b)所示。如果此時校驗值發生了變化,說明頁面內容發生變化,這種頁面不適合添加到不穩定的紅黑樹中
  • 掃描 page1,當前穩定的紅黑樹中沒有成員,略過穩定的紅黑樹的搜索。搜索不穩定的紅黑樹,遍歷紅黑樹中所有成員。 page1發現自己的內容與不穩定的紅黑樹中的 rmap_item()一致,因此嘗試將page0和 page1合併成一個穩定的節點,合併過程就是讓WMA0對映的虛擬地址、vaddr0映時到page1上。,並且把對應的PTE屬性修改成只讀展性。另外,VMA1映射到 page1的PTE屬性也設置爲只讀屬性。新創建一個穩定的節點,這個節點包含了page1的頁幀號等信息,把這個穩定的節點添加到穩定的紅黑樹中。把代表page0的 map _item0和page1的rmap_item1添加到這個穩定的節點的hlist鏈表中,最後釋放page0頁面,如圖(c)所示
  • 掃描page2。因爲穩定的紅黑樹中有成員,因此,先和穩定的紅黑樹中的成員進行比較,檢査是否可以合併。若發現page2的內容和穩定的節點內容一致,那麼把VMA2中的vaddr2映射到穩定的節點對應的 page1上,並且把PTE屬性設置爲只讀屬性。把代表page2的rmap_item2添加到穩定的節點的 hlist 鏈表中,最後釋放page2頁面,如圖(d)所示

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章