1. 程式人生 > >Redis原始碼剖析--記憶體管理zmalloc

Redis原始碼剖析--記憶體管理zmalloc

功能函式總覽

在zmalloc.h中,定義了Redis記憶體分配的主要功能函式,這些函式基本上實現了Redis記憶體申請,釋放和統計等功能,其函式宣告如下:

void *zmalloc(size_t size);                                // 呼叫zmalloc函式,申請size大小的空間
void *zcalloc(size_t size);                                // 呼叫系統函式calloc申請記憶體空間
void *zrealloc(void *ptr, size_t size);                    //
原記憶體重新調整為size空間的大小 void zfree(void *ptr); // 呼叫zfree釋放記憶體空間 void zmalloc_set_oom_handler(void (*oom_handler)(size_t)); // 可自定義設定記憶體溢位的處理方法 void zlibc_free(void *ptr); // 原始系統free釋放方法

zmalloc.c中的幾個變數和概念:

static size_t used_memory = 0
; // 已使用記憶體的大小 static int zmalloc_thread_safe = 0; // 執行緒安全模式狀態 pthread_mutex_t used_memory_mutex = PTHREAD_MUTEX_INITIALIZER;

 

記憶體申請函式zmalloc

Redis的記憶體申請函式zmalloc本質就是呼叫了系統的malloc函式,然後對其進行了適當的封裝,加上了異常處理函式和記憶體統計。在zmalloc函式中,實際可能會每次多申請一個 PREFIX_SIZE的空間。從如下的程式碼中看出,如果定義了巨集HAVE_MALLOC_SIZE,那麼 PREFIX_SIZE的長度為0。其他的情況下,都會多分配額外的PREFIX_SIZE記憶體空間來儲存記憶體空間大小。其原始碼如下:

// 定義了HAVE_MALLOC_SIZE巨集,不會額外申請儲存空間大小的記憶體
#ifdef HAVE_MALLOC_SIZE
#define PREFIX_SIZE (0)
#else
// 申請額外空間儲存空間大小
#if defined(__sun) || defined(__sparc) || defined(__sparc__)
#define PREFIX_SIZE (sizeof(long long))
#else
#define PREFIX_SIZE (sizeof(size_t))
#endif
#endif
void *zmalloc(size_t size) 
{
    // 呼叫malloc函式進行記憶體申請
    // 多申請的PREFIX_SIZE大小的記憶體用於記錄該段記憶體的大小
    void *ptr = malloc(size+PREFIX_SIZE);

    // 如果ptr為NULL,則呼叫異常處理函式
    if (!ptr) zmalloc_oom_handler(size);

    // 更新use_memory的大小
#ifdef HAVE_MALLOC_SIZE
    update_zmalloc_stat_alloc(zmalloc_size(ptr));
    return ptr;
#else
    *((size_t*)ptr) = size;
    update_zmalloc_stat_alloc(size+PREFIX_SIZE);
    return (char*)ptr+PREFIX_SIZE;
#endif
}

這麼做的原因是因為: tcmalloc 和 Mac平臺下的 malloc 函式族提供了計算已分配空間大小的函式(分別是tc_malloc_size和malloc_size),所以就不需要單獨分配一段空間記錄大小了。而針對linux和sun平臺則要記錄分配空間大小。對於linux,使用sizeof(size_t)定長欄位記錄;對於sun os,使用sizeof(long long)定長欄位記錄。因此當巨集HAVE_MALLOC_SIZE沒有被定義的時候,就需要在多分配出的空間內記錄下當前申請的記憶體空間的大小。

對於巨集HAVE_MALLOC_SIZE的定義如下(此處僅給出tcmalloc部分):

#if defined(USE_TCMALLOC)
#define ZMALLOC_LIB ("tcmalloc-" __xstr(TC_VERSION_MAJOR) "." __xstr(TC_VERSION_MINOR))
#include <google/tcmalloc.h>
#if (TC_VERSION_MAJOR == 1 && TC_VERSION_MINOR >= 6) || (TC_VERSION_MAJOR > 1)
// 安裝tcmalloc時,通過巨集使用計算分配空間大小的函式tc_malloc_size()
#define HAVE_MALLOC_SIZE 1
#define zmalloc_size(p) tc_malloc_size(p)
#else
#error "Newer version of tcmalloc required"
#endif

 

update_zmalloc_stat_alloc

update_zmalloc_stat_alloc 是一個巨集,因為sizeof(long) == 8 [64位系統中],所以其實第一個if的程式碼等價於if(_n&7) _n += 8 - (_n&7); 這段程式碼就是判斷分配的記憶體空間的大小是不是8的倍數。如果記憶體大小不是8的倍數,就加上相應的偏移量使之變成8的倍數。_n&7 在功能上等價於 _n%8,不過位操作的效率顯然更高。第二個if主要判斷當前是否處於執行緒安全的情況下。如果處於執行緒安全的情況下,就使用update_zmalloc_stat_add巨集來更改全域性變數used_memory。否則的話就直接加上n。

#define update_zmalloc_stat_alloc(__n) do { \
    size_t _n = (__n); \
    // 將_n調整為sizeof(long)的整數倍
    if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1)); \
    if (zmalloc_thread_safe) { \        // 如果啟用了執行緒安全模式
        update_zmalloc_stat_add(_n); \  // 呼叫原子操作加(+)來更新已用記憶體
    } else { \
        used_memory += _n; \            // 不考慮執行緒安全,則直接更新已用記憶體
    } \
} while(0)

update_zmalloc_stat_add()的定義如下:

// __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,則呼叫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)

zfree

有分配就有記憶體回收,zfree 函式就是實現記憶體回收的功能。

void zfree(void *ptr) 
{
#ifndef HAVE_MALLOC_SIZE
    void *realptr;
    size_t oldsize;
#endif

    if (ptr == NULL) return;
#ifdef HAVE_MALLOC_SIZE
    update_zmalloc_stat_free(zmalloc_size(ptr));
    free(ptr);
#else
    realptr = (char*)ptr-PREFIX_SIZE;
    oldsize = *((size_t*)realptr);
    update_zmalloc_stat_free(oldsize+PREFIX_SIZE);
    free(realptr);
#endif
}

上面的程式碼可以看出,根據用的庫不相同,回收的時候也採用了不同的方法。

可以發現如果使用的libc庫,則需要將ptr指標向前偏移PREFIX_SIZE個位元組的長度,回退到最初malloc返回的地址,然後通過型別轉換再取指標所指向的值。通過zmalloc()函式的分析,可知這裡儲存著我們最初需要分配的記憶體大小(zmalloc中的size),這裡賦值給oldsize。update_zmalloc_stat_free()也是一個巨集函式,和zmalloc中update_zmalloc_stat_alloc()大致相同,唯一不同之處是前者在給變數used_memory減去分配的空間,而後者是加上該空間大小。
最後free(realptr),清除空間。

 

 

在zmalloc.h中,定義了Redis記憶體分配的主要功能函式,這些函式基本上實現了Redis記憶體申請,釋放和統計等功能,其函式宣告如下:

void *zmalloc(size_t size);                                // 呼叫zmalloc函式,申請size大小的空間
void *zcalloc(size_t size);                                // 呼叫系統函式calloc申請記憶體空間
void *zrealloc(void *ptr, size_t size);                    // 原記憶體重新調整為size空間的大小
void zfree(void *ptr);                                     // 呼叫zfree釋放記憶體空間
void zmalloc_set_oom_handler(void (*oom_handler)(size_t)); // 可自定義設定記憶體溢位的處理方法
void zlibc_free(void *ptr);                                // 原始系統free釋放方法

zmalloc.c中的幾個變數和概念:

static size_t used_memory = 0;        // 已使用記憶體的大小
static int zmalloc_thread_safe = 0;   // 執行緒安全模式狀態
pthread_mutex_t used_memory_mutex = PTHREAD_MUTEX_INITIALIZER; 

 

記憶體申請函式zmalloc

Redis的記憶體申請函式zmalloc本質就是呼叫了系統的malloc函式,然後對其進行了適當的封裝,加上了異常處理函式和記憶體統計。在zmalloc函式中,實際可能會每次多申請一個 PREFIX_SIZE的空間。從如下的程式碼中看出,如果定義了巨集HAVE_MALLOC_SIZE,那麼 PREFIX_SIZE的長度為0。其他的情況下,都會多分配額外的PREFIX_SIZE記憶體空間來儲存記憶體空間大小。其原始碼如下:

// 定義了HAVE_MALLOC_SIZE巨集,不會額外申請儲存空間大小的記憶體
#ifdef HAVE_MALLOC_SIZE
#define PREFIX_SIZE (0)
#else
// 申請額外空間儲存空間大小
#if defined(__sun) || defined(__sparc) || defined(__sparc__)
#define PREFIX_SIZE (sizeof(long long))
#else
#define PREFIX_SIZE (sizeof(size_t))
#endif
#endif
void *zmalloc(size_t size) 
{
    // 呼叫malloc函式進行記憶體申請
    // 多申請的PREFIX_SIZE大小的記憶體用於記錄該段記憶體的大小
    void *ptr = malloc(size+PREFIX_SIZE);

    // 如果ptr為NULL,則呼叫異常處理函式
    if (!ptr) zmalloc_oom_handler(size);

    // 更新use_memory的大小
#ifdef HAVE_MALLOC_SIZE
    update_zmalloc_stat_alloc(zmalloc_size(ptr));
    return ptr;
#else
    *((size_t*)ptr) = size;
    update_zmalloc_stat_alloc(size+PREFIX_SIZE);
    return (char*)ptr+PREFIX_SIZE;
#endif
}

這麼做的原因是因為: tcmalloc 和 Mac平臺下的 malloc 函式族提供了計算已分配空間大小的函式(分別是tc_malloc_size和malloc_size),所以就不需要單獨分配一段空間記錄大小了。而針對linux和sun平臺則要記錄分配空間大小。對於linux,使用sizeof(size_t)定長欄位記錄;對於sun os,使用sizeof(long long)定長欄位記錄。因此當巨集HAVE_MALLOC_SIZE沒有被定義的時候,就需要在多分配出的空間內記錄下當前申請的記憶體空間的大小。

對於巨集HAVE_MALLOC_SIZE的定義如下(此處僅給出tcmalloc部分):

#if defined(USE_TCMALLOC)
#define ZMALLOC_LIB ("tcmalloc-" __xstr(TC_VERSION_MAJOR) "." __xstr(TC_VERSION_MINOR))
#include <google/tcmalloc.h>
#if (TC_VERSION_MAJOR == 1 && TC_VERSION_MINOR >= 6) || (TC_VERSION_MAJOR > 1)
// 安裝tcmalloc時,通過巨集使用計算分配空間大小的函式tc_malloc_size()
#define HAVE_MALLOC_SIZE 1
#define zmalloc_size(p) tc_malloc_size(p)
#else
#error "Newer version of tcmalloc required"
#endif

 

update_zmalloc_stat_alloc

update_zmalloc_stat_alloc 是一個巨集,因為sizeof(long) == 8 [64位系統中],所以其實第一個if的程式碼等價於if(_n&7) _n += 8 - (_n&7); 這段程式碼就是判斷分配的記憶體空間的大小是不是8的倍數。如果記憶體大小不是8的倍數,就加上相應的偏移量使之變成8的倍數。_n&7 在功能上等價於 _n%8,不過位操作的效率顯然更高。第二個if主要判斷當前是否處於執行緒安全的情況下。如果處於執行緒安全的情況下,就使用update_zmalloc_stat_add巨集來更改全域性變數used_memory。否則的話就直接加上n。

#define update_zmalloc_stat_alloc(__n) do { \
    size_t _n = (__n); \
    // 將_n調整為sizeof(long)的整數倍
    if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1)); \
    if (zmalloc_thread_safe) { \        // 如果啟用了執行緒安全模式
        update_zmalloc_stat_add(_n); \  // 呼叫原子操作加(+)來更新已用記憶體
    } else { \
        used_memory += _n; \            // 不考慮執行緒安全,則直接更新已用記憶體
    } \
} while(0)

update_zmalloc_stat_add()的定義如下:

// __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,則呼叫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)

zfree

有分配就有記憶體回收,zfree 函式就是實現記憶體回收的功能。

void zfree(void *ptr) 
{
#ifndef HAVE_MALLOC_SIZE
    void *realptr;
    size_t oldsize;
#endif

    if (ptr == NULL) return;
#ifdef HAVE_MALLOC_SIZE
    update_zmalloc_stat_free(zmalloc_size(ptr));
    free(ptr);
#else
    realptr = (char*)ptr-PREFIX_SIZE;
    oldsize = *((size_t*)realptr);
    update_zmalloc_stat_free(oldsize+PREFIX_SIZE);
    free(realptr);
#endif
}

上面的程式碼可以看出,根據用的庫不相同,回收的時候也採用了不同的方法。

可以發現如果使用的libc庫,則需要將ptr指標向前偏移PREFIX_SIZE個位元組的長度,回退到最初malloc返回的地址,然後通過型別轉換再取指標所指向的值。通過zmalloc()函式的分析,可知這裡儲存著我們最初需要分配的記憶體大小(zmalloc中的size),這裡賦值給oldsize。update_zmalloc_stat_free()也是一個巨集函式,和zmalloc中update_zmalloc_stat_alloc()大致相同,唯一不同之處是前者在給變數used_memory減去分配的空間,而後者是加上該空間大小。
最後free(realptr),清除空間。