今天讀了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的內存管理方案再摸清楚一點,寫清楚點。