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,感激不盡_!