2.6.29的一个节省内存的补丁-阿里云开发者社区

开发者社区> 科技小能手> 正文

2.6.29的一个节省内存的补丁

简介:
+关注继续查看

今天看linux内核的maillist,发现了一个很有创意的补丁,叫做ksm,也就是kernel shared memory driver,读了之后感觉太有创意了,可是不知道到底有没有实际用处。这个补丁的大致思想就是,扫描系统中所有的页面,把内容一样的页面合并为一个,并且设置为只读,然后写时复制,如果系统中存在很多潜在的内容一模一样的页面,那么这个补丁显然可以节省大量的内存,但是问题是,第一,存在这种内容一样的页面的几率大吗?第二就是现在的内存都很不值钱,有必要这种时间换空间的行为吗?可是不管怎样,这个创意很值得欣赏,特别是它的一些数据结构个算法。首先先看一下它的主要数据结构。

struct ksm_memory_region { //每个可以被这个补丁管理的可以扫描的区域用这个结构体表示

__u32 npages; //该区域内的页面数量

__u32 pad;

__u64 addr; //起始的虚拟地址

__u64 reserved_bits;

};

以上的这个数据结构就好比一个vma,它其实是对进程有效的。

struct ksm_mem_slot {

struct list_head link; //系统中所有的这个slot连接成一个链表

struct list_head sma_link; //同时一个sma中的slot也连接成一个链表

struct mm_struct *mm; //这个slot所在的mm,也就是地址空间

unsigned long addr; //这个slot的起始地址

unsigned npages; //这个slot的页面数量

};

注意上面的两个数据结构的异同,它们其实很多地方时一样的,只不过它们有意义的层次不同,ksm_memory_region是用户进程注册用的,ksm内核接收到这个ksm_memory_region以后就会初始化一个slot,然后把这个slot链接到ksm系统,还要注意的就是,slot中的地址区间实际上就是vma中的区间,因此它也有一个mm,指明了使用的是哪一个地址空间,这个mm字段很有用,一会分析代码的时候就会分析到。

struct ksm_sma {

struct list_head sma_slots;

};

这个结构再也简单不过了,这个list_head代表的就是一串slot,哪一串呢?实际上这个结构体是每进程一个的,那么里面的这个list就是该进程的的slot的链表,每个进程都有一个ksm_sma,然后这个进程的所有的slot链接到这个ksm_sma的sma_slots链表中,链表的节点其实就是上面的ksm_mem_slot。

struct ksm_scan {

struct ksm_mem_slot *slot_index; //当前正在扫描的slot,注意肯定有一个mm上下文

unsigned long page_index; //当前的slot的当前页面,因为每个slot中可能有npages个页面,而这个数不一定为1

};

为何slot要有一个mm上下文呢?因为一会合并页面的时候需要找到要合并的页面,这是肯定的,这里给出的都是虚拟地址,而找到页面就需要物理地址,而找到物理地址就需要用MMU的方式,通过页目录->页表的方式,而mmu是基于地址空间的,mm恰恰就是一个地址空间的内核表征,其中存储有pgd。

struct tree_item {

struct rb_node node;

struct rmap_item *rmap_item;

};

这个结构是个树节点,怎么又联系到树了?其实在这个补丁中,要扫描就必须有效的找到需要扫描的页面,那么必须有一套很高效的方式存储这些需要定时扫描的页面,于是红黑树是一个不错的选择,那么这一棵红黑树的键值是什么呢?当然是页面的内容了,在没有更好的方式之前,用memcmp比较两个页面的内荣是一种方式,可是我倒是觉得可以变相用strcmp,因为memcmp必须比较所有的一个页面的内容,而strcmp只需要比较/0之前的就可以了,可是为何说变相使用呢?因为如果两个页面的前面几个都是0,后面的不同,难道strcmp会返回不同吗?很显然不能,因此就要改造这个strcmp,使得不必比较整个页面但是却不会漏掉任何字节。

struct rmap_item { //这是一个反向映射,因为我们不但需要从slot找到树节点,还要有相反的映射

struct hlist_node link;

struct mm_struct *mm;

unsigned long address;

unsigned int oldchecksum; //上一次的校验码

unsigned char stable_tree; //是否在“稳定树”中

struct tree_item *tree_item;

struct rmap_item *next;

struct rmap_item *prev;

};

这个rmap是一个很高效的数据结构,linux的虚拟内存就有一个反向映射,正向映射是从一个页表项映射到唯一一个页面,而反向映射就是从一个页面映射到可能很多的若干个页表项,也就是一对多的反向映射。这里的rmap_item也是这样,一个树节点可能有多个页面与之对应,因为这个补丁的作用就是促使很多的相同的物理页面合并为一个,也就是很多的slot对应一个树节点。下面开始动人心魄的算法。

static inline int PageKsm(struct page *page)

{

return !PageAnon(page);

}

这个函数很有意思,虽然这个补丁的设计者不能区分什么样的页面是共享页面,但是他可以断定,只要不是匿名的页面,那么一定是共享的,也就是说非匿名页是页面共享的必要条件而不是充分条件。

static inline u32 calc_checksum(struct page *page)

{

u32 checksum;

void *addr = kmap_atomic(page, KM_USER0); //临时映射到KM_USER0,注意,不要睡觉

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

kunmap_atomic(addr, KM_USER0);

return checksum; //返回这个hash值

}

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

static struct rmap_item *get_rmap_item(struct mm_struct *mm, unsigned long addr)

{

struct rmap_item *rmap_item;

struct hlist_head *bucket;

struct hlist_node *node;

bucket = &rmap_hash[addr % nrmaps_hash]; //得到这个地址的hash桶

hlist_for_each_entry(rmap_item, node, bucket, link) {

if (mm == rmap_item->mm && rmap_item->address == addr) {

return rmap_item;

}

}

return NULL;

}

以上这个函数得到一个反向映射的结构,也就是说,给一个虚拟地址和一个地址空间,然后返回一个反向映射,要知道既然是反向映射,那么就不止一个,那么这些相同的反向映射就连接进一个hash表中,也就是说,红黑树的每一个节点存有一个共享的物理页面,所有共享这个物理页面的slot链接进一个hash链表。

static struct rmap_item *create_new_rmap_item(struct mm_struct *mm, unsigned long addr, unsigned int checksum)

{

struct rmap_item *rmap_item;

struct hlist_head *bucket;

rmap_item = alloc_rmap_item();

if (!rmap_item)

return NULL;

rmap_item->mm = mm;

rmap_item->address = addr;

rmap_item->oldchecksum = checksum;

rmap_item->stable_tree = 0;

rmap_item->tree_item = NULL;

bucket = &rmap_hash[addr % nrmaps_hash]; //计算hash桶

hlist_add_head(&rmap_item->link, bucket); //加入hash链表

return rmap_item;

}

以上函数创建一个新的反向映射节点,这个反向映射节点有两个用途,一个就是计算hash值之后,然后链接进相应hash桶的链表,另外一个用途就是链接进红黑树,如果已经可以在红黑树中找到,那么就连接入该找到的节点的反向映射的链表。一共有两棵树,一棵是stable树,表述已经被共享的页面,一棵是unstable树,表述还没有被共享的,但是可能被共享的页面,凡是扫描到一个页面,就会先在stable树种寻找可能和这个页面一样的页面节点,如果找到的话,那么将这两个页面合并,在合并之前就将合并后的页面设置为一个写保护的写时复制页面,然后将这个反向映射加入树,既然找了可以合并的节点,那么只需要将这个反向映射连接到这个找到的节点的反向映射的链表就可以了;如果没有找到,那么无论如何先将这个被扫描的页面和unstable树的每个节点比较,如果找到相同的,那么尝试合并,如果合并成功则退出unstable树而加入stable树,如果不成功,那么最起码加入了unstable树,等到下次扫描的时候,如果没有变化,那么就有可能加入stable树,注意,查找hash的时候是根据mm和addr进行的,这一切都在下面的这个函数中:

static int cmp_and_merge_page(struct ksm_scan *ksm_scan, struct page *page)

{

struct page *page2[1];

struct ksm_mem_slot *slot;

struct tree_item *tree_item;

struct rmap_item *rmap_item;

struct rmap_item *tree_rmap_item;

unsigned int checksum;

unsigned long addr;

int wait = 0;

int ret;

slot = ksm_scan->slot_index;

addr = slot->addr + ksm_scan->page_index * PAGE_SIZE;

rmap_item = get_rmap_item(slot->mm, addr);

if (rmap_item) {

if (update_tree(rmap_item, &wait))

rmap_item = NULL;

} //以下先在stable树中寻找和这个page对应的反向映射,并且初始化page2,如果找到,那就说明page2就是一个已经被共享的页面,这个page就可以和page2合并,并且也不用再将这个反向映射插入到树中了,而只需要连接入树节点的吊链就可以

tree_rmap_item = stable_tree_search(page, page2, rmap_item);

if (tree_rmap_item) {

ret = try_to_merge_two_pages(slot->mm, page, tree_rmap_item->mm, page2[0], addr, tree_rmap_item->address);

put_page(page2[0]);

if (!ret) {

if (!rmap_item)

rmap_item = create_new_rmap_item(slot->mm, addr, 0); //如果还没有一个反向映射,那么构造一个并且连接到相应hash桶的hlist链表

...

rmap_item->next = tree_rmap_item->next; //既然已经在stable树中找到了这个tree_item,那么就不用插入了,而是在相应的节点的反向映射的链表中连接入就可以了,这个和文件缓存页面的优先级树的反向映射思想一样,都是树吊链结构,每个节点吊一条链子

rmap_item->prev = tree_rmap_item;

if (tree_rmap_item->next)

tree_rmap_item->next->prev = rmap_item;

tree_rmap_item->next = rmap_item;

rmap_item->stable_tree = 1;

rmap_item->tree_item = tree_rmap_item->tree_item;

}

ret = !ret;

goto out;

}

if (rmap_item) { //在已经找到反向映射的情况下,说明上一次加入stable树没有成功,那么如果这次还是不成功,则仍然放弃

checksum = calc_checksum(page); //如果校验码变化了,那么就说明这个页面是变的,就不必要继续了,它即使和别的页面合并,那么由于写时复制而被踢出的可能性也是很大。

if (rmap_item->oldchecksum != checksum) {

rmap_item->oldchecksum = checksum;

goto out;

}

}

tree_item = unstable_tree_search_insert(page, page2, rmap_item);

if (tree_item) {

rmap_item = tree_item->rmap_item;

ret = try_to_merge_two_pages(slot->mm, page, rmap_item->mm, page2[0], addr, rmap_item->address);

if (!ret) { //如果合并成功,那么就将这个反向映射插入到stable树中,其实也就是在这里有机会将反向映射插入stable树

rb_erase(&tree_item->node, &root_unstable_tree);

stable_tree_insert(page2[0], tree_item, rmap_item);

}

put_page(page2[0]);

ret = !ret;

goto out;

}

...

}

最后,我们看一下扫面的过程,其实很简单,就是将slots链表的节点挨个扫描,每一个slot的每一个页面对齐的地址都要按照get_user_pages页面都要根据slot结构中的mm字段用get_user_pages来的到页面,用这个页面按照stable树->unstable树的顺序每个节点比较,如果找到相同的,那么尝试合并,如果比较过程中,发现有些页面被换出或者由于cow改变了属性,那么就在树中删除它。系统每隔一段时间就会扫描整个系统的所有slots尝试发现可以被合并的页面。整个过程十分简单,就是基于两棵树,可是这个算法的思想非常不错,可以说极端的不错,虽然显得有些没有什么用处,但是我就是觉得好,一棵stable树维持了一个稳定的已经被合并的地址反向映射集合,而一棵unstable树提供了候选的节点,这就是一个阶梯状的体系,一个朴素的选择优先级的体现。最后的问题就是用户接口,这个补丁用的是vfs接口,ioctl控制。

这个补丁的实现依赖于一个帮助函数,就是replace_page,这个函数按照其作者的解释就是:this function is needed in cases you want to change the userspace virtual mapping into diffrent physical page,this function is working by removing the oldpage from the rmap and

calling put_page on it, and by setting the virtual address pte to point into the new page.

2.6.29的一个节省内存的补丁(续)

ksm补丁巧妙的用rmap_item这个数据结构来作为键值来索引红黑树中的元素,我们看一下红黑树中吊链的查找代码: 
while (found_rmap_item) { 


 本文转自 dog250 51CTO博客,原文链接:http://blog.51cto.com/dog250/1273501


版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
阿里云服务器怎么设置密码?怎么停机?怎么重启服务器?
如果在创建实例时没有设置密码,或者密码丢失,您可以在控制台上重新设置实例的登录密码。本文仅描述如何在 ECS 管理控制台上修改实例登录密码。
10099 0
使用NAT网关轻松为单台云服务器设置多个公网IP
在应用中,有时会遇到用户询问如何使单台云服务器具备多个公网IP的问题。 具体如何操作呢,有了NAT网关这个也不是难题。
26799 0
阿里云服务器ECS远程登录用户名密码查询方法
阿里云服务器ECS远程连接登录输入用户名和密码,阿里云没有默认密码,如果购买时没设置需要先重置实例密码,Windows用户名是administrator,Linux账号是root,阿小云来详细说下阿里云服务器远程登录连接用户名和密码查询方法
11644 0
阿里云服务器如何登录?阿里云服务器的三种登录方法
购买阿里云ECS云服务器后如何登录?场景不同,阿里云优惠总结大概有三种登录方式: 登录到ECS云服务器控制台 在ECS云服务器控制台用户可以更改密码、更换系.
13897 0
windows server 2008阿里云ECS服务器安全设置
最近我们Sinesafe安全公司在为客户使用阿里云ecs服务器做安全的过程中,发现服务器基础安全性都没有做。为了为站长们提供更加有效的安全基础解决方案,我们Sinesafe将对阿里云服务器win2008 系统进行基础安全部署实战过程! 比较重要的几部分 1.
9161 0
阿里云ECS云服务器初始化设置教程方法
阿里云ECS云服务器初始化是指将云服务器系统恢复到最初状态的过程,阿里云的服务器初始化是通过更换系统盘来实现的,是免费的,阿里云百科网分享服务器初始化教程: 服务器初始化教程方法 本文的服务器初始化是指将ECS云服务器系统恢复到最初状态,服务器中的数据也会被清空,所以初始化之前一定要先备份好。
7366 0
阿里云服务器ECS登录用户名是什么?系统不同默认账号也不同
阿里云服务器Windows系统默认用户名administrator,Linux镜像服务器用户名root
4511 0
23706
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
《2021云上架构与运维峰会演讲合集》
立即下载
《零基础CSS入门教程》
立即下载
《零基础HTML入门教程》
立即下载