天天看點

zmalloc記憶體管理(1)(Redis源碼學習)zmalloc記憶體管理(1)(Redis源碼學習)

zmalloc記憶體管理(1)(Redis源碼學習)

在學習sds.c源碼時,sdsnewlen函數中開始調用zmalloc相關庫函數:

sds sdsnewlen(const void *init, size_t initlen) {
    ... //省略部分代碼

    // 根據是否有初始化内容,選擇适當的記憶體配置設定方式
    // T = O(N)
    if (init) {
        // zmalloc 不初始化所配置設定的記憶體
        sh = zmalloc(sizeof(struct sdshdr)+initlen+1); //8 + 3 + 1
    } else {
        // zcalloc 将配置設定的記憶體全部初始化為 0
        sh = zcalloc(sizeof(struct sdshdr)+initlen+1);
    }
    ... //省略部分代碼
           

對于這種基礎底層的庫,相信後續其它子產品也都用的,故花時間學習研究并總結加深印象。

1. 主要函數

void *zmalloc(size_t size);
void *zcalloc(size_t size);
void *zrealloc(void *ptr, size_t size);
void zfree(void *ptr);
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);
size_t zmalloc_get_private_dirty(void);
void zlibc_free(void *ptr);
           

本文主要講解其中zmalloc,zcalloc,zfree函數,其它函數後續遇到繼續學習并總結。

2. zmalloc函數及調用

void *zmalloc(size_t size) {
    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
    *((size_t*)ptr) = size;
    update_zmalloc_stat_alloc(size+PREFIX_SIZE);
    return (char*)ptr+PREFIX_SIZE;
#endif
}
           

參數size是要配置設定的空間大小,見文章開頭為12。但該函數将我們需要配置設定的空間大小改為

size+PREFIX_SIZE

。PREFIX_SIZE是根據平台的不同和HAVE_MALLOC_SIZE宏定義控制的。關于HAVE_MALLOC_SIZE及相關宏定義,後續文章會專門介紹這一部分,涉及到不同系統環境。

如果malloc()函數調用失敗,就會調用zmalloc_oom_handler()函數來列印異常,并且會有調用abort()終止。zmalloc_oom_handler其實是一個函數指針,真正調用的函數是zmalloc_default_oom(),size作為參數傳入。

zmalloc_default_oom()函數源碼如下:

static void zmalloc_default_oom(size_t size) {
    fprintf(stderr, "zmalloc: Out of memory trying to allocate %zu bytes\n",
        size);
    fflush(stderr);
    abort();
}
static void (*zmalloc_oom_handler)(size_t) = zmalloc_default_oom;
           

我在RHEL6.9上進行測試上述代碼時,由于最開始源碼編譯安裝redis時,直接使用make編譯時報錯,是以根據其下README檔案中提示用的是

make MALLOC=libc

,這個make編譯會對此處的代碼産生影響,我編譯運作sds子產品時,則上面zmalloc函數就會直接走到

#else

部分。

PREFIX_SIZE在我使用的RHEL6.9中會是下面這個值,

PREFIX_SIZE其實就是字長,64位伺服器則為8個位元組,32位機器就是4位元組,在malloc區域之後,則

*((size_t*)ptr) = size;

會将存儲區頭8個位元組存放函數儲存SIZE大小,用以記錄配置設定記憶體的長度,下面簡圖示意:

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-F2p13vwD-1625733324121)(P1.jpg)]

在update_zmalloc_stat_alloc()宏定義函數中更新used_memory這個靜态變量的值,update_zmalloc_stat_alloc()源碼如下:

#define update_zmalloc_stat_alloc(__n) do { \
    size_t _n = (__n); \
    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_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,該變量累加了每次新配置設定的記憶體大小。zmalloc函數在最後傳回了一個偏移的指針,指向了目前配置設定記憶體的資料體部分。

說明:

我在sds.c代碼學習時,隻是将sds代碼子產品h和c檔案直接拿出來進行編譯,并沒有使用直接啟動redis進行代碼調試學習。是以在我單獨調試驗證sds子產品時,

zmalloc_thread_safe就會一直是0, 就會直接執行

used_memory += _n;

這一句。

但是,在redis代碼main函數入口沒幾行,就調用了 zmalloc_enable_thread_safeness 函數,該函數内容如下:

void zmalloc_enable_thread_safeness(void) {
    zmalloc_thread_safe = 1;
}
           

是以說如果直接啟動Redis程式,則zmalloc_thread_safe值為1,會預設走if分支,這一點要注意。

小知識:

在遇到宏時,想要看看宏參數傳入以後的樣子,則在gcc編譯時将-g改為-g3,然後用gdb調試,就可以看到宏參數代入以後的函數樣子。

3. zfree函數及調用

zmalloc配置設定内測調用C語言的malloc函數,那自然會有zfree調用free釋放記憶體的函數比對。

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
}
           

zfree 函數首先判斷,如果沒有定義 HAVE_MALLOC_SIZE,就定義了變量 realptr 和 oldsize ,然後找到 zmalloc 中配置設定的内測起始位址,需要減去 PREFIX_SIZE,參考上面圖檔中。然後對應的進行調用 update_zmalloc_stat_free 函數釋放配置設定的記憶體空間。還要注意上面 void 和 size_t 的類型轉換以及從指針中取值。oldsize 的值是存儲部分的長度,直接從 used_memory 減去 PREFIX_SIZE 與 實際存儲部分的長度之和。如果一時不好了解,請按照上面圖檔畫圖反推就能明白。

對應的 update_zmalloc_stat_free 代碼如下所示,操作均與 malloc 上面調用的 alloc 宏相反。

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

4. 小結

zmalloc及zfree相關内測操作函數,簡單總結如上,參考網絡文章如下連結:

https://www.cnblogs.com/daryl-blog/p/11003127.html

https://www.cnblogs.com/lizhimin123/p/9964205.html

https://blog.csdn.net/guodongxiaren/article/details/44747719

本人才疏學淺,了解水準有限,如果有錯誤及不當之處,請批評指正。

如果對您有一點點幫助,請幫忙轉發和關注工作号:hongmaolinux,感激不盡_!

zmalloc記憶體管理(1)(Redis源碼學習)zmalloc記憶體管理(1)(Redis源碼學習)

繼續閱讀