KSM (内存管理合并相同页)

Ksm介绍

2.6.32引入了KSM(Kernel Samepage Merging)允许这个系统管理程序通过合并内存页面来增加并发虚拟机的数量。VMware 的 ESX 服务器系统管理程序将这个特性命名为 Transparent Page Sharing (TPS),而 XEN 将其称为 MemoryCoW。不管采用哪种名称和实现,这个特性都提供了更好的内存利用率,从而允许操作系统(KVM 的系统管理程序)过量使用内存,支持更多的应用程序或 VM。

假如操作系统和应用程序代码以及常量数据在 VMs 之间相同,那么这个特点就很有用。当页面惟一时,它们可以被合并,从而释放内存,供其他应用程序使用。

一台主机(Host)同时运行好几个相同类型的实例(guests),通过这种技术共享相同代码,比如每个guest的核心代码,那么随着guest实例增加,Host内存不会急剧的下降,有效的增加Host的provisioning能力。同时释放出内存,供其他系统或程序使用。这也是我们如何能够让一台16G内存的Server跑起52台1G内存XP系统的方法。VMware 的 ESX/ESXi 将这个特性定义为 Transparent Page Sharing (TPS),而 XEN 则将其称为 MemoryCoW (Copy-on-Write )。存储技术中的这种技术我们称为去耦合(de-duplication)。去耦合这种技术通过删除冗余数据(基于数据块,或者基于更大的数据片段,比如文件)来减少已存储的数据。公共数据片段被合并(以一种 copy-on-write [CoW] 方式),释放空间供其他用途。使用这种方法,存储成本更低,最终需要的存储器也更少。鉴于当前的数据增长速度,这个功能显得非常重要。

尽管 Linux 中的内存共享在虚拟环境中有优势(KSM 最初设计用于基于内核的虚拟机),但它在非虚拟环境中仍然有用。事实上,KSM 甚至在嵌入式 Linux 系统中也有用处,表明了这种方法的灵活性。

 

       KSM 作为内核中的守护进程(称为 ksmd)存在。它定期执行页面扫描,识别副本页面并合并副本,释放这些页面以供它用。

KSM 执行上述操作的过程对用户透明。例如,副本页面被合并,然后被标记为只读,但是,如果这个页面的其中一个用户由于某种原因更改该页面,该用户将以 CoW 方式收到自己的副本。可以在内核源代码 ./mm/ksm.c 中找到 KSM 内核模块的完整实现。

       KSM 应用程序编程接口(API)通过 madvise 系统调用和一个新的推荐参数MADV_MERGEABLE(表明已定义的区域可以合并)来实现。可以通过 MADV_UNMERGEABLE 参数(立即从一个区域取消合并任何已合并页面)从可合并状态删除一个区域。注意,通过 madvise 来删除一个页面区域可能会导致一个 EAGAIN 错误,因为该操作可能会在取消合并过程中耗尽内存,从而可能会导致更大的麻烦(内存不足情况)。     

一旦某个区域被定义为 “可合并”,KSM 将把该区域添加到它的工作内存列表。启用 KSM 时,它将搜索相同的页面,以写保护的 CoW 方式保留一个页面,释放另一个页面以供它用。

     KSM 使用的方法与内存去耦合中使用的方法不同。在传统的去耦合中,对象被散列化,然后使用散列值进行初始相似性检查。当散列值一致时,下一步是进行一个实际对象比较(本例中是一个内存比较),以便正式确定这些对象是否一致。KSM 在它的第一个实现中采用这种方法,但后来开发了一种更直观的方法来简化它。

     在当前的 KSM 中,页面通过两个 “红-黑” 树管理,其中一个 “红-黑” 树是临时的。第一个树称为不稳定树,用于存储还不能理解为稳定的新页面。换句话说,作为合并候选对象的页面(在一段时间内没有变化)存储在这个不稳定树中。不稳定树中的页面不是写保护的。第二个树称为稳定树,存储那些已经发现是稳定的且通过 KSM 合并的页面。为确定一个页面是否是稳定页面,KSM 使用了一个简单的 32 位校验和(checksum)。当一个页面被扫描时,它的校验和被计算且与该页面存储在一起。在一次后续扫描中,如果新计算的校验和不等于此前计算的校验和,则该页面正在更改,因此不是一个合格的合并候选对象。

     使用 KSM 进程处理一个单一的页面时,第一步是检查是否能够在稳定树中发现该页面。搜索稳定树的过程很有趣,因为每个页面都被视为一个非常大的数字(页面的内容)。一个 memcmp(内存比较)操作将在该页面和相关节点的页面上执行。如果 memcmp 返回 0,则页面相同,发现一个匹配值。反之,如果 memcmp 返回 -1,则表示候选页面小于当前节点的页面;如果返回 1,则表示候选页面大于当前节点的页面。尽管比较 4KB 的页面似乎是相当重量级的比较,但是在多数情况下,一旦发现一个差异,memcmp 将提前结束。

     如果候选页面位于稳定树中,则该页面被合并,候选页面被释放。反之,如果没有发现候选页面,则应转到不稳定树。

     在不稳定树中搜索时,第一步是重新计算页面上的校验和。如果该值与原始校验和不同,则本次扫描的后续搜索将抛弃这个页面(因为它更改了,不值得跟踪)。如果校验和没有更改,则会搜索不稳定树以寻找候选页面。不稳定树的处理与稳定树的处理有一些不同。第一,如果搜索代码没有在不稳定树中发现页面,则在不稳定树中为该页面添加一个新节点。但是如果在不稳定树中发现了页面,则合并该页面,然后将该节点迁移到稳定树中。

     当扫描完成时,稳定树被保存下来,但不稳定树则被删除并在下一次扫描时重新构建。这个过程大大简化了工作,因为不稳定树的组织方式可以根据页面的变化而变化。由于稳定树中的所有页面都是写保护的,因此当一个页面试图被写入时将生成一个页面故障,从而允许 CoW 进程为写入程序取消页面合并(break_cow())。稳定树中的孤立页面将在稍后被删除。

 

Ksm实现

static int __init ksm_init(void) 用于ksm初始化:

1、创建ksmd线程,该线程用于物理页面扫描合并:

ksm_thread= kthread_run(ksm_scan_thread, NULL, "ksmd");

 

2、创建sysfs的配置和监控,下章节介绍:

         err = sysfs_create_group(mm_kobj,&ksm_attr_group);

         if (err) {

                   printk(KERN_ERR "ksm:register sysfs failed\n");

                   kthread_stop(ksm_thread);

                   goto out_free2;

         }

 

主函数ksm_scan_thread为一个非实时任务,循环进行页合并操作:

staticint ksm_scan_thread(void *nothing)

{

         set_user_nice(current,5); 设置成nice值为5非实时任务

 

         while (!kthread_should_stop()) {

                   mutex_lock(&ksm_thread_mutex);

                   if (ksmd_should_run())

                            ksm_do_scan(ksm_thread_pages_to_scan);一次扫描成功ksm_thread_pages_to_scan页返回

                   mutex_unlock(&ksm_thread_mutex);

 

                   if (ksmd_should_run()) {

                            schedule_timeout_interruptible(

                                     msecs_to_jiffies(ksm_thread_sleep_millisecs));休息ksm_thread_sleep_millisecs时间后进行下一次扫描

                   } else {

                            wait_event_interruptible(ksm_thread_wait,

                                     ksmd_should_run()|| kthread_should_stop());

                   }

         }

         return 0;

}

 

首先介绍一个数据结构rmap_item反向映射条目,该条目保存有物理地址到虚拟地址的反向映射,同时用来组织稳定树的红黑树结构,对于非稳定树保存有校验和。

structrmap_item {

         struct list_head link;

         struct mm_struct *mm;

         unsigned long address;             /* + low bits used for flags below*/

         union {

                   unsigned int oldchecksum;                /* when unstable */

                   struct rmap_item *next;                            /* when stable */

         };

         union {

                   struct rb_node node;                          /* when tree node */

                   struct rmap_item *prev;                            /* in stable list */

         };

};

页合并主函数ksm_do_scan首先根据扫描区间获得一个page,并为其建立反向映射条目。接着对该页在稳定树和非稳定树中寻找相同的页进行比较与合并。

staticvoid ksm_do_scan(unsigned int scan_npages)

{

         struct rmap_item *rmap_item;

         struct page *page;

 

         while (scan_npages--) {

                   cond_resched();

                   rmap_item =scan_get_next_rmap_item(&page);//对扫描区间的每一个页进行扫描

                   if (!rmap_item)

                            return;

                   if (!PageKsm(page) ||!in_stable_tree(rmap_item))//已经在稳定树中不必考虑,PageKsmksm机制引入的,replace_page中对添加的对稳定树页的特殊标志

                            cmp_and_merge_page(page,rmap_item); //对该页在稳定树和非稳定树中寻找相同的页进行比较与合并

                   else if (page_mapcount(page)== 1) {//当此时页的引用计数为1时,将页从稳定树上摘除,成为普通页

                            /*

                             * Replace now-unshared ksm page by ordinarypage.

                             */

                            break_cow(rmap_item->mm,rmap_item->address);

                            remove_rmap_item_from_tree(rmap_item);

                            rmap_item->oldchecksum =calc_checksum(page);//计算校验和

                   }

                   put_page(page);

         }

}

 

了解scan_get_next_rmap_item前,首先介绍一个数据结构:

structksm_scan {

         struct mm_slot *mm_slot; //当前正扫描mm空间

         unsigned long address;//当前正扫描地址

         struct rmap_item *rmap_item;//为当前页面建立的反向映射条目(用于操作树)

};

scan_get_next_rmap_item根据扫描区间获得一个page,并为其建立反向映射条目。

staticstruct rmap_item *scan_get_next_rmap_item(struct page **page)

{

         struct mm_struct *mm;

         struct mm_slot *slot;

         struct vm_area_struct *vma;

         struct rmap_item *rmap_item;

 

         if(list_empty(&ksm_mm_head.mm_list))//扫描区间不为0

                   return NULL;

 

         slot =ksm_scan.mm_slot;获取当前扫描空间

         if (slot ==&ksm_mm_head) {//每轮询一个循环建立一个root_unstable_tree表,重新初始化

                   root_unstable_tree= RB_ROOT;

 

         … …

next_mm:

                   ksm_scan.address = 0;

                   ksm_scan.rmap_item =list_entry(&slot->rmap_list,

                                                        structrmap_item, link);

         }

 

         mm = slot->mm;

         down_read(&mm->mmap_sem);

         if (ksm_test_exit(mm))

                   vma = NULL;

         else

                   vma = find_vma(mm,ksm_scan.address);

 

         for (; vma; vma = vma->vm_next) {

                   if(!(vma->vm_flags & VM_MERGEABLE))//没有VM_MERGEABLE标志,不能合并

                            continue;

                  if (ksm_scan.address < vma->vm_start)//vm_start开始扫描

                            ksm_scan.address =vma->vm_start;

                  if (!vma->anon_vma)//只扫描匿名页

                            ksm_scan.address =vma->vm_end;

 

                  while (ksm_scan.address vm_end) {vma区全部扫描

                            if(ksm_test_exit(mm))

                                     break;

                            *page = follow_page(vma, ksm_scan.address,FOLL_GET);//一次快速的get_page

                            if (*page &&PageAnon(*page)) {

                                     flush_anon_page(vma,*page, ksm_scan.address);

                                     flush_dcache_page(*page);

                                     rmap_item= get_next_rmap_item(slot,// get_next_rmap_item调用alloc_rmap_item建立一个rmap_item条目并插入到链表末尾

                                               ksm_scan.rmap_item->link.next,

                                               ksm_scan.address);

                                     if(rmap_item) {

                                               ksm_scan.rmap_item= rmap_item;

                                               ksm_scan.address+= PAGE_SIZE;

                                     } else

                                               put_page(*page);

                                     up_read(&mm->mmap_sem);

                                     returnrmap_item;

                            }

                            if (*page)

                                     put_page(*page);

                            ksm_scan.address +=PAGE_SIZE;

                            cond_resched();

                   }

         }

 

         if (ksm_test_exit(mm)) {

                   ksm_scan.address = 0;

                   ksm_scan.rmap_item =list_entry(&slot->rmap_list,

                                                        structrmap_item, link);

         }

         /*

          * Nuke all the rmap_items that are above thiscurrent rmap:

          * because there were no VM_MERGEABLE vmas withsuch addresses.

          */

         remove_trailing_rmap_items(slot,ksm_scan.rmap_item->link.next);

 

         spin_lock(&ksm_mmlist_lock);

         ksm_scan.mm_slot= list_entry(slot->mm_list.next,//根据链表得到下一个扫描空间

                                                        structmm_slot, mm_list);

… …

 

         /* Repeat until we've completedscanning the whole list */

         slot =ksm_scan.mm_slot;

         if (slot !=&ksm_mm_head)//如果扫描没有到头,继续扫描

                   gotonext_mm;

 

         ksm_scan.seqnr++;

         return NULL;

}

 

cmp_and_merge_page经过scan_get_next_rmap_item扫描到的item寻找相同的页进行比较与合并。

首先尝试是否与稳定树的页一致,如果不行检查校验和看页是否有修改,如果没有修改合入不稳定树,如果与不稳定树的页一致,则合入稳定树。

staticvoid cmp_and_merge_page(struct page *page, struct rmap_item *rmap_item)

{

         struct page *page2[1];

         struct rmap_item *tree_rmap_item;

         unsigned int checksum;

         int err;

… …s

         tree_rmap_item= stable_tree_search(page, page2, rmap_item);//首先检查page内容是否与一个稳定树中的页相同。

         if (tree_rmap_item) {

                  if (page == page2[0])                 //寻找到的是同一个物理页不必处理

                            err = 0;

                   else

                            err = try_to_merge_with_ksm_page(rmap_item->mm,

// try_to_merge_with_ksm_page用来将oldpage合并至newpage的物理页

                                                                  rmap_item->address,

                                                                  page, page2[0]);

                   put_page(page2[0]);

 

                   if (!err) {

                            /*

                             * The page was successfully merged:

                             * add its rmap_item to the stable tree.

                             */

                            stable_tree_append(rmap_item,tree_rmap_item);//物理页合并成功后将rmap_item插入稳定树条目

                   }

                   return;

         }

 

         /*

          * A ksm page might have got here by fork, butits other

          * references have already been removed fromthe stable tree.

          * Or it might be left over from a break_ksmwhich failed

          * when the mem_cgroup had reached its limit:try again now.

          */

         if (PageKsm(page))

                   break_cow(rmap_item->mm,rmap_item->address);

 

         /*

          * In case the hash value of the page waschanged from the last time we

          * have calculated it, this page to be changedfrequely, therefore we

          * don't want to insert it to the unstabletree, and we don't want to

          * waste our time to search if there is somethingidentical to it there.

          */

         checksum =calc_checksum(page);//计算hash

         if(rmap_item->oldchecksum != checksum) {//校验和失败,页被修改,属于经常被修改的页

                   rmap_item->oldchecksum =checksum;

                   return;

         }

 

         tree_rmap_item =unstable_tree_search_insert(page, page2, rmap_item);//尝试比较并插入不稳定树

         if (tree_rmap_item) {

                   err =try_to_merge_two_pages(rmap_item->mm,

// try_to_merge_two_pages调用try_to_merge_with_ksm_page(同上)将两个不稳定物理页合二为一

                                                    rmap_item->address, page,

                                                    tree_rmap_item->mm,

                                                    tree_rmap_item->address, page2[0]);

                   /*

                    * As soon as we merge this page, we want toremove the

                    * rmap_item of the page we have merged withfrom the unstable

                    * tree, and insert it instead as new node inthe stable tree.

                    */

                   if (!err) {

                            rb_erase(&tree_rmap_item->node,&root_unstable_tree);//成功合并之后从不稳定树删除该节点

                            tree_rmap_item->address&= ~NODE_FLAG;

                            ksm_pages_unshared--;

 

                            /*

                             * If we fail to insert the page into thestable tree,

                             * we will have 2 virtual addresses that arepointing

                             * to a ksm page left outside the stable tree,

                             * in which case we need to break_cow on both.

                             */

                            if(stable_tree_insert(page2[0], tree_rmap_item))//条目加入到稳定数

                                     stable_tree_append(rmap_item,tree_rmap_item);

                            else {

                                      break_cow(tree_rmap_item->mm,//合并至稳定树失败则分别写时复制一分为二

                                                        tree_rmap_item->address);

                                      break_cow(rmap_item->mm,rmap_item->address);

                            }

                   }

 

                   put_page(page2[0]);

         }

}

calc_checksum计算一个页面的hash值,最终将这个hash值存入rmap_item结构的oldchechsum中,如果再次计算的时候这个值变了,那么就说明页面被写了:

staticinline u32 calc_checksum(struct page *page)

 

{

u32checksum;

void*addr = kmap_atomic(page, KM_USER0); //临时映射到KM_USER0

checksum = jhash(addr, PAGE_SIZE, 17); //计算这个页面的内容的hash

kunmap_atomic(addr,KM_USER0);//释放

returnchecksum; //返回这个hash值

}

 

stable_tree_search用来检查page内容是否与一个稳定树中的页相同:

staticstruct rmap_item *stable_tree_search(struct page *page,

                                                   struct page **page2,

                                                   struct rmap_item *rmap_item)

{

         structrb_node *node = root_stable_tree.rb_node;//root_stable_tree是稳定树表

 

         while (node) {

                   struct rmap_item*tree_rmap_item, *next_rmap_item;

                   int ret;

 

                  tree_rmap_item = rb_entry(node, structrmap_item, node);//轮询稳定树条目

                   while (tree_rmap_item) {

                            BUG_ON(!in_stable_tree(tree_rmap_item));

                            cond_resched();

                            page2[0] =get_ksm_page(tree_rmap_item);//根据条目取出一个物理页面

                            if (page2[0])

                                     break;

                            next_rmap_item =tree_rmap_item->next;

                            remove_rmap_item_from_tree(tree_rmap_item);//取不出说明物理页已经不存在,ok从稳定树中删除反向映射条目

                            tree_rmap_item =next_rmap_item;

                   }

                   if (!tree_rmap_item)

                            return NULL;

 

                  ret = memcmp_pages(page, page2[0]);//bit比较是否同样的物理页

 

                   if (ret < 0) {

                            put_page(page2[0]);//不一样的话则从红黑树继续寻找

                            node =node->rb_left;

                   } else if (ret > 0) {

                            put_page(page2[0]); //不一样的话则从红黑树继续寻找

                            node =node->rb_right;

                   } else {

                            return tree_rmap_item;//同样,则返回随后合并之

                   }

         }

 

         return NULL;

}

 

try_to_merge_with_ksm_page用来将oldpage合并至newpage的物理页:

try_to_merge_with_ksm_page ->try_to_merge_one_page->replace_page

staticint replace_page(struct vm_area_struct *vma, struct page *oldpage,

                            struct page*newpage, pte_t orig_pte)

{

         struct mm_struct *mm = vma->vm_mm;

         pgd_t *pgd;

         pud_t *pud;

         pmd_t *pmd;

         pte_t *ptep;

         spinlock_t *ptl;

         unsigned long addr;

         pgprot_t prot;

         int err = -EFAULT;

 

         prot =vm_get_page_prot(vma->vm_flags & ~VM_WRITE);

 

         addr = page_address_in_vma(oldpage,vma);

         if (addr == -EFAULT)

                   goto out;

 

         pgd = pgd_offset(mm, addr);

         if (!pgd_present(*pgd))

                   goto out;

 

         pud = pud_offset(pgd, addr);

         if (!pud_present(*pud))

                   goto out;

 

         pmd = pmd_offset(pud, addr);

         if (!pmd_present(*pmd))

                   goto out;

 

         ptep = pte_offset_map_lock(mm, pmd,addr, &ptl);

         if (!pte_same(*ptep, orig_pte)) {//以上验证oldpage页表存在,并上锁

                   pte_unmap_unlock(ptep, ptl);

                   goto out;

         }

 

         get_page(newpage);

         page_add_ksm_rmap(newpage);//增加newpage匿名页计数

 

         flush_cache_page(vma, addr,pte_pfn(*ptep));

         ptep_clear_flush(vma, addr, ptep);

         set_pte_at_notify(mm,addr, ptep, mk_pte(newpage, prot));//修改oldpage的页表替换成newpage的页表。

 

         page_remove_rmap(oldpage);//释放oldpage相关信息

         put_page(oldpage);//释放页

 

         pte_unmap_unlock(ptep, ptl);

         err = 0;

out:

         return err;

}

 

unstable_tree_search_insert用来将一个条目比较并插入不稳定树:

staticstruct rmap_item *unstable_tree_search_insert(struct page *page,

                                                        structpage **page2,

                                                        structrmap_item *rmap_item)

{

         structrb_node **new = &root_unstable_tree.rb_node;//不稳定树

         struct rb_node *parent = NULL;

 

         while (*new) {

                   struct rmap_item*tree_rmap_item;

                   int ret;

 

                   cond_resched();

                   tree_rmap_item =rb_entry(*new, struct rmap_item, node);

                  page2[0] = get_mergeable_page(tree_rmap_item);//取出不稳定树的一个页

                   if (!page2[0])

                            return NULL;

 

                   /*

                    * Don't substitute an unswappable ksm page

                    * just for one good swappable forked page.

                    */

                   if (page == page2[0]) {

                            put_page(page2[0]);

                            return NULL;

                   }

 

                  ret = memcmp_pages(page, page2[0]);//比较页面内容是否相同

 

                   parent = *new;

                   if (ret < 0) {

                            put_page(page2[0]);

                            new =&parent->rb_left;

                   } else if (ret > 0) {

                            put_page(page2[0]);

                            new =&parent->rb_right;

                   } else {

                            return tree_rmap_item;//找到同样的页面返回,后续合并入稳定树

                   }

         }

 

         rmap_item->address |= NODE_FLAG;

         rmap_item->address |=(ksm_scan.seqnr & SEQNR_MASK);

         rb_link_node(&rmap_item->node,parent, new);//没有找到相同的页面,则插入到root_unstable_tree

         rb_insert_color(&rmap_item->node,&root_unstable_tree);

 

         ksm_pages_unshared++;

         return NULL;

}

 

KSM使用

     KSM 的管理和监控通过sysfs(位于根 /sys/kernel/mm/ksm)执行。在这个 sysfs 子目录中,有些用于开关、控制和监控。

# cat/sys/kernel/mm/ksm/

/sys/kernel/mm/ksm/full_scans        /sys/kernel/mm/ksm/pages_unshared

/sys/kernel/mm/ksm/max_kernel_pages  /sys/kernel/mm/ksm/pages_volatile

/sys/kernel/mm/ksm/pages_shared      /sys/kernel/mm/ksm/run

/sys/kernel/mm/ksm/pages_sharing     /sys/kernel/mm/ksm/sleep_millisecs

/sys/kernel/mm/ksm/pages_to_scan

 

      开关:

Run文件用于启用和禁用 KSM 的页面合并。默认情况下,KSM 被禁用(0),但可以通过将一个 1 写入这个文件来启用 KSM 守护进程(例如,echo 1 > sys/kernel/mm/ksm/run)。通过写入一个 0,可以从运行状态禁用这个守护进程(但是保留合并页面的当前集合)。另外,通过写入一个 2,可以从运行状态(1)停止 KSM 并请求取消合并所有合并页面。

    控制:

KSM 运行时,可以通过 其中3 个参数(sysfs 中的文件)来控制它。sleep_millisecs文件定义执行另一次页面扫描前 ksmd 休眠的毫秒数默认20ms。最后,pages_to_scan 文件定义一次给定扫描中可以扫描的页面数,默认100。max_kernel_pages为最多合并页面数

      监控:

还有 5 个通过 sysfs 导出的可监控文件(均为只读),它们表明 ksmd 的运行情况和效果。

full_scans :表明已经执行的全区域扫描的次数

pages_shared: stable稳定树的节点数(共享后的物理页面数)。
pages_sharing:表示被共享的物理页面数。(例如将3个相同的页面合并成1个页面,则pages_shared=1,pages_sharing=2,两者比例体现了页面共享效率)
pages_unshared:ksm的暂未共享页面数,即unstable不稳定树的节点数。
ksm_rmap_items:反向映射条目数,可用来计算频繁改变的页面的数量。计算方式:  ksm_pages_volatile =ksm_rmap_items - ksm_pages_shared- ksm_pages_sharing - ksm_pages_unshared

运行前检查配置项CONFIG_KSM是否配置

配置项:CONFIG_KSM

    Symbol: KSM [=y]                                                                                                                                                                                 

    Prompt: Enable KSM for page merging                                                                                                                                                               

      Defined at mm/Kconfig:214                                                                                                                                                                      

      Depends on: MMU [=y]                                                                                                                                                                           

      Location:                                                                                                                                                                                      

        -> Kernel options   

 

测试用例:

#include

#include

#include

#include

 

#definePAGE_SIZE 4096

#definePAGE_MASK (~(PAGE_SIZE - 1))

 

intmain(int argc, char **argv)

{

        char*p;

        intsize;

 

        if(argc < 3)

                gotofailed;

 

        size= atoi(argv[1]) * 1024 * 1024;

        if(!size)

                gotofailed;

 

        p= malloc(size);

        if(!p)

                gotofailed;

 

        memset(p,atoi(argv[2]), size);

 

        p= (char *)((unsigned long)p & PAGE_MASK);

        size= madvise(p, size, 12);

        if(size == -1)

                perror("failed\n");

        

        //scanf("%d",&size);

        return0;

 

failed:

        printf("failed\n");

        return1;

}

 

测试过程结果:

# mount-n -t sysfs none /sys

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

# cat/sys/kernel/mm/ksm/run     

1

#free 

              total         used         free       shared      buffers

  Mem:     2076068        11228      2064840            0            0

 Swap:            0            0            0

Total:      2076068        11228      2064840

# ./ksm200 3 &   200表示申请内存大小200M,3表示内存填充的值

# cat/sys/kernel/mm/ksm/pages_shared

1

# cat/sys/kernel/mm/ksm/pages_volatile

0

# cat/sys/kernel/mm/ksm/pages_sharing

51199

# cat/sys/kernel/mm/ksm/full_scans

3

# cat/sys/kernel/mm/ksm/full_scans

4

# cat/sys/kernel/mm/ksm/pages_volatile

0

# cat/sys/kernel/mm/ksm/pages_unshared

0

可见这场景下有效的节省了内存,申请200M物理页面,实际只占用了1个页。

 

参考相关资料:

http://www.linux-kvm.com/content/using-ksm-kernel-samepage-merging-kvm

http://www.linux-kvm.org/page/KSM

你可能感兴趣的:(KSM (内存管理合并相同页))