redis封装的内部结构,脑子里要有这幅图,就差不多了。实际分配的内存比size要多。
redis的zmalloc函数
// 已经使用的内存,malloc函数增加,free减少这个值
static size_t used_memory = 0;
// 线程安全,其实没用;redis是单线程模型
static int zmalloc_thread_safe = 0;
// used_memory变量作为临界区,做同步
pthread_mutex_t used_memory_mutex = PTHREAD_MUTEX_INITIALIZER;
// 默认内存溢出handler
static void zmalloc_default_oom(size_t size) {
// 打印日志
fprintf(stderr, "zmalloc: Out of memory trying to allocate %zu bytes\n",
size);
// 还是打印日志,到哪里 TODO
fflush(stderr);
// TODO
abort();
}
static void (*zmalloc_oom_handler)(size_t) = zmalloc_default_oom;
void *zmalloc(size_t size) {
void *ptr = malloc(size+PREFIX_SIZE);
// 如果申请失败,一定是OOM
if (!ptr) zmalloc_oom_handler(size);
#ifdef HAVE_MALLOC_SIZE
// 统计用计数器增增加
update_zmalloc_stat_alloc(zmalloc_size(ptr));
return ptr;
#else
// 使用4个字节记录申请内存字节数
*((size_t*)ptr) = size;
update_zmalloc_stat_alloc(size+PREFIX_SIZE);
// 返回实际内存指针
return (char*)ptr+PREFIX_SIZE;
#endif
1 首先把和多线程相关的去掉,先忽略。
2 HAVE_MALLOC_SIZE是什么意思zmalloc_size 内容是什么?
http://www.petermao.com/redis/78.html 这篇文章 对 HAVE_MALLOC_SIZE 有一行注释,“另外对于 apple系统,可以用malloc_size(redis_malloc_size是对它的封装)取得指针所指向的内存块大小,因此就不需要手动保存大小了。” 据此明了,redis 实现了类似apple的功能,使用申请的内存的前一段PREFIX_SIZE,保存真正申请(可以使用)的内存字节数。
zmalloc_size函数的注释,对于一些不支持zmalloc_size的系统这种情况,redis在每次分配内存的时候使用前几个字节保存实际使用的字节数。
/* Provide zmalloc_size() for systems where this function is not provided by
* malloc itself, given that in that case we store a header with this
* information as the first bytes of every allocation. */
#ifndef HAVE_MALLOC_SIZE
size_t zmalloc_size(void *ptr) {
void *realptr = (char*)ptr-PREFIX_SIZE;
size_t size = *((size_t*)realptr);
/* Assume at least that all the allocations are padded at sizeof(long) by
* the underlying allocator. */
if (size&(sizeof(long)-1)) size += sizeof(long)-(size&(sizeof(long)-1));
return size+PREFIX_SIZE;
}
#endif
3 PREFIX_SIZE 记录一次分配内存,使用内存的字节数,他的大小怎么定义的?
http://blog.csdn.net/jinjinstudy/article/details/18189567
PREFIX_SIZE用来记录malloc已分配到的内存大小
tc_malloc/je_malloc/Mac平台分别采用tc_malloc_size/malloc_size/je_malloc_usable_size(不需要单独开空间计算得到的内存大小,PREFIX_SIZE值置为0)
linux和sun平台分别采用sizeof(size_t)=8字节和sizeof(long long)定长字段记录,所以要记录分配空间的大小
#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
简单来讲,可以把PREFIX_SIZE看作一个size_t的大小(一般是8字节)。它的主要作用是来记录本次分配的内存空间大小。对于不同平台来说,PREFIX_SIZE的值是不同的。
/* Assume at least that all the allocations are padded at sizeof(long) by
* the underlying allocator. */
if (size&(sizeof(long)-1)) size += sizeof(long)-(size&(sizeof(long)-1));
例如 size = 15 ,二进制表示 11111,sizeof(long) - 1 = 7,二进制表示111, 按位与15 & 7,结果是111,不等于0;所以,size +=
8 - 7,结果是16.
5 当使用tcmalloc库/jemalloc库的时候,显式覆盖malloc/calloc/realloc/free的方法
/* Explicitly override malloc/free etc when using tcmalloc. */
#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
6 增加内存和释放内存时used_memory计数器
#if defined(__ATOMIC_RELAXED) // 平台相关define
#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
#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
// 已使用内存 增加delta
#define update_zmalloc_stat_add(__n) do { \
pthread_mutex_lock(&used_memory_mutex); \
used_memory += (__n); \
pthread_mutex_unlock(&used_memory_mutex); \
} while(0) // 只执行一次
// 已使用内存 减少delta
#define update_zmalloc_stat_sub(__n) do { \
pthread_mutex_lock(&used_memory_mutex); \
used_memory -= (__n); \
pthread_mutex_unlock(&used_memory_mutex); \
} while(0)
#endif
#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) { \ // 多线程case使用
update_zmalloc_stat_add(_n); \
} else { \
used_memory += _n; \
} \
} while(0)
#define update_zmalloc_stat_free(__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_sub(_n); \
} else { \
used_memory -= _n; \
} \
} while(0)
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
}
http://blog.csdn.net/firecityplans/article/details/4490124/
void *malloc(unsigned size)//动态申请size个字节的内存空间;功能:在内存的动态存储区中分配一块长度为" size" 字节的连续区域。函数的返回值为该区域的首地址。。(类型说明符*)表示把返回值强制转换为该类型指针。
(void *)calloc(unsigned n,unsigned size)// 用于向系统动态申请n个, 每个占size个字节的内存空间; 并把分配的内存全都初始化为零值。函数的返回值为该区域的首地址
(void *)realloc(void *p,unsigned size)//将指针p所指向的已分配内存区的大小改为size
区别:两者都是动态分配内存。主要的不同是malloc不初始化分配的内存,已分配的内存中可以是任意的值. calloc 初始化已分配的内存为0。次要的不同是calloc返回的是一个数组,而malloc返回的是一个对象。
/**
重新分配内存;外部传入的指针地址是字符串地址;
*/
void *zrealloc(void *ptr, size_t size) {
#ifndef HAVE_MALLOC_SIZE
// ptr - PREFIX_SIZE 的地址
void *realptr;
#endif
size_t oldsize;
void *newptr;
// 如果ptr == null,重新分配一段内存
if (ptr == NULL) return zmalloc(size);
#ifdef HAVE_MALLOC_SIZE
oldsize = zmalloc_size(ptr);
newptr = realloc(ptr,size);
if (!newptr) zmalloc_oom_handler(size);
update_zmalloc_stat_free(oldsize);
update_zmalloc_stat_alloc(zmalloc_size(newptr));
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
}
补说明,realloc函数的说明
realloc(void *__ptr, size_t __size):更改已经配置的内存空间,即更改由malloc()函数分配的内存空间的大小。
如果将分配的内存减少,realloc仅仅是改变索引的信息。
如果是将分配的内存扩大,则有以下情况:
1)如果当前内存段后面有需要的内存空间,则直接扩展这段内存空间,realloc()将返回原指针。
2)如果当前内存段后面的空闲字节不够,那么就使用堆中的第一个能够满足这一要求的内存块,将目前的数据复制到新的位置,并将原来的数据块释放掉,返回新的内存块位置。
3)如果申请失败,将返回NULL,此时,原来的指针仍然有效。
注意:如果调用成功,不管当前内存段后面的空闲空间是否满足要求,都会释放掉原来的指针,重新返回一个指针,虽然返回的指针有可能和原来的指针一样,即不能再次释放掉原来的指针。
9 free函数,系统中除了分配请求大小的内存外,还在该内存块头部保存了该内存块的大小,这样,释放的时候可以通过该大小找到该内存块的起始位置:
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
}
10 函数 获取已使用内存量,有并发的场景,前后加锁。
size_t zmalloc_used_memory(void) {
size_t um;
if (zmalloc_thread_safe) {
#if defined(__ATOMIC_RELAXED) || defined(HAVE_ATOMIC)
um = update_zmalloc_stat_add(0);
#else
pthread_mutex_lock(&used_memory_mutex);
um = used_memory;
pthread_mutex_unlock(&used_memory_mutex);
#endif
}
else {
um = used_memory;
}
return um;
}
参考:
http://blog.csdn.net/guodongxiaren/article/details/44747719
http://blog.csdn.net/guodongxiaren/article/details/44783767