打算學(xué)習(xí)一下redis源碼,結(jié)果剛開始看sds就發(fā)現(xiàn)一個(gè)陌生的詞匯,zmalloc,查看zmalloc的實(shí)現(xiàn),發(fā)現(xiàn)是對malloc的封裝,并且還引出了ptMalloc和tcMalloc等知識,關(guān)于malloc庫和redis的其他內(nèi)存管理的知識,后續(xù)查看了后再談
字長與字節(jié)對齊
首先要了解一個(gè)操作系統(tǒng)的基礎(chǔ),字長和字節(jié)對齊,字長是指 CPU一次性能讀取數(shù)據(jù)的二進(jìn)制位數(shù),也就是我們通常所說的32位系統(tǒng)(字長4個(gè)字節(jié))、64位系統(tǒng)(字長8個(gè)字節(jié))的由來。所謂的字節(jié)對齊,簡單的介紹可以查看 https://blog.csdn.net/ldw662523/article/details/79623404,總之,當(dāng)我們向系統(tǒng)申請內(nèi)存的時(shí)候,系統(tǒng)會(huì)返回給我們 n倍個(gè)字長的字節(jié)數(shù),比如我們申請10字節(jié),64位系統(tǒng)下會(huì)返回給我們2*8個(gè)字節(jié)
long類型變量和指針類型變量占用一字長的字節(jié)數(shù),在32為系統(tǒng)下,sizeof(char *) 和 sizeof(long) 是4,在64位下是8,本文接下來內(nèi)容都用64位,也就是8字節(jié)對齊為主
主要變量和函數(shù)
全局變量
//定義當(dāng)前進(jìn)程已使用的內(nèi)存總量
static size_t used_memory = 0;
//標(biāo)識是否線程安全,值為1時(shí)線程安全
static int zmalloc_thread_safe = 0;
//如果是線程安全的,修改used_memory 時(shí)的互斥鎖
pthread_mutex_t used_memory_mutex = PTHREAD_MUTEX_INITIALIZER;
//zmalloc_oom_handler 為函數(shù)指針,當(dāng)內(nèi)存出錯(cuò)時(shí)調(diào)用,默認(rèn)的調(diào)用函數(shù)是 zmalloc_default_oom
//oom 是out of memory
static void (*zmalloc_oom_handler)(size_t) = zmalloc_default_oom;
主要函數(shù)
void *zmalloc(size_t size); //分配內(nèi)存空間
void zcalloc(size_t size); //分配內(nèi)存并初始化為0
void zrealloc(void *ptr,size_t size); //重新分配內(nèi)存空間的大小
void zfree(void *ptr); //釋放zmalloc所分配的內(nèi)存空間
char *zstrdup(const char *s); //字符串復(fù)制
void zlibc_free(void *ptr); //同free()
宏
//linux的glibc下是sizeof(size_t),64位系統(tǒng)為8字節(jié)
//當(dāng)zmalloc申請內(nèi)存時(shí),多分配PREFIX_SIZE 個(gè)字節(jié)
PREFIX_SIZE
//若使用tcmalloc、jemalloc或Mac系統(tǒng)則定義此宏,linux的glibc不定義
HAVE_MALLOC_SIZE
宏函數(shù)
//分配內(nèi)存空間后更新used_memory的值
update_zamlloc_stat_alloc
//釋放內(nèi)存空間后更新used_memory的值
update_zamlloc_stat_free
//線程安全地used_memory增加操作
update_zamlloc_stat_add
//線程安全地used_memory減少操作
update_zamlloc_stat_sub
zmalloc
void *zmalloc(size_t size) {
//size是要分配的內(nèi)存大小,多分配了PREFIX_SIZE個(gè)字節(jié),用于存放size的大小
void *ptr = malloc(size + PREFIX_SIZE);
//如果內(nèi)存分配失敗,調(diào)用函數(shù)指針zmalloc_oom_handler所指向的函數(shù)
if (!ptr) zmalloc_oom_handler(size);
#ifdef HAVE_MALLOC_SIZE
update_zmalloc_stat_alloc(zmalloc_size(ptr));
return ptr;
#else
//前sizeof(size_t) 個(gè)字節(jié),存儲(chǔ)要分配的內(nèi)存大小
*((size_t*)ptr) = size;
update_zmalloc_stat_alloc(size+PREFIX_SIZE);
//只返回給用戶可用的內(nèi)存空間
return (char*)ptr+PREFIX_SIZE;
#endif
}
PREFIX_SIZE是一個(gè)條件編譯的宏,不同的平臺有不同的結(jié)果,在Linux中其值是sizeof(size_t),所以我們多分配了一個(gè)字長(8個(gè)字節(jié))的空間,用于存放size的值
zmalloc_oom_handler指向內(nèi)存錯(cuò)誤處理函數(shù),默認(rèn)是指向函數(shù)zmalloc_default_oom,其主要功能就是打印錯(cuò)誤信息并終止程序。
static void zmalloc_default_oom(size_t size) {
//將錯(cuò)誤輸出到標(biāo)準(zhǔn)錯(cuò)誤
fprintf(stderr, "zmalloc: Out of memory trying to allocate %zu bytes\n",
size);
//立即刷新標(biāo)準(zhǔn)錯(cuò)誤緩沖區(qū)
fflush(stderr);
abort();
}
linux下不定義宏 HAVE_MALLOC_SIZE,所以走 #else的代碼
//前sizeof(size_t) 個(gè)字節(jié),存儲(chǔ)要分配的內(nèi)存大小
*((size_t*)ptr) = size;
update_zmalloc_stat_alloc(size+PREFIX_SIZE);
//只返回給用戶可用的內(nèi)存空間
return (char*)ptr+PREFIX_SIZE;
前sizeof(size_t),也就是PREFIX_SIZE個(gè)字節(jié),存放所申請的內(nèi)存大小size。我們先看一下
return (char*)ptr+PREFIX_SIZE;
將指針后移 PREFIX_SIZE 位,只給用戶返回可用的內(nèi)存空間,所以malloc出來的內(nèi)存被分為了兩部分,一部分用來存放size的值,一部分返回給用戶。
然后調(diào)用了宏函數(shù)update_zmalloc_stat_alloc
update_zmalloc_stat_alloc
#define update_zmalloc_stat_alloc(__n) do { \
size_t _n = (__n); \
//判斷 _n是否是8的倍數(shù)
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)
其中
if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1)); \
用位運(yùn)算判斷 _n 是否是8的倍數(shù),如果不是,加上所需的數(shù)值,使之能被8整除,比如,申請20bytes, 20 & (sizeof (long) - 1) = 20 & 7 = 4,非零,不能整除,即缺 8 - 4 = 4 bytes,于是申請的20 bytes多申請4bytes,24 mod 8 = 0正好
其實(shí)malloc出來的內(nèi)存,已經(jīng)是8的倍數(shù),系統(tǒng)自動(dòng)幫我們完成字節(jié)對齊,我們之所以還要判斷一次,是為了保證used_memory的值是實(shí)際分配的內(nèi)存大小。比如用戶申請20字節(jié),malloc以后,系統(tǒng)分配了24字節(jié),但是我們并不知道分配了24字節(jié),所以我們要像系統(tǒng)一樣,去做一次字節(jié)對齊,這樣能夠知道系統(tǒng)具體分配了多少
接下來判斷是否是線程安全的,zmalloc_thread_safe默認(rèn)為0,也就是走else下面,直接給used_memory 加上值。如果是線程安全的,調(diào)用宏函數(shù) update_zmalloc_stat_add
update_zmalloc_stat_add
#define update_zmalloc_stat_add(__n) do { \
pthread_mutex_lock(&used_memory_mutex); \
used_memory += (__n); \
pthread_mutex_unlock(&used_memory_mutex); \
} while(0)
其實(shí)就是調(diào)用了互斥鎖來保證增加used_memory 值的時(shí)候是線程安全的,注意到一點(diǎn)是宏函數(shù)外層都用 do while(0) 包裹著,其實(shí)并不是額外加了什么功能,只是為了保持宏函數(shù)實(shí)現(xiàn)的時(shí)候語意和我們所寫的代碼一致,防止大括號以及分號的干擾,詳見 https://blog.csdn.net/Move_now/article/details/73480195
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
//將指針前移 PREFIX_SIZE 位
realptr = (char*)ptr-PREFIX_SIZE;
//取出當(dāng)zmalloc時(shí)返回給用戶的可用的內(nèi)存空間大小
oldsize = *((size_t*)realptr);
update_zmalloc_stat_free(oldsize+PREFIX_SIZE);
free(realptr);
#endif
}
我們還是關(guān)注沒有定義 HAVE_MALLOC_SIZE 的部分,定義指針realptr,讓其指向最初malloc返回的地址,用oldsize 保存用戶所申請的內(nèi)存大小
update_zmalloc_stat_free 和 update_zmalloc_stat_alloc 一樣是宏函數(shù),不同之處是前者在給變量used_memory減去分配的空間,而后者是加上該空間大小
update_zmalloc_stat_free
#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)
update_zmalloc_sub與zmalloc()中的update_zmalloc_add相對應(yīng),但功能相反,提供線程安全地used_memory減法操作
update_zmalloc_stat_sub
#define update_zmalloc_stat_sub(__n) do { \
pthread_mutex_lock(&used_memory_mutex); \
used_memory -= (__n); \
pthread_mutex_unlock(&used_memory_mutex); \
} while(0)
zcalloc
zcalloc()的實(shí)現(xiàn)基于calloc(),但是兩者編程接口不同
void *calloc(size_t nmemb, size_t size);
void *zcalloc(size_t size);
calloc()的功能是也是分配內(nèi)存空間,與malloc()的不同之處有兩點(diǎn):
1.它分配的空間大小是 size * nmemb。比如calloc(10,sizoef(char)); // 分配10個(gè)字節(jié)
2.calloc()會(huì)對分配的空間做初始化工作(初始化為0),而malloc()不會(huì)
void *zcalloc(size_t size) {
void *ptr = calloc(1, 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
}
zaclloc和zmalloc原理是一樣的,只不過申請內(nèi)存時(shí)malloc換成了calloc,和zmalloc相比就是申請的內(nèi)存被初始化了
zrealloc
zrealloc()和realloc()具有相同的編程接口:
void *realloc (void *ptr, size_t size);
void *zrealloc(void *ptr, size_t size);
realloc()要完成的功能是給首地址ptr的內(nèi)存空間,重新分配大小。如果失敗了,則在其它位置新建一塊大小為size字節(jié)的空間,將原先的數(shù)據(jù)復(fù)制到新的內(nèi)存空間,并返回這段內(nèi)存首地址【原內(nèi)存會(huì)被系統(tǒng)自然釋放】。 zrealloc()要完成的功能也類似。
void *zrealloc(void *ptr, size_t size) {
#ifndef HAVE_MALLOC_SIZE
void *realptr;
#endif
size_t oldsize;
void *newptr;
if (ptr == NULL) return zmalloc(size);
#ifdef HAVE_MALLOC_SIZE
oldsize = zmalloc_size(ptr);
newptr = realloc(ptr,size);
if (!newptr) zmalloc_oom_handler(size);
update_zmalloc_stat_free(oldsize);
update_zmalloc_stat_alloc(zmalloc_size(newptr));
return newptr;
#else
realptr = (char*)ptr-PREFIX_SIZE;
//原來分配的內(nèi)存大小
oldsize = *((size_t*)realptr);
//新申請的內(nèi)存大小
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;
#endif
}
zstrdup
復(fù)制字符串s的內(nèi)容,申請新的內(nèi)存空間存儲(chǔ)字符串。并將這段新的字符串地址返回
char *zstrdup(const char *s) {
//strlen()函數(shù)是不統(tǒng)計(jì)'\0'的,所以最后要加1
size_t l = strlen(s)+1;
char *p = zmalloc(l);
//調(diào)用memcpy來完成復(fù)制
memcpy(p,s,l);
return p;
}
zmalloc_size
這個(gè)函數(shù)是為glibc定制的,只有用這個(gè)庫時(shí),才能使用這個(gè)函數(shù)
#ifndef HAVE_MALLOC_SIZE
size_t zmalloc_size(void *ptr) {
void *realptr = (char*)ptr-PREFIX_SIZE;
size_t size = *((size_t*)realptr);
if (size&(sizeof(long)-1)) size += sizeof(long)-(size&(sizeof(long)-1));
return size+PREFIX_SIZE;
}
#endif
得到zmalloc像系統(tǒng)申請的真實(shí)的內(nèi)存大小
參考資料:https://blog.csdn.net/guodongxiaren/article/details/44747719
https://blog.csdn.net/u012842205/article/details/50392119