Redis源码解析:zmalloc--内存操作的小小封装

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

临时插入这一块内容,是关于内存分配释放函数的,也就是malloc/calloc/realloc/free这四个,毕竟在很多代码中都有用到。


在redis3.2.6中,对内存分配函数进行了封装,代码主要在zmalloc.h/.c文件里面,有以下几个特性:
1. 支持多种内存处理函数
2. 支持自定义错误处理函数
3. 可获取已分配内存总量

首先说说支持多种内存处理函数:
在3.2.6版本中,支持了tcmalloc、jemalloc和glibc-malloc,需要在编译时通过编译选项指定:

# Default allocator
ifeq ($(uname_S),Linux)
    MALLOC=jemalloc
else
    MALLOC=libc
endif

# Backwards compatibility for selecting an allocator
ifeq ($(USE_TCMALLOC),yes)
    MALLOC=tcmalloc
endif

ifeq ($(USE_TCMALLOC_MINIMAL),yes)
    MALLOC=tcmalloc_minimal
endif

ifeq ($(USE_JEMALLOC),yes)
    MALLOC=jemalloc
endif

ifeq ($(USE_JEMALLOC),no)
    MALLOC=libc
endif

在代码中,则直接根据宏定义进行替换了:

#if defined(USE_TCMALLOC)
#define malloc(size) tc_malloc(size)
#define calloc(count,size) tc_calloc(count,size)
#define realloc(ptr,size) tc_realloc(ptr,size)
#define free(ptr) tc_free(ptr)
#elif defined(USE_JEMALLOC)
#define malloc(size) je_malloc(size)
#define calloc(count,size) je_calloc(count,size)
#define realloc(ptr,size) je_realloc(ptr,size)
#define free(ptr) je_free(ptr)
#endif

如此,实现了内存操作函数的可配置化。

其次,也支持错误处理函数的自定义,具体表现在允许调用函数zmalloc_set_oom_handler传入函数指针,原型如下:

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

在每个内存分配函数中,当分配出现问题的时候,都可以调用到,譬如:

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
}

再次,可以动态获取到当前已经使用的内存总量,这是全局性的,通过static size_t used_memory存储。Redis为操作该变量专门添加了一堆保证原子性的宏:

#if defined(__ATOMIC_RELAXED)
#define update_zmalloc_stat_add(__n) __atomic_add_fetch(&used_memory, (__n), __ATOMIC_RELAXED)
#define update_zmalloc_stat_sub(__n) __atomic_sub_fetch(&used_memory, (__n), __ATOMIC_RELAXED)
#elif defined(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))
#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)

#define update_zmalloc_stat_sub(__n) do { \
    pthread_mutex_lock(&used_memory_mutex); \
    used_memory -= (__n); \
    pthread_mutex_unlock(&used_memory_mutex); \
} while(0)

另外,还可以选择放弃线程安全模式,直接对这个变量进行操作,譬如:

#define update_zmalloc_stat_alloc(__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_add(_n); \ //进行线程安全的操作
    } else { \
        used_memory += _n; \ //直接对used_memory进行操作
    } \
} while(0)

总的来说,zmalloc.h/.c只是redis对于内存操作有一个小封装,实现了内存操作函数的统一入口,没有太多可说的东西,估计这也是《redis的设计与实现》一书没有详说的原因吧。

转载于:https://my.oschina.net/dodomouse/blog/1826255

你可能感兴趣的:(Redis源码解析:zmalloc--内存操作的小小封装)