开始阅读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
读源码,真的给力