天天看點

redis源碼學習之配置設定記憶體

1、redis 總體概況

Redis在記憶體配置設定方面,僅僅是對系統的malloc/free做了一層簡單的封裝,然後加上了異常處理功能和記憶體統計功能。其實作主要在zmalloc.c和zmalloc.h檔案中

2、功能函數

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釋放記憶體空間
char *zstrdup(const char *s); // 字元串複制方法
size_t zmalloc_used_memory(void); // 擷取目前以及占用的記憶體空間大小
void zmalloc_enable_thread_safeness(void); // 是否設定線程安全模式
void zmalloc_set_oom_handler(void (*oom_handler)(size_t)); // 可自定義設定記憶體溢出的處理方法
float zmalloc_get_fragmentation_ratio(size_t rss); // 擷取所給記憶體和已使用記憶體的大小之比
size_t zmalloc_get_rss(void); // 擷取RSS資訊(Resident Set Size)
size_t zmalloc_get_private_dirty(void); // 獲得實際記憶體大小
size_t zmalloc_get_smap_bytes_by_field(char *field); // 擷取/proc/self/smaps字段的位元組數
size_t zmalloc_get_memory_size(void); // 擷取實體記憶體大小
void zlibc_free(void *ptr); // 原始系統free釋放方法

幾個變量
static size_t used_memory = 0;  // 已使用記憶體的大小
static int zmalloc_thread_safe = 0; // 線程安全模式狀态
pthread_mutex_t used_memory_mutex = PTHREAD_MUTEX_INITIALIZER; // 為此伺服器

           

3、分步解析

  1. 異常處理函數

    如果配置設定失敗,就将錯誤輸出到标準錯誤輸出位元組流上面,程序終止

    将其指派給函數指針static void (*zmalloc_oom_handler)(size_t) = zmalloc_default_oom

static void zmalloc_default_oom(size_t size) {
    fprintf(stderr, "zmalloc: Out of memory trying to allocate %Iu bytes\n",    WIN_PORT_FIX size);
    fflush(stderr);  //fflush用于清空緩沖流
    abort(); //abort()函數一定會使得程序終止
}
           

2.zmalloc函數

Redis的記憶體申請函數zmalloc本質就是調用了系統的malloc函數,然後對其進行了适當的封裝,加上了異常處理函數和記憶體統計

void *zmalloc(size_t size) {
    // 調用malloc函數進行記憶體申請
    // 多申請的PREFIX_SIZE大小的記憶體用于記錄該段記憶體的大小
    void *ptr = malloc(size+PREFIX_SIZE);

    // 如果ptr為NULL,則調用異常處理函數
    if (!ptr) zmalloc_oom_handler(size);
    // 以下是記憶體統計
    *((size_t*)ptr) = size;
    update_zmalloc_stat_alloc(size+PREFIX_SIZE); // 更新used_memory的值
    return (char*)ptr+PREFIX_SIZE;
}
           

思考:為什麼要有PREFIX_SIZE?

由于malloc函數申請的記憶體不會辨別記憶體塊的大小,而我們需要統計記憶體大小,是以需要在多申請PREFIX_SIZE大小的記憶體,用于存放該大小

更新used_memory值的 函數以宏定義給出

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

思考:為什麼将将_n調整為sizeof(long)的整數倍?

因為要将其調整為8的倍數,根據記憶體池的配原理來

在上述函數中,線程安全的模式:對used_memory 這個資源的通路,通過加互斥鎖實,保證了同一時刻隻有唯一的一個線程對這個共享進行通路

改進:c++11中提供了一些原子操作,将used_memory 定義為原子操作資料,就無需借用鎖機制

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

    與malloc一樣,zcalloc調用的是系統給的calloc()來申請記憶體。

void *zcalloc(size_t size) {
    void *ptr = calloc(1, size+PREFIX_SIZE);
    // 異常處理函數
    if (!ptr) zmalloc_oom_handler(size);
    // 記憶體統計函數
    *((size_t*)ptr) = size;
    update_zmalloc_stat_alloc(size+PREFIX_SIZE);
    return (char*)ptr+PREFIX_SIZE;
}
           

思考:為什麼有了malloc 還會有calloc配置設定記憶體?

函數malloc()和calloc()都可以用來動态配置設定記憶體空間,但兩者稍有差別。

malloc()函數有一個參數,即要配置設定的記憶體空間的大小:

void *malloc(size_t size);

calloc()函數有兩個參數,分别為元素的數目和每個元素的大小,這兩個參數的乘積就是要配置設定的記憶體空間的大小。

void *calloc(size_t numElements,size_t sizeOfElement);

如果調用成功,函數malloc()和函數calloc()都将傳回所配置設定的記憶體空間的首位址。

但是, 函數malloc()和函數calloc()的主要差別是前者不能初始化所配置設定的記憶體空間,而後者能。如果由malloc()函數配置設定的記憶體空間原來沒有被使用過,則其中的每一位可能都是0;反之,如果這部分記憶體曾經被配置設定過,則其中可能遺留有各種各樣的資料,可能後面執行的時候會出錯

函數calloc()會将所配置設定的記憶體空間中的每一位都初始化為零,也就是說,如果你是為字元類型或整數類型的元素配置設定記憶體,那麽這些元素将保證會被初始化為0;如果你是為指針類型的元素配置設定記憶體,那麽這些元素通常會被初始化為空指針;如果你為實型資料配置設定記憶體,則這些元素會被初始化為浮點型的零

  1. zrecalloc函數

    作用:調整記憶體

    定義的zrecalloc用于調整已申請記憶體的大小,其本質也是直接調用系統函數recalloc()

void *zrealloc(void *ptr, size_t size) {
    size_t oldsize;
    void *newptr;
    // 為空直接退出
    if (ptr == NULL) return zmalloc(size);
    // 找到記憶體真正的起始位置
    realptr = (char*)ptr-PREFIX_SIZE;
    oldsize = *((size_t*)realptr);
    // 調用recalloc函數
    newptr = realloc(realptr,size+PREFIX_SIZE);
    if (!newptr) zmalloc_oom_handler(size);
    // 記憶體統計
    *((size_t*)newptr) = size;
    update_zmalloc_stat_free(oldsize); // 先減去原來的已使用記憶體大小
    update_zmalloc_stat_alloc(size); // 在加上調整後的大小
    return (char*)newptr+PREFIX_SIZE;
}
           

realloc(void *__ptr, size_t __size):更改已經配置的記憶體空間,即更改由malloc()函數配置設定的記憶體空間的大小

如果是将配置設定的記憶體擴大,則有以下情況:

1)如果目前記憶體段後面有需要的記憶體空間,則直接擴充這段記憶體空間,realloc()将傳回原指針。

2)如果目前記憶體段後面的空閑位元組不夠,那麼就使用堆中的第一個能夠滿足這一要求的記憶體塊,将目前的資料複制到新的位置,并将原來的資料塊釋放掉,傳回新的記憶體塊位置。

3)如果申請失敗,将傳回NULL,此時,原來的指針仍然有效

  1. zfree函數

    記憶體釋放也是調用系統的free()函數來實作記憶體釋放

void zfree(void *ptr) {
    if (ptr == NULL) return;  // 為空直接傳回
    realptr = (char*)ptr-PREFIX_SIZE; // 找到該段記憶體真正的起始位置
    oldsize = *((size_t*)realptr);
    update_zmalloc_stat_free(oldsize+PREFIX_SIZE);// 更新use_memory函數
    free(realptr); // 調用系統的記憶體釋放函數
}
           

更新記憶體狀态統計函數

#define update_zmalloc_stat_free(__n) do {
    size_t _n = (__n); 
    if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1));  // 将記憶體大小調整為sizeof(long)的整數倍
    if (zmalloc_thread_safe) {  // 如果開啟了線程安全模式
        update_zmalloc_stat_sub(_n); // 更新use_memory值(與上述的update_zmalloc_stat_add這裡就不贅述了)
    } else {
        used_memory -= _n; // 沒有線程安全則直接減
    }
} while(0)
           
  1. zsize函數

    傳回記憶體塊的大小

size_t zmalloc_size(void *ptr) {
    void *realptr = (char*)ptr-PREFIX_SIZE; //記憶體起始位址
    size_t size = *((size_t*)realptr) ;
    if (size&(sizeof(PORT_LONG)-1)) size += sizeof(PORT_LONG)-(size&(sizeof(PORT_LONG)-1));//調整到long 的整數倍
    return size+PREFIX_SIZE;
}
           
  1. 字元串複制
char *zstrdup(const char *s) {
    size_t l = strlen(s)+1; 
    char *p = zmalloc(l); // 開辟一段新記憶體
    memcpy(p,s,l); // 調用字元串複制函數
    return p;
}
           
  1. 設定異常處理函數

    自行定義異常函數,參數:函數指針

void zmalloc_set_oom_handler(void (*oom_handler)(size_t)) {
    zmalloc_oom_handler = oom_handler; // 綁定自定義的異常處理函數
}
           
  1. 開啟線程安全
void zmalloc_enable_thread_safeness(void) {
    zmalloc_thread_safe = 1;  // 此參數用來控制是否開啟線程安全
}
           
  1. 擷取已使用記憶體
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; // 未開啟則直接使用used_memory
    }

    return um;
}