redis源碼解讀--內(nèi)存分配zmalloc

主要函數(shù)

  • 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_set_oom_handler(void (*oom_handler)(size_t));

  • size_t zmalloc_get_rss(void);

  • int zmalloc_get_allocator_info(size_t *allocated, size_t *active, size_t *resident);

  • size_t zmalloc_get_private_dirty(long pid);

  • size_t zmalloc_get_smap_bytes_by_field(char *field, long pid);

  • size_t zmalloc_get_memory_size(void);

  • void zlibc_free(void *ptr);

void *zmalloc(size_t size)

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
}

參數(shù)size是需要分配的空間大小。事實上我們需要分配的空間大小為size+PREFIX_SIZE。PREFIX_SIZE是根據(jù)平臺的不同和HAVE_MALLOC_SIZE宏定義控制的。如果malloc()函數(shù)調(diào)用失敗致盟,就會調(diào)用zmalloc_oom_handler()函數(shù)來打印異常矮台,并且會終止函數(shù)阻问,zmalloc_oom_handler其實是一個函數(shù)指針啊终,真正調(diào)用的函數(shù)是zmalloc_default_oom(),zmalloc_default_oom()函數(shù)源碼如下:

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;

內(nèi)存分配成功之后,會依據(jù)HAVE_MALLOC_SIZE的控制對前八個字節(jié)操作份企,用以記錄分配內(nèi)存的長度,會在update_zmalloc_stat_alloc()宏定義函數(shù)中更新used_memory這個靜態(tài)變量的值巡莹,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)); \
    atomicIncr(used_memory,__n); \
} while(0)
if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1));

這一行是為了將不為sizeof(long)的_n對sizeof(long)補齊
atomicIncr(used_memory,__n);會調(diào)用__atomic_add_fetch(&var,(count),__ATOMIC_RELAXED);用于保證更新used_memory變量的操作是一個原子操作司志。

void *zcalloc(size_t size)

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
}

zcalloc函數(shù)和zmalloc函數(shù)處理的思路很是相似,就不做太多的解釋了

void *zrealloc(void *ptr, size_t size)

void *zrealloc(void *ptr, size_t size) {
    // 如果沒有定義HAVE_MALLOC_SIZE降宅,就說明PREFIX_SIZE宏定義不為0骂远,那么ptr并不是該段內(nèi)存真正的開始地址
#ifndef HAVE_MALLOC_SIZE
    void *realptr;
#endif
    size_t oldsize;
    void *newptr;

    if (ptr == NULL) return zmalloc(size);
// 根據(jù)HAVE_MALLOC_SIZE宏定義,oldsize,newptr獲取方式不一樣腰根,以及更新used_memory的細節(jié)
#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;
    oldsize = *((size_t*)realptr);
    newptr = realloc(realptr,size+PREFIX_SIZE);
    if (!newptr) zmalloc_oom_handler(size);

    *((size_t*)newptr) = size;
    update_zmalloc_stat_free(oldsize+PREFIX_SIZE);
    update_zmalloc_stat_alloc(size+PREFIX_SIZE);
    return (char*)newptr+PREFIX_SIZE;
#endif
}

大致思路就是根據(jù)新的size進行重新分配內(nèi)存激才,并且對used_memory變量進行更新。只不過獲取原內(nèi)存大小方式不一樣额嘿,根據(jù)HAVE_MALLOC_SIZE進行區(qū)分瘸恼。

void zfree(void *ptr)

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函數(shù)和zrealloc函數(shù)做法差不到太多,都是對oldsize和realptr對HAVE_MALLOC_SIZE有無聲明分別進行操作册养。

char *zstrdup(const char *s)

char *zstrdup(const char *s) {
    size_t l = strlen(s)+1;
    char *p = zmalloc(l);

    memcpy(p,s,l);
    return p;
}

該函數(shù)是創(chuàng)建一個字符串副本

size_t zmalloc_used_memory(void)

size_t zmalloc_used_memory(void) {
    size_t um;
    atomicGet(used_memory,um);
    return um;
}

獲取used_memory變量的值东帅,主要保證原子操作(在atomicGet(used_memory,um);中保證)

void zmalloc_set_oom_handler(void (*oom_handler)(size_t))

void zmalloc_set_oom_handler(void (*oom_handler)(size_t)) {
    zmalloc_oom_handler = oom_handler;
}

主要用來設(shè)置內(nèi)存分配失敗處理函數(shù)指針zmalloc_oom_handler的值

size_t zmalloc_get_rss(void)

size_t zmalloc_get_rss(void) {
    int page = sysconf(_SC_PAGESIZE);
    size_t rss;
    char buf[4096];
    char filename[256];
    int fd, count;
    char *p, *x;

    snprintf(filename,256,"/proc/%d/stat",getpid());
    if ((fd = open(filename,O_RDONLY)) == -1) return 0;
    if (read(fd,buf,4096) <= 0) {
        close(fd);
        return 0;
    }
    close(fd);

    p = buf;
    count = 23; /* RSS is the 24th field in /proc/<pid>/stat */
    while(p && count--) {
        p = strchr(p,' ');
        if (p) p++;
    }
    if (!p) return 0;
    x = strchr(p,' ');
    if (!x) return 0;
    *x = '\0';

    rss = strtoll(p,NULL,10);
    rss *= page;
    return rss;
}

返回駐留集大小

int zmalloc_get_allocator_info(size_t *allocated, size_t *active, size_t *resident)

#if defined(USE_JEMALLOC)
int zmalloc_get_allocator_info(size_t *allocated,
                               size_t *active,
                               size_t *resident) {
    uint64_t epoch = 1;
    size_t sz;
    *allocated = *resident = *active = 0;
    /* Update the statistics cached by mallctl. */
    sz = sizeof(epoch);
    je_mallctl("epoch", &epoch, &sz, &epoch, sz);
    sz = sizeof(size_t);
    /* Unlike RSS, this does not include RSS from shared libraries and other non
     * heap mappings. */
    je_mallctl("stats.resident", resident, &sz, NULL, 0);
    /* Unlike resident, this doesn't not include the pages jemalloc reserves
     * for re-use (purge will clean that). */
    je_mallctl("stats.active", active, &sz, NULL, 0);
    /* Unlike zmalloc_used_memory, this matches the stats.resident by taking
     * into account all allocations done by this process (not only zmalloc). */
    je_mallctl("stats.allocated", allocated, &sz, NULL, 0);
    return 1;
}
#else
int zmalloc_get_allocator_info(size_t *allocated,
                               size_t *active,
                               size_t *resident) {
    *allocated = *resident = *active = 0;
    return 1;
}
#endif

獲取分配器的信息,主要在使用jemalloc前提下使用球拦,獲取jemalloc分配的信息靠闭,詳細信息可在http://jemalloc.net/jemalloc.3.html查閱

size_t zmalloc_get_smap_bytes_by_field(char *field, long pid)

#if defined(HAVE_PROC_SMAPS)
size_t zmalloc_get_smap_bytes_by_field(char *field, long pid) {
    char line[1024];
    size_t bytes = 0;
    int flen = strlen(field);
    FILE *fp;

    if (pid == -1) {
        // /proc/pid/smaps反應(yīng)了運行時的進程的內(nèi)存影響,系統(tǒng)的運行時庫(so)坎炼,堆愧膀,棧信息均可在其中看到。
        fp = fopen("/proc/self/smaps","r");
    } else {
        char filename[128];
        snprintf(filename,sizeof(filename),"/proc/%ld/smaps",pid);
        fp = fopen(filename,"r");
    }

    if (!fp) return 0;
    while(fgets(line,sizeof(line),fp) != NULL) {
        if (strncmp(line,field,flen) == 0) {
            char *p = strchr(line,'k');
            if (p) {
                *p = '\0';
                bytes += strtol(line+flen,NULL,10) * 1024;
            }
        }
    }
    fclose(fp);
    return bytes;
}
#else
size_t zmalloc_get_smap_bytes_by_field(char *field, long pid) {
    ((void) field);
    ((void) pid);
    return 0;
}
#endif

獲取/proc/pid/smaps中某一個field的字節(jié)大小

size_t zmalloc_get_private_dirty(long pid)

size_t zmalloc_get_private_dirty(long pid) {
    return zmalloc_get_smap_bytes_by_field("Private_Dirty:",pid);
}

獲取Rss中已改寫的私有頁面頁面大小

size_t zmalloc_get_memory_size(void)

size_t zmalloc_get_memory_size(void) {
#if defined(__unix__) || defined(__unix) || defined(unix) || \
    (defined(__APPLE__) && defined(__MACH__))
#if defined(CTL_HW) && (defined(HW_MEMSIZE) || defined(HW_PHYSMEM64))
    int mib[2];
    mib[0] = CTL_HW;
#if defined(HW_MEMSIZE)
    mib[1] = HW_MEMSIZE;            /* OSX. --------------------- */
#elif defined(HW_PHYSMEM64)
    mib[1] = HW_PHYSMEM64;          /* NetBSD, OpenBSD. --------- */
#endif
    int64_t size = 0;               /* 64-bit */
    size_t len = sizeof(size);
    if (sysctl( mib, 2, &size, &len, NULL, 0) == 0)
        return (size_t)size;
    return 0L;          /* Failed? */

#elif defined(_SC_PHYS_PAGES) && defined(_SC_PAGESIZE)
    /* FreeBSD, Linux, OpenBSD, and Solaris. -------------------- */
    return (size_t)sysconf(_SC_PHYS_PAGES) * (size_t)sysconf(_SC_PAGESIZE);

#elif defined(CTL_HW) && (defined(HW_PHYSMEM) || defined(HW_REALMEM))
    /* DragonFly BSD, FreeBSD, NetBSD, OpenBSD, and OSX. -------- */
    int mib[2];
    mib[0] = CTL_HW;
#if defined(HW_REALMEM)
    mib[1] = HW_REALMEM;        /* FreeBSD. ----------------- */
#elif defined(HW_PHYSMEM)
    mib[1] = HW_PHYSMEM;        /* Others. ------------------ */
#endif
    unsigned int size = 0;      /* 32-bit */
    size_t len = sizeof(size);
    if (sysctl(mib, 2, &size, &len, NULL, 0) == 0)
        return (size_t)size;
    return 0L;          /* Failed? */
#else
    return 0L;          /* Unknown method to get the data. */
#endif
#else
    return 0L;          /* Unknown OS. */
#endif
}

獲取物理內(nèi)存的字節(jié)數(shù)

總結(jié)

看了redis內(nèi)存分配的源碼后点弯,其實沒有相信中的那么難以理解扇调,或許只是心理上的作用,當然也說明redis源碼寫得真的是好抢肛,讓我這種渣渣都能輕而易舉的看懂狼钮,并且注釋也很少碳柱,這里的函數(shù)幾乎都是對glibc的malloc中的函數(shù)進行了一層包裝,并且維護了一個叫做used_memory的全局變量熬芜,并且每一次對全局變量的操作都是原子操作莲镣。也對一些常用的函數(shù)進行了封裝,例如:獲取rss的大小涎拉,獲取/proc/pid/smaps文件中某一field占用字節(jié)數(shù)的大小瑞侮,獲取物理內(nèi)存字節(jié)數(shù)等等,總的來說鼓拧,收益匪淺半火,沒想到內(nèi)存操作可以做到這樣簡單。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末季俩,一起剝皮案震驚了整個濱河市钮糖,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌酌住,老刑警劉巖店归,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異酪我,居然都是意外死亡消痛,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進店門都哭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來秩伞,“玉大人,你說我怎么就攤上這事欺矫〕砬福” “怎么了?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵汇陆,是天一觀的道長怒炸。 經(jīng)常有香客問我,道長毡代,這世上最難降的妖魔是什么阅羹? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮教寂,結(jié)果婚禮上捏鱼,老公的妹妹穿的比我還像新娘。我一直安慰自己酪耕,他們只是感情好导梆,可當我...
    茶點故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般看尼。 火紅的嫁衣襯著肌膚如雪递鹉。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天藏斩,我揣著相機與錄音躏结,去河邊找鬼。 笑死狰域,一個胖子當著我的面吹牛媳拴,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播兆览,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼屈溉,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了抬探?” 一聲冷哼從身側(cè)響起语婴,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎驶睦,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體匿醒,經(jīng)...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡场航,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了廉羔。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片溉痢。...
    茶點故事閱讀 40,503評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖憋他,靈堂內(nèi)的尸體忽然破棺而出孩饼,到底是詐尸還是另有隱情,我是刑警寧澤竹挡,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布镀娶,位于F島的核電站,受9級特大地震影響揪罕,放射性物質(zhì)發(fā)生泄漏梯码。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一好啰、第九天 我趴在偏房一處隱蔽的房頂上張望轩娶。 院中可真熱鬧,春花似錦框往、人聲如沸鳄抒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽许溅。三九已至瓤鼻,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間闹司,已是汗流浹背娱仔。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留游桩,地道東北人牲迫。 一個月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像借卧,于是被迫代替她去往敵國和親盹憎。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,512評論 2 359

推薦閱讀更多精彩內(nèi)容