Redis源碼分析(1)內存管理

Redis在zmalloc.h和zmalloc.c實現了底層的內存管理,代碼很簡潔。
Redis的內存管理提供了以下幾個函數:


// 申請內存,封裝malloc
void *zmalloc(size_t size);
// 申請內存並初始化爲0,封裝calloc
void *zcalloc(size_t size);
// 調整內存大小,封裝realloc
void *zrealloc(void *ptr, size_t size);
// 釋放內存,封裝free
void zfree(void *ptr);
// 拷貝字符串
char *zstrdup(const char *s);
/***********************以下都是用於控制程序內存*******************************/
// 獲取已使用的內存大小(猜測用於內存管理LRU等)
size_t zmalloc_used_memory(void);
// 啓用線程安全
void zmalloc_enable_thread_safeness(void);
// 設置處理out-of-memory的函數
void zmalloc_set_oom_handler(void (*oom_handler)(size_t));
// 獲取碎片化比例,rss:是常駐內存集(Resident Set Size),表示該進程分配的內存大小
float zmalloc_get_fragmentation_ratio(size_t rss);
// 獲取程序佔用的內存(常駐內存集Resident Set Size),除了程序分配的內存,還包括進程運行本身需要的內存、內存碎片等,但是不包括虛擬內存
size_t zmalloc_get_rss(void);

size_t zmalloc_get_private_dirty(void);
size_t zmalloc_get_smap_bytes_by_field(char *field);
void zlibc_free(void *ptr);
WIN32_ONLY(void zmalloc_free_used_memory_mutex(void);)

// 獲取指定指針申請的內存空間大小
size_t zmalloc_size(void *ptr);

要分析redis的內存管理,先看內存是如何申請的:

// 申請一段內存空間
void *zmalloc(size_t size) {
    // 申請內存,多申請了PREFIX_SIZE,PREFIX_SIZE大小是sizeof(size_t)
    void *ptr = malloc(size+PREFIX_SIZE);

	// 判斷申請是否成功,當內存不足時將申請失敗
    if (!ptr) zmalloc_oom_handler(size);
#ifdef HAVE_MALLOC_SIZE
	// 更新當前內存統計數
    update_zmalloc_stat_alloc(zmalloc_size(ptr));
    return ptr;
#else
    // 將長度寫入頭部,前PREFIX_SIZE個byte用於存儲申請的內存大小
    *((size_t*)ptr) = size;
    // 更新內存使用量統計,添加使用內存量計數
    update_zmalloc_stat_alloc(size+PREFIX_SIZE);
    // 返回內存數據區起始地址
    return (char*)ptr+PREFIX_SIZE;
#endif
}

redis使用zmalloc申請的內存結構如下:

----------------------------------------------------------------
|  數據區長度 |       數據區     |
----------------------------------------------------------------
↑         ↑
實際頭地址    返回的指針地址
在這裏插入圖片描述
redis通過**update_zmalloc_stat_alloc、update_zmalloc_stat_free、**這個函數調整當前內存使用量的統計:

#define update_zmalloc_stat_add(__n) do { \
    pthread_mutex_lock(&used_memory_mutex); \
    used_memory += (__n); \
    pthread_mutex_unlock(&used_memory_mutex); \
} while(0)

#define update_zmalloc_stat_sub(__n) do { \
    pthread_mutex_lock(&used_memory_mutex); \
    used_memory -= (__n); \
    pthread_mutex_unlock(&used_memory_mutex); \
} while(0)

#define update_zmalloc_stat_alloc(__n) do { \
    size_t _n = (__n); \
    if (_n&(sizeof(PORT_LONG)-1)) _n += sizeof(PORT_LONG)-(_n&(sizeof(PORT_LONG)-1)); \
    if (zmalloc_thread_safe) { \
        update_zmalloc_stat_add(_n); \
    } else { \
        used_memory += _n; \
    } \
} while(0)

#define update_zmalloc_stat_free(__n) do { \
    size_t _n = (__n); \
    if (_n&(sizeof(PORT_LONG)-1)) _n += sizeof(PORT_LONG)-(_n&(sizeof(PORT_LONG)-1)); \
    if (zmalloc_thread_safe) { \
        update_zmalloc_stat_sub(_n); \
    } else { \
        used_memory -= _n; \
    } \
} while(0)

static size_t used_memory = 0;

這個有個要點,計算機是如何分配內存的?例如我們用malloc(1)申請一個byte的內存,會佔用多大的空間?

在64位系統,如果申請內存爲1~24字節,系統內存消耗32字節,當申請25字節的內存時,系統內存消耗48字節。而對於32位系統,申請內存爲1~12字節時,系統內存消耗爲16字節,當申請內存爲13字節時,系統內存消耗爲24字節。(這段在linux下適用,是實際佔用的內存,可用malloc_usable_size測試,但與下文的無關)

redis給了一個計算申請內存大小的公式:

#define PORT_LONG size_t
// 獲取使用的內存大小,將n調整爲sizeof(PORT_LONG)的整數倍
int get_memory_size(size_t _n)
{
    if (_n & (sizeof(PORT_LONG) - 1))
        _n += sizeof(PORT_LONG) - (_n & (sizeof(PORT_LONG) - 1));
    return _n;
}

redis是如何獲取申請的內存大小的:

size_t zmalloc_used_memory(void) {
    size_t um;

    if (zmalloc_thread_safe) {
#if defined(__ATOMIC_RELAXED) || defined(HAVE_ATOMIC)
        um = update_zmalloc_stat_add(0);
#else
        pthread_mutex_lock(&used_memory_mutex);
        um = used_memory;
        pthread_mutex_unlock(&used_memory_mutex);
#endif
    }
    else {
        um = used_memory;
    }
    
    return um;
}

used_memory是靜態變量,用於保存申請的內存大小,每次內存調整都會修改其值。

redis通過封裝malloc、zalloc、realloc、free實現了自己的內存管理,主要是定義了自己的內存結構並可以統計內存使用量。

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