内存管理 · 2017-05-13 0

【Linux内存管理】vmalloc不连续内存管理(1)

伙伴管理算法初衷是解决外部碎片问题,而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链表中(该链表同样是根据地址排序的)。