Redis源码剖析--内存管理zmalloc

功能函数总览

在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释放内存空间
void zmalloc_set_oom_handler(void (*oom_handler)(size_t)); // 可自定义设置内存溢出的处理方法
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

Redis的内存申请函数zmalloc本质就是调用了系统的malloc函数,然后对其进行了适当的封装,加上了异常处理函数和内存统计。在zmalloc函数中,实际可能会每次多申请一个 PREFIX_SIZE的空间。从如下的代码中看出,如果定义了宏HAVE_MALLOC_SIZE,那么 PREFIX_SIZE的长度为0。其他的情况下,都会多分配额外的PREFIX_SIZE内存空间来储存内存空间大小。其源代码如下:

// 定义了HAVE_MALLOC_SIZE宏,不会额外申请储存空间大小的内存
#ifdef HAVE_MALLOC_SIZE
#define PREFIX_SIZE (0)
#else
// 申请额外空间储存空间大小
#if defined(__sun) || defined(__sparc) || defined(__sparc__)
#define PREFIX_SIZE (sizeof(long long))
#else
#define PREFIX_SIZE (sizeof(size_t))
#endif
#endif
void *zmalloc(size_t size) 
{
    // 调用malloc函数进行内存申请
    // 多申请的PREFIX_SIZE大小的内存用于记录该段内存的大小
    void *ptr = malloc(size+PREFIX_SIZE);

    // 如果ptr为NULL,则调用异常处理函数
    if (!ptr) zmalloc_oom_handler(size);

    // 更新use_memory的大小
#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
}

这么做的原因是因为: tcmalloc 和 Mac平台下的 malloc 函数族提供了计算已分配空间大小的函数(分别是tc_malloc_size和malloc_size),所以就不需要单独分配一段空间记录大小了。而针对linux和sun平台则要记录分配空间大小。对于linux,使用sizeof(size_t)定长字段记录;对于sun os,使用sizeof(long long)定长字段记录。因此当宏HAVE_MALLOC_SIZE没有被定义的时候,就需要在多分配出的空间内记录下当前申请的内存空间的大小。

对于宏HAVE_MALLOC_SIZE的定义如下(此处仅给出tcmalloc部分):

#if defined(USE_TCMALLOC)
#define ZMALLOC_LIB ("tcmalloc-" __xstr(TC_VERSION_MAJOR) "." __xstr(TC_VERSION_MINOR))
#include <google/tcmalloc.h>
#if (TC_VERSION_MAJOR == 1 && TC_VERSION_MINOR >= 6) || (TC_VERSION_MAJOR > 1)
// 安装tcmalloc时,通过宏使用计算分配空间大小的函数tc_malloc_size()
#define HAVE_MALLOC_SIZE 1
#define zmalloc_size(p) tc_malloc_size(p)
#else
#error "Newer version of tcmalloc required"
#endif

update_zmalloc_stat_alloc

update_zmalloc_stat_alloc 是一个宏,因为sizeof(long) == 8 [64位系统中],所以其实第一个if的代码等价于if(_n&7) _n += 8 - (_n&7); 这段代码就是判断分配的内存空间的大小是不是8的倍数。如果内存大小不是8的倍数,就加上相应的偏移量使之变成8的倍数。_n&7 在功能上等价于 _n%8,不过位操作的效率显然更高。第二个if主要判断当前是否处于线程安全的情况下。如果处于线程安全的情况下,就使用update_zmalloc_stat_add宏来更改全局变量used_memory。否则的话就直接加上n。

#define update_zmalloc_stat_alloc(__n) do { \
    size_t _n = (__n); \
    // 将_n调整为sizeof(long)的整数倍
    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)

update_zmalloc_stat_add()的定义如下:

// __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)

zfree

有分配就有内存回收,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
    realptr = (char*)ptr-PREFIX_SIZE;
    oldsize = *((size_t*)realptr);
    update_zmalloc_stat_free(oldsize+PREFIX_SIZE);
    free(realptr);
#endif
}

上面的代码可以看出,根据用的库不相同,回收的时候也采用了不同的方法。

可以发现如果使用的libc库,则需要将ptr指针向前偏移PREFIX_SIZE个字节的长度,回退到最初malloc返回的地址,然后通过类型转换再取指针所指向的值。通过zmalloc()函数的分析,可知这里存储着我们最初需要分配的内存大小(zmalloc中的size),这里赋值给oldsize。update_zmalloc_stat_free()也是一个宏函数,和zmalloc中update_zmalloc_stat_alloc()大致相同,唯一不同之处是前者在给变量used_memory减去分配的空间,而后者是加上该空间大小。
最后free(realptr),清除空间。

 

在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释放内存空间
void zmalloc_set_oom_handler(void (*oom_handler)(size_t)); // 可自定义设置内存溢出的处理方法
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

Redis的内存申请函数zmalloc本质就是调用了系统的malloc函数,然后对其进行了适当的封装,加上了异常处理函数和内存统计。在zmalloc函数中,实际可能会每次多申请一个 PREFIX_SIZE的空间。从如下的代码中看出,如果定义了宏HAVE_MALLOC_SIZE,那么 PREFIX_SIZE的长度为0。其他的情况下,都会多分配额外的PREFIX_SIZE内存空间来储存内存空间大小。其源代码如下:

// 定义了HAVE_MALLOC_SIZE宏,不会额外申请储存空间大小的内存
#ifdef HAVE_MALLOC_SIZE
#define PREFIX_SIZE (0)
#else
// 申请额外空间储存空间大小
#if defined(__sun) || defined(__sparc) || defined(__sparc__)
#define PREFIX_SIZE (sizeof(long long))
#else
#define PREFIX_SIZE (sizeof(size_t))
#endif
#endif
void *zmalloc(size_t size) 
{
    // 调用malloc函数进行内存申请
    // 多申请的PREFIX_SIZE大小的内存用于记录该段内存的大小
    void *ptr = malloc(size+PREFIX_SIZE);

    // 如果ptr为NULL,则调用异常处理函数
    if (!ptr) zmalloc_oom_handler(size);

    // 更新use_memory的大小
#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
}

这么做的原因是因为: tcmalloc 和 Mac平台下的 malloc 函数族提供了计算已分配空间大小的函数(分别是tc_malloc_size和malloc_size),所以就不需要单独分配一段空间记录大小了。而针对linux和sun平台则要记录分配空间大小。对于linux,使用sizeof(size_t)定长字段记录;对于sun os,使用sizeof(long long)定长字段记录。因此当宏HAVE_MALLOC_SIZE没有被定义的时候,就需要在多分配出的空间内记录下当前申请的内存空间的大小。

对于宏HAVE_MALLOC_SIZE的定义如下(此处仅给出tcmalloc部分):

#if defined(USE_TCMALLOC)
#define ZMALLOC_LIB ("tcmalloc-" __xstr(TC_VERSION_MAJOR) "." __xstr(TC_VERSION_MINOR))
#include <google/tcmalloc.h>
#if (TC_VERSION_MAJOR == 1 && TC_VERSION_MINOR >= 6) || (TC_VERSION_MAJOR > 1)
// 安装tcmalloc时,通过宏使用计算分配空间大小的函数tc_malloc_size()
#define HAVE_MALLOC_SIZE 1
#define zmalloc_size(p) tc_malloc_size(p)
#else
#error "Newer version of tcmalloc required"
#endif

update_zmalloc_stat_alloc

update_zmalloc_stat_alloc 是一个宏,因为sizeof(long) == 8 [64位系统中],所以其实第一个if的代码等价于if(_n&7) _n += 8 - (_n&7); 这段代码就是判断分配的内存空间的大小是不是8的倍数。如果内存大小不是8的倍数,就加上相应的偏移量使之变成8的倍数。_n&7 在功能上等价于 _n%8,不过位操作的效率显然更高。第二个if主要判断当前是否处于线程安全的情况下。如果处于线程安全的情况下,就使用update_zmalloc_stat_add宏来更改全局变量used_memory。否则的话就直接加上n。

#define update_zmalloc_stat_alloc(__n) do { \
    size_t _n = (__n); \
    // 将_n调整为sizeof(long)的整数倍
    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)

update_zmalloc_stat_add()的定义如下:

// __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)

zfree

有分配就有内存回收,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
    realptr = (char*)ptr-PREFIX_SIZE;
    oldsize = *((size_t*)realptr);
    update_zmalloc_stat_free(oldsize+PREFIX_SIZE);
    free(realptr);
#endif
}

上面的代码可以看出,根据用的库不相同,回收的时候也采用了不同的方法。

可以发现如果使用的libc库,则需要将ptr指针向前偏移PREFIX_SIZE个字节的长度,回退到最初malloc返回的地址,然后通过类型转换再取指针所指向的值。通过zmalloc()函数的分析,可知这里存储着我们最初需要分配的内存大小(zmalloc中的size),这里赋值给oldsize。update_zmalloc_stat_free()也是一个宏函数,和zmalloc中update_zmalloc_stat_alloc()大致相同,唯一不同之处是前者在给变量used_memory减去分配的空间,而后者是加上该空间大小。
最后free(realptr),清除空间。

猜你喜欢

转载自www.cnblogs.com/lizhimin123/p/9964205.html