HAproxy 和Agent的内存管理

今天读了agent的内存管理,其优势在于不用频繁地申请和释放内存,从而消耗时间,但是也有劣势在于内存只会增加,不会下降。那么下面来解读一下它的实现。

一:
首先从基本架构来说,可以看到,在内存里面其实是由链表连接的pool。
每个pool包含了一个链表和size,链表内的每个项表示每个内存块,每个内存块的大小由size决定,。可以看下面的数据结构。一些重要的数据项已经用注释说明。

这里写图片描述

struct pool_head {
    void **free_list;//可以理解为链表,free_list的值表示当前可以分配的地址,*free_list的值表示下一块可以分配的地址
    CIRCLEQ_ENTRY(pool_head) pool_circqe;// 用来连接pool的数据项
    unsigned int used;//表示当前pool内已经使用的内存块
    unsigned int allocated;//表示当前pool已经分配的内存块
    unsigned int limit;
    unsigned int minavail;//
    unsigned int size;//表示每个内存块的大小
    unsigned int flags;
    unsigned int users;
    char name[12];
};

二:
看完了数据结构,那么pool创建的时候是怎么样的呢?
看下述函数,有3个参数,第一个参数表示当前pool的名字,第二个表示pool内存块的大小。
其实思想比较简单,就是遍历当前所有pool,然后找到跟想要分配size相同的内存,如果找到了, 就公用。不然新创建一个,插入队列。
具体细节可以看下面的注释。

struct pool_head *create_pool(char *name, unsigned int size, unsigned int flags) {


    struct pool_head *pool;
    struct pool_head *entry;
    struct pool_head *start;
    unsigned int align;

    align = 16;
    size = (size + align - 1) & -align;// 让size为16的倍数

    start = NULL;
    pool = NULL;

    CIRCLEQ_FOREACH(entry,&pools,pool_circqe)//变量当前分配的pool
    {
        if (entry->size == size) {
            if (flags & entry->flags & MEM_F_SHARED) {
            //如果size大小相等且都为shared,则直接用这个pool就可以了
                pool = entry;
                log_debug("Sharing %s with %s\n", name, pool->name);
                break;
            }
        } else if (entry->size > size) {
            log_debug("no suitable pool for %s", name);
            start = entry;
            break;
        }
    }

    if (!pool) {//如果没找到
        pool = calloc(1, sizeof(*pool));//分配pool的内存
        if (!pool) {
            log_error("allocate %s error,no more memory!", name);
            return NULL;
        }
        if (name)
            da_strlcpy(pool->name, name, sizeof(pool->name));
        pool->size = size;//让pool的size等于入参size
        pool->flags = flags;
        if(start==NULL)//判断是不是空队列,如果空队列,插头,否则插start后面
        {
            CIRCLEQ_INSERT_HEAD(&pools, pool, pool_circqe);
        }
        else
        {
            CIRCLEQ_INSERT_AFTER(&pools, start, pool, pool_circqe);
        }
    }
    pool->users++;
    return pool;
}

三:
那下一步是怎么分配内存,分配内存的代码很简单,如下示例

struct msg *m = pool_alloc(pool2_msg);//pool2_msg 是pool_head类型的

那么这个pool_alloc函数(其实是个宏定义)是怎么实现的呢?
它有两种可能,
1. 当前free_list没有可用的内存了(即(void *)free_list == NULL),那么它就调用pool_refill_alloc这个函数来分配内存,直接返回给上述struct msg类型的m。pool_refill_alloc就不再赘述了,就是calloc一下内存,让pool->used++, pool->allocated++;
2. 当前free_list还有可用内存,那么就将free_list往后移一格,即((pool)->free_list = (void *)(pool)->free_list), 然后返回(void *)free_list;

这里面有些指针比较难,列一下,可以反复思考下,理解下:
(pool)->free_list = (void *)(pool)->free_list;

看到这,我当时蒙了,这样来说free_list岂不是一直为NULL,那么就可以看下面的释放内存了。

#define pool_alloc(pool)                                        \
({                                                              \
        void *__p;                                              \
        if ((__p = (pool)->free_list) == NULL)                  \
                __p = pool_refill_alloc(pool);                  \
        else {                                                  \
                (pool)->free_list = *(void **)(pool)->free_list;\
                (pool)->used++;                                 \
        }                                                       \
        __p;                                                    \
})


释放内存:
代码比较简单,分为了3步走
1: 让当前(pool)->free_list转为(void ), 然后ptr转为(void *)之后*ptr = (pool)->free_list。
2. (pool)->free_list = (void *)ptr
3. (pool)->used–; 减少pool内存用的数量
这样我们等于ptr的后面挂了free_list的当前可用节点,ptr是当前可用节点内存块。
这里面有些指针比较难,列一下,可以反复思考下,理解下:
(void *) ptr
(void *)(ptr)

其实到这,我们不禁思考,一个calloc必定对应free 的呢?这样岂不是内存泄漏了?
No: 后面还有一个memory gc的操作,这个操作会真正释放内存,这个操作可以作为在程序退出的时候执行,或者接收信号量异步释放

#define pool_free(pool, ptr)                                    \
({                                                              \
        if (likely((ptr) != NULL)) {                            \
                *(void **)(ptr) = (void *)(pool)->free_list;    \
                (pool)->free_list = (void *)(ptr);              \
                (pool)->used--;                                 \
        }                                                       \
})

五:
真正的内存释放:
很简单了,看注释吧

CIRCLEQ_FOREACH(entry,&pools,pool_circqe)//遍历所有pool
    {
        void *temp, *next;

        next = entry->free_list;//pool中free_list的元素
        while (next && entry->allocated > entry->minavail
                && entry->allocated > entry->used) {
            //变量freelist元素
            temp = next;
            next = *(void **) temp;// next指向free_list下一个元素,temp用来被释放
            entry->allocated--;
            free(temp);//释放真正的内存
        }
        entry->free_list = next;//注意这句话哦,要赋值哦,不然思考下后果?
    }

完:
这个内存管理还是比较有意思的, 自己在写之前也不是特别清晰,写完自己也清楚了很多,这是一个内存管理方案,下次把Nginx的内存管理方案再摸清楚一点,写清楚点。

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