Redis源碼剖析--內(nèi)存分配

Redis在內(nèi)存分配方面,僅僅是對系統(tǒng)的malloc/free做了一層簡單的封裝,然后加上了異常處理功能和內(nèi)存統(tǒng)計功能。其實現(xiàn)主要在zmalloc.c和zmalloc.h文件中。

功能函數(shù)總覽

在zmalloc.h中,定義了Redis內(nèi)存分配的主要功能函數(shù),這些函數(shù)基本上實現(xiàn)了Redis內(nèi)存申請,釋放和統(tǒng)計等功能,其函數(shù)聲明如下:

void *zmalloc(size_t size); // 調(diào)用zmalloc函數(shù),申請size大小的空間
void *zcalloc(size_t size); // 調(diào)用系統(tǒng)函數(shù)calloc申請內(nèi)存空間
void *zrealloc(void *ptr, size_t size); // 原內(nèi)存重新調(diào)整為size空間的大小
void zfree(void *ptr);  // 調(diào)用zfree釋放內(nèi)存空間
char *zstrdup(const char *s); // 字符串復(fù)制方法
size_t zmalloc_used_memory(void); // 獲取當前以及占用的內(nèi)存空間大小
void zmalloc_enable_thread_safeness(void); // 是否設(shè)置線程安全模式
void zmalloc_set_oom_handler(void (*oom_handler)(size_t)); // 可自定義設(shè)置內(nèi)存溢出的處理方法
float zmalloc_get_fragmentation_ratio(size_t rss); // 獲取所給內(nèi)存和已使用內(nèi)存的大小之比
size_t zmalloc_get_rss(void); // 獲取RSS信息(Resident Set Size)
size_t zmalloc_get_private_dirty(void); // 獲得實際內(nèi)存大小
size_t zmalloc_get_smap_bytes_by_field(char *field); // 獲取/proc/self/smaps字段的字節(jié)數(shù)
size_t zmalloc_get_memory_size(void); // 獲取物理內(nèi)存大小
void zlibc_free(void *ptr); // 原始系統(tǒng)free釋放方法

另外,我們還要注意到zmalloc.c中的幾個變量和概念,

static size_t used_memory = 0;  // 已使用內(nèi)存的大小
static int zmalloc_thread_safe = 0; // 線程安全模式狀態(tài)
pthread_mutex_t used_memory_mutex = PTHREAD_MUTEX_INITIALIZER; // 為此服務(wù)器

接下來,我分幾個章節(jié)來一一剖析zmalloc.c中的函數(shù)實現(xiàn)。
內(nèi)存申請函數(shù)zmalloc
Redis的內(nèi)存申請函數(shù)zmalloc本質(zhì)就是調(diào)用了系統(tǒng)的malloc函數(shù),然后對其進行了適當?shù)姆庋b,加上了異常處理函數(shù)和內(nèi)存統(tǒng)計。其源代碼如下:

void *zmalloc(size_t size) {
    // 調(diào)用malloc函數(shù)進行內(nèi)存申請
    // 多申請的PREFIX_SIZE大小的內(nèi)存用于記錄該段內(nèi)存的大小
    void *ptr = malloc(size+PREFIX_SIZE);
    // 如果ptr為NULL,則調(diào)用異常處理函數(shù)
    if (!ptr) zmalloc_oom_handler(size);
    // 以下是內(nèi)存統(tǒng)計
    *((size_t*)ptr) = size;
    update_zmalloc_stat_alloc(size+PREFIX_SIZE); // 更新used_memory的值
    return (char*)ptr+PREFIX_SIZE;
}

上述代碼中的PREFIX_SIZE解釋:由于malloc函數(shù)申請的內(nèi)存不會標識內(nèi)存塊的大小,而我們需要統(tǒng)計內(nèi)存大小,所以需要在多申請PREFIX_SIZE大小的內(nèi)存,用于存放該大小。
其中,異常處理函數(shù)如下:

static void zmalloc_default_oom(size_t size) {
    fprintf(stderr, "zmalloc: Out of memory trying to allocate %zu bytes\n", // 打印輸出日志
        size);
    fflush(stderr);
    abort(); // 中斷退出
}

更新used_memory值得函數(shù)以宏定義給出,其代碼和注釋如下:

#define update_zmalloc_stat_alloc(__n) do { \
    size_t _n = (__n); \
    if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1)); \   // 將_n調(diào)整為sizeof(long)的整數(shù)倍
    if (zmalloc_thread_safe) { \ // 如果啟用了線程安全模式
        update_zmalloc_stat_add(_n); \   // 調(diào)用原子操作加(+)來更新已用內(nèi)存
    } else { \
        used_memory += _n; \   // 不考慮線程安全,則直接更新已用內(nèi)存
    } \
} while(0)

在上述函數(shù)中,又用到了原子加操作,其代碼和注釋如下:

// __atomic_add_fetch是C++11特性中提供的原子加操作
#if defined(__ATOMIC_RELAXED)
#define update_zmalloc_stat_add(__n) __atomic_add_fetch(&used_memory, (__n), __ATOMIC_RELAXED)
// 如果不支持C++11,則調(diào)用GCC提供的原子加操作
#elif defined(HAVE_ATOMIC)
#define update_zmalloc_stat_add(__n) __sync_add_and_fetch(&used_memory, (__n))
// 如果上述都沒有,則只能采用加鎖操作
#else
#define update_zmalloc_stat_add(__n) do { \
    pthread_mutex_lock(&used_memory_mutex); \
    used_memory += (__n); \
    pthread_mutex_unlock(&used_memory_mutex); \
} while(0)

內(nèi)存申請函數(shù)zcalloc

與malloc一樣,zcalloc調(diào)用的是系統(tǒng)給的calloc()來申請內(nèi)存。

void *zcalloc(size_t size) {
    void *ptr = calloc(1, size+PREFIX_SIZE);
    // 異常處理函數(shù)
    if (!ptr) zmalloc_oom_handler(size);
    // 內(nèi)存統(tǒng)計函數(shù)
    *((size_t*)ptr) = size;
    update_zmalloc_stat_alloc(size+PREFIX_SIZE);
    return (char*)ptr+PREFIX_SIZE;
}

內(nèi)存調(diào)整函數(shù)zrecalloc
Redis定義的zrecalloc用于調(diào)整已申請內(nèi)存的大小,其本質(zhì)也是直接調(diào)用系統(tǒng)函數(shù)recalloc()

void *zrealloc(void *ptr, size_t size) {
    size_t oldsize;
    void *newptr;
    // 為空直接退出
    if (ptr == NULL) return zmalloc(size);
    // 找到內(nèi)存真正的起始位置
    realptr = (char*)ptr-PREFIX_SIZE;
    oldsize = *((size_t*)realptr);
    // 調(diào)用recalloc函數(shù)
    newptr = realloc(realptr,size+PREFIX_SIZE);
    if (!newptr) zmalloc_oom_handler(size);
    // 內(nèi)存統(tǒng)計
    *((size_t*)newptr) = size;
    update_zmalloc_stat_free(oldsize); // 先減去原來的已使用內(nèi)存大小
    update_zmalloc_stat_alloc(size); // 在加上調(diào)整后的大小
    return (char*)newptr+PREFIX_SIZE;
}

內(nèi)存釋放函數(shù)

與內(nèi)存申請函數(shù)調(diào)用malloc一樣,內(nèi)存釋放也是調(diào)用系統(tǒng)的free()函數(shù)來實現(xiàn)內(nèi)存釋放

void zfree(void *ptr) {
    if (ptr == NULL) return;  // 為空直接返回
    realptr = (char*)ptr-PREFIX_SIZE; // 找到該段內(nèi)存真正的起始位置
    oldsize = *((size_t*)realptr);
    update_zmalloc_stat_free(oldsize+PREFIX_SIZE);// 更新use_memory函數(shù)
    free(realptr); // 調(diào)用系統(tǒng)的內(nèi)存釋放函數(shù)
}

其中,內(nèi)存狀態(tài)統(tǒng)計函數(shù)的代碼實現(xiàn)如下:

#define update_zmalloc_stat_free(__n) do { \
    size_t _n = (__n); \ 
    if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1)); \  // 將內(nèi)存大小調(diào)整為sizeof(long)的整數(shù)倍
    if (zmalloc_thread_safe) { \  // 如果開啟了線程安全模式
        update_zmalloc_stat_sub(_n); \ // 更新use_memory值(與上述的update_zmalloc_stat_add這里就不贅述了)
    } else { \
        used_memory -= _n; \ // 沒有線程安全則直接減
    } \
} while(0)

講到這里,Redis基本的內(nèi)存處理函數(shù)已經(jīng)分析完畢了。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • C語言中內(nèi)存分配 在任何程序設(shè)計環(huán)境及語言中,內(nèi)存管理都十分重要。在目前的計算機系統(tǒng)或嵌入式系統(tǒng)中,內(nèi)存資源仍然是...
    一生信仰閱讀 1,314評論 0 2
  • (JG-2014-08-20)(前半部分經(jīng)過網(wǎng)上多篇文章對比整理)(后半部分根據(jù)ExceptionalCpp、C+...
    JasonGao閱讀 5,745評論 2 23
  • 轉(zhuǎn)載地址:http://gnucto.blog.51cto.com/3391516/998509 Redis與Me...
    Ddaidai閱讀 21,550評論 0 82
  • (一)萬祥酒店:下榻,早餐 (二)景點1:陡坡塘瀑布 (三)景點2:天星橋 (四)黃果樹瀑布(大瀑布)
    李家莊閱讀 252評論 0 0
  • 我愛旗袍 夏海芹 少有這樣的淘寶店鋪,打開網(wǎng)頁,音樂便如原野的風撲面而來,讓身體的每一個毛孔都舒適服帖。這是一家旗...
    清泉石上流的簡書閱讀 1,342評論 3 7

友情鏈接更多精彩內(nèi)容