HAproxy 和Agent的内存管理

来源:互联网 发布:鹏鹏扣字软件谁? 编辑:程序博客网 时间:2024/06/06 20:31

今天读了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的内存管理方案再摸清楚一点,写清楚点。

0 0
原创粉丝点击