Redis源码剖析——内存管理

开始阅读Redis3.0版本源码,并以此为剖析对象,因为此版本有注释,并且网上有文章,便于初学者学习参考,了解以前的版本才能更好地学习最新的4.x版本

内存管理


Redis的内存管理仅仅对malloc,,free做了一层封装,并未实现内存池,远没有STL的内存分配器巧妙

内存管理函数

内存管理函数如下:

void *zmalloc(size_t size); //封装malloc
void *zcalloc(size_t size); //封装calloc
void *zrealloc(void *ptr, size_t size); //封装realloc
void zfree(void *ptr);  //封装free
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);  //所给内存和已用内存比率(RSS / allocated-bytes)
size_t zmalloc_get_rss(void);   //获取RSS信息
size_t zmalloc_get_private_dirty(void); /*如果定义了HAVE_PROC_SMAPS,
从/proc/self/smaps获取信息,(?不知道什么信息,有“Private_Dirty:”字段)
如果没有定义,返回0 */
void zlibc_free(void *ptr); //free

1. 内存分配函数zmalloc,zcalloc,zrealloc

1.1 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)); //zmalloc_size获取实际分配的内存
    return ptr;
#else
    *((size_t*)ptr) = size;
    update_zmalloc_stat_alloc(size+PREFIX_SIZE);//更新used_memory
    return (char*)ptr+PREFIX_SIZE;
#endif
}

PREFIX_SIZE为前缀,每申请一块内存就在开头记录它的大小
内存分配失败处理函数如下:

static void (*zmalloc_oom_handler)(size_t) = 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();
}

默认处理报错,然后把程序崩了

如果定义了HAVE_MALLOC_SIZE:

#ifdef HAVE_MALLOC_SIZE
    update_zmalloc_stat_alloc(zmalloc_size(ptr));
    return ptr;
// zmalloc_size如下
/* 如果使用的tc_malloc 调用tc_malloc库函数 #define zmalloc_size(p) tc_malloc_size(p)
   如果使用了je_malloc 调用je_malloc库函数 #define zmalloc_size(p) je_malloc_usable_size(p)
   如果都没有且在IOS系统下 调用malloc_size(p) */

可以总结出zmalloc_size(ptr)获取的是实际分配的内存大小
update_zmalloc_stat_alloc()宏更新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上调到long的整数倍
    if (zmalloc_thread_safe) { \ // 开启了线程安全
        update_zmalloc_stat_add(_n); \
    } else { \
        used_memory += _n; \ //未开启直接加上
    } \
} while(0)

这里要说一下三个重要的全局变量

static size_t used_memory = 0;   //分配的内存总量
static int zmalloc_thread_safe = 0; //线程安全是否开启
pthread_mutex_t used_memory_mutex = PTHREAD_MUTEX_INITIALIZER; //多线程同步互斥锁

update_zmalloc_stat_add(_n)宏

#ifdef HAVE_ATOMIC
#define update_zmalloc_stat_add(__n) __sync_add_and_fetch(&used_memory, (__n))
#define update_zmalloc_stat_sub(__n) __sync_sub_and_fetch(&used_memory, (__n)) //这两个由gcc提供原子操作
#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)   //不支持则使用互斥锁

到这里内存分配解析完毕,简要说下宏定义中大量使用do{}while(0)的原因
Linux内核源码中也大量运用了,刚好看到
原因如下:

// 如果定义如下
#define test(p, n) {do_something(); do_otherthing();}
// 如果有这样的使用场景
if (...)
    test(p, n);
else
    ...;
// 那么编译器替换为
if (...){
    do_something(); 
    do_otherthing();
};
else
    ...;
// else前的分号会引发编译错误
// 采用 
do {
    ...
} while(false)
// 则OK

1.2 zcalloc

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
}

仅仅调用了库函数calloc,其余与zmalloc相同

1.2 zrealloc

重新分配内存更新memory_used值

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); //空指针调用zmalloc
#ifdef HAVE_MALLOC_SIZE
    oldsize = zmalloc_size(ptr); //原来分配的真是内存大小
    newptr = realloc(ptr,size);
    if (!newptr) zmalloc_oom_handler(size);

    update_zmalloc_stat_free(oldsize); // 更新memory_used (-)
    update_zmalloc_stat_alloc(zmalloc_size(newptr)); // 更新memory_used(+)
    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);
    update_zmalloc_stat_alloc(size);
    return (char*)newptr+PREFIX_SIZE;
#endif
}

2. 内存释放函数zfree

完成释放内存更新memory_used两项任务

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
}

小结

Redis的内存分配很简单,并未向STL那样实现内存分配器,内存池。只是简单包装了malloc, free
推荐Source Insight读源码,真的给力

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值