redis源碼1---內(nèi)存管理(zmalloc)

打算學(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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容