Redis源码剖析--内存分配

Redis在内存分配方面,仅仅是对系统的malloc/free做了一层简单的封装,然后加上了异常处理功能和内存统计功能。其实现主要在zmalloc.c和zmalloc.h文件中。

功能函数总览

在zmalloc.h中,定义了Redis内存分配的主要功能函数,这些函数基本上实现了Redis内存申请,释放和统计等功能,其函数声明如下:

void *zmalloc(size_t size); // 调用zmalloc函数,申请size大小的空间
void *zcalloc(size_t size); // 调用系统函数calloc申请内存空间
void *zrealloc(void *ptr, size_t size); // 原内存重新调整为size空间的大小
void zfree(void *ptr);  // 调用zfree释放内存空间
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); // 获取RSS信息(Resident Set Size)
size_t zmalloc_get_private_dirty(void); // 获得实际内存大小
size_t zmalloc_get_smap_bytes_by_field(char *field); // 获取/proc/self/smaps字段的字节数
size_t zmalloc_get_memory_size(void); // 获取物理内存大小
void zlibc_free(void *ptr); // 原始系统free释放方法

另外,我们还要注意到zmalloc.c中的几个变量和概念,

static size_t used_memory = 0;  // 已使用内存的大小
static int zmalloc_thread_safe = 0; // 线程安全模式状态
pthread_mutex_t used_memory_mutex = PTHREAD_MUTEX_INITIALIZER; // 为此服务器

接下来,我分几个章节来一一剖析zmalloc.c中的函数实现。
内存申请函数zmalloc
Redis的内存申请函数zmalloc本质就是调用了系统的malloc函数,然后对其进行了适当的封装,加上了异常处理函数和内存统计。其源代码如下:

void *zmalloc(size_t size) {
    // 调用malloc函数进行内存申请
    // 多申请的PREFIX_SIZE大小的内存用于记录该段内存的大小
    void *ptr = malloc(size+PREFIX_SIZE);
    // 如果ptr为NULL,则调用异常处理函数
    if (!ptr) zmalloc_oom_handler(size);
    // 以下是内存统计
    *((size_t*)ptr) = size;
    update_zmalloc_stat_alloc(size+PREFIX_SIZE); // 更新used_memory的值
    return (char*)ptr+PREFIX_SIZE;
}

上述代码中的PREFIX_SIZE解释:由于malloc函数申请的内存不会标识内存块的大小,而我们需要统计内存大小,所以需要在多申请PREFIX_SIZE大小的内存,用于存放该大小。
其中,异常处理函数如下:

static void zmalloc_default_oom(size_t size) {
    fprintf(stderr, "zmalloc: Out of memory trying to allocate %zu bytes\n", // 打印输出日志
        size);
    fflush(stderr);
    abort(); // 中断退出
}

更新used_memory值得函数以宏定义给出,其代码和注释如下:

#define update_zmalloc_stat_alloc(__n) do { \
    size_t _n = (__n); \
    if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1)); \   // 将_n调整为sizeof(long)的整数倍
    if (zmalloc_thread_safe) { \ // 如果启用了线程安全模式
        update_zmalloc_stat_add(_n); \   // 调用原子操作加(+)来更新已用内存
    } else { \
        used_memory += _n; \   // 不考虑线程安全,则直接更新已用内存
    } \
} while(0)

在上述函数中,又用到了原子加操作,其代码和注释如下:

// __atomic_add_fetch是C++11特性中提供的原子加操作
#if defined(__ATOMIC_RELAXED)
#define update_zmalloc_stat_add(__n) __atomic_add_fetch(&used_memory, (__n), __ATOMIC_RELAXED)
// 如果不支持C++11,则调用GCC提供的原子加操作
#elif defined(HAVE_ATOMIC)
#define update_zmalloc_stat_add(__n) __sync_add_and_fetch(&used_memory, (__n))
// 如果上述都没有,则只能采用加锁操作
#else
#define update_zmalloc_stat_add(__n) do { \
    pthread_mutex_lock(&used_memory_mutex); \
    used_memory += (__n); \
    pthread_mutex_unlock(&used_memory_mutex); \
} while(0)

内存申请函数zcalloc

与malloc一样,zcalloc调用的是系统给的calloc()来申请内存。

void *zcalloc(size_t size) {
    void *ptr = calloc(1, size+PREFIX_SIZE);
    // 异常处理函数
    if (!ptr) zmalloc_oom_handler(size);
    // 内存统计函数
    *((size_t*)ptr) = size;
    update_zmalloc_stat_alloc(size+PREFIX_SIZE);
    return (char*)ptr+PREFIX_SIZE;
}

内存调整函数zrecalloc
Redis定义的zrecalloc用于调整已申请内存的大小,其本质也是直接调用系统函数recalloc()

void *zrealloc(void *ptr, size_t size) {
    size_t oldsize;
    void *newptr;
    // 为空直接退出
    if (ptr == NULL) return zmalloc(size);
    // 找到内存真正的起始位置
    realptr = (char*)ptr-PREFIX_SIZE;
    oldsize = *((size_t*)realptr);
    // 调用recalloc函数
    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;
}

内存释放函数

与内存申请函数调用malloc一样,内存释放也是调用系统的free()函数来实现内存释放

void zfree(void *ptr) {
    if (ptr == NULL) return;  // 为空直接返回
    realptr = (char*)ptr-PREFIX_SIZE; // 找到该段内存真正的起始位置
    oldsize = *((size_t*)realptr);
    update_zmalloc_stat_free(oldsize+PREFIX_SIZE);// 更新use_memory函数
    free(realptr); // 调用系统的内存释放函数
}

其中,内存状态统计函数的代码实现如下:

#define update_zmalloc_stat_free(__n) do { \
    size_t _n = (__n); \ 
    if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1)); \  // 将内存大小调整为sizeof(long)的整数倍
    if (zmalloc_thread_safe) { \  // 如果开启了线程安全模式
        update_zmalloc_stat_sub(_n); \ // 更新use_memory值(与上述的update_zmalloc_stat_add这里就不赘述了)
    } else { \
        used_memory -= _n; \ // 没有线程安全则直接减
    } \
} while(0)

讲到这里,Redis基本的内存处理函数已经分析完毕了。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容

  • C语言中内存分配 在任何程序设计环境及语言中,内存管理都十分重要。在目前的计算机系统或嵌入式系统中,内存资源仍然是...
    一生信仰阅读 1,151评论 0 2
  • (JG-2014-08-20)(前半部分经过网上多篇文章对比整理)(后半部分根据ExceptionalCpp、C+...
    JasonGao阅读 5,599评论 2 23
  • 转载地址:http://gnucto.blog.51cto.com/3391516/998509 Redis与Me...
    Ddaidai阅读 21,445评论 0 82
  • (一)万祥酒店:下榻,早餐 (二)景点1:陡坡塘瀑布 (三)景点2:天星桥 (四)黄果树瀑布(大瀑布)
    李家庄阅读 221评论 0 0
  • 我爱旗袍 夏海芹 少有这样的淘宝店铺,打开网页,音乐便如原野的风扑面而来,让身体的每一个毛孔都舒适服帖。这是一家旗...
    清泉石上流的简书阅读 1,265评论 3 7