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