伙伴管理算法初衷是解决外部碎片问题,而slab算法则是用于解决内部碎片问题,但是内存使用的得不合理终究会产生碎片。碎片问题产生后,申请大块连续内存将可能持续失败,但是实际上内存的空闲空间却是足够的。这时候就引入了不连续页面管理算法,即我们常用的vmalloc申请分配的内存空间,它主要是用于将不连续的页面,通过内存映射到连续的虚拟地址空间中,提供给申请者使用,由此实现内存的高利用。
按照分析管理,从该管理算法的初始化入手。vmalloc_init()为vmalloc不连续内存管理初始化函数。具体实现:
【file:/mm/vmalloc.c】 void __init vmalloc_init(void) { struct vmap_area *va; struct vm_struct *tmp; int i; for_each_possible_cpu(i) { struct vmap_block_queue *vbq; struct vfree_deferred *p; vbq = &per_cpu(vmap_block_queue, i); spin_lock_init(&vbq->lock); INIT_LIST_HEAD(&vbq->free); p = &per_cpu(vfree_deferred, i); init_llist_head(&p->list); INIT_WORK(&p->wq, free_work); } /* Import existing vmlist entries. */ for (tmp = vmlist; tmp; tmp = tmp->next) { va = kzalloc(sizeof(struct vmap_area), GFP_NOWAIT); va->flags = VM_VM_AREA; va->va_start = (unsigned long)tmp->addr; va->va_end = va->va_start + tmp->size; va->vm = tmp; __insert_vmap_area(va); } vmap_area_pcpu_hole = VMALLOC_END; vmap_initialized = true; }
该函数先是遍历每CPU的vmap_block_queue和vfree_deferred变量并进行初始化。其中vmap_block_queue是非连续内存块队列管理结构,主要是队列以及对应的保护锁;而vfree_deferred是vmalloc的内存延迟释放管理,除了队列初始外,还创建了一个free_work()工作队列用于异步释放内存。接着将挂接在vmlist链表的各项__insert_vmap_area()输入到非连续内存块的管理中。
其中__insert_vmap_area()的实现值得一读,这里面有几个比较关键的数据在这里。
【file:/mm/vmalloc.c】 static void __insert_vmap_area(struct vmap_area *va) { struct rb_node **p = &vmap_area_root.rb_node; struct rb_node *parent = NULL; struct rb_node *tmp; while (*p) { struct vmap_area *tmp_va; parent = *p; tmp_va = rb_entry(parent, struct vmap_area, rb_node); if (va->va_start < tmp_va->va_end) p = &(*p)->rb_left; else if (va->va_end > tmp_va->va_start) p = &(*p)->rb_right; else BUG(); } rb_link_node(&va->rb_node, parent, p); rb_insert_color(&va->rb_node, &vmap_area_root); /* address-sort this list */ tmp = rb_prev(&va->rb_node); if (tmp) { struct vmap_area *prev; prev = rb_entry(tmp, struct vmap_area, rb_node); list_add_rcu(&va->list, &prev->list); } else list_add_rcu(&va->list, &vmap_area_list); }
该函数主要动作先是遍历vmap_area_root红黑树(这是一棵根据非连续内存地址排序的红黑树),查找合适的节点位置,然后rb_insert_color()插入到红黑树中,最后则是查找插入的内存块管理树的父节点,有则插入到该节点链表的后面位置,否则作为链表头插入到vmap_area_list链表中(该链表同样是根据地址排序的)。