功能函数总览
在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)); // 可自定义设置内存溢出的处理方法 size_t zmalloc_used_memory(void); // 获取已使用的内存大小
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),清除空间。