zmalloc内存管理(1)(Redis源码学习)
在学习sds.c源码时,sdsnewlen函数中开始调用zmalloc相关库函数:
sds sdsnewlen(const void *init, size_t initlen) {
... //省略部分代码
// 根据是否有初始化内容,选择适当的内存分配方式
// T = O(N)
if (init) {
// zmalloc 不初始化所分配的内存
sh = zmalloc(sizeof(struct sdshdr)+initlen+1); //8 + 3 + 1
} else {
// zcalloc 将分配的内存全部初始化为 0
sh = zcalloc(sizeof(struct sdshdr)+initlen+1);
}
... //省略部分代码
对于这种基础底层的库,相信后续其它模块也都用的,故花时间学习研究并总结加深印象。
1. 主要函数
void *zmalloc(size_t size);
void *zcalloc(size_t size);
void *zrealloc(void *ptr, size_t size);
void zfree(void *ptr);
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);
size_t zmalloc_get_rss(void);
size_t zmalloc_get_private_dirty(void);
void zlibc_free(void *ptr);
本文主要讲解其中zmalloc,zcalloc,zfree函数,其它函数后续遇到继续学习并总结。
2. 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));
return ptr;
#else
*((size_t*)ptr) = size;
update_zmalloc_stat_alloc(size+PREFIX_SIZE);
return (char*)ptr+PREFIX_SIZE;
#endif
}
参数size是要分配的空间大小,见文章开头为12。但该函数将我们需要分配的空间大小改为 size+PREFIX_SIZE
。PREFIX_SIZE是根据平台的不同和HAVE_MALLOC_SIZE宏定义控制的。关于HAVE_MALLOC_SIZE及相关宏定义,后续文章会专门介绍这一部分,涉及到不同系统环境。
如果malloc()函数调用失败,就会调用zmalloc_oom_handler()函数来打印异常,并且会有调用abort()终止。zmalloc_oom_handler其实是一个函数指针,真正调用的函数是zmalloc_default_oom(),size作为参数传入。
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();
}
static void (*zmalloc_oom_handler)(size_t) = zmalloc_default_oom;
我在RHEL6.9上进行测试上述代码时,由于最开始源码编译安装redis时,直接使用make编译时报错,所以根据其下README文件中提示用的是make MALLOC=libc
,这个make编译会对此处的代码产生影响,我编译运行sds模块时,则上面zmalloc函数就会直接走到#else
部分。
PREFIX_SIZE在我使用的RHEL6.9中会是下面这个值,
#define PREFIX_SIZE (sizeof(size_t))
PREFIX_SIZE其实就是字长,64位服务器则为8个字节,32位机器就是4字节,在malloc区域之后,则*((size_t*)ptr) = size;
会将存储区头8个字节存放函数保存SIZE大小,用以记录分配内存的长度,下面简图示意:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F2p13vwD-1625733324121)(P1.jpg)]
在update_zmalloc_stat_alloc()宏定义函数中更新used_memory这个静态变量的值,update_zmalloc_stat_alloc()源码如下:
#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; \
} \
} while(0)
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,该变量累加了每次新分配的内存大小。zmalloc函数在最后返回了一个偏移的指针,指向了当前分配内存的数据体部分。
说明:
我在sds.c代码学习时,只是将sds代码模块h和c文件直接拿出来进行编译,并没有使用直接启动redis进行代码调试学习。所以在我单独调试验证sds模块时,
static int zmalloc_thread_safe = 0;
zmalloc_thread_safe就会一直是0, 就会直接执行used_memory += _n;
这一句。
但是,在redis代码main函数入口没几行,就调用了 zmalloc_enable_thread_safeness 函数,该函数内容如下:
void zmalloc_enable_thread_safeness(void) {
zmalloc_thread_safe = 1;
}
所以说如果直接启动Redis程序,则zmalloc_thread_safe值为1,会默认走if分支,这一点要注意。
小知识:
在遇到宏时,想要看看宏参数传入以后的样子,则在gcc编译时将-g改为-g3,然后用gdb调试,就可以看到宏参数代入以后的函数样子。
3. zfree函数及调用
zmalloc分配内测调用C语言的malloc函数,那自然会有zfree调用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
}
zfree 函数首先判断,如果没有定义 HAVE_MALLOC_SIZE,就定义了变量 realptr 和 oldsize ,然后找到 zmalloc 中分配的内测起始地址,需要减去 PREFIX_SIZE,参考上面图片中。然后对应的进行调用 update_zmalloc_stat_free 函数释放分配的内存空间。还要注意上面 void 和 size_t 的类型转换以及从指针中取值。oldsize 的值是存储部分的长度,直接从 used_memory 减去 PREFIX_SIZE 与 实际存储部分的长度之和。如果一时不好理解,请按照上面图片画图反推就能明白。
对应的 update_zmalloc_stat_free 代码如下所示,操作均与 malloc 上面调用的 alloc 宏相反。
#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)
4. 小结
zmalloc及zfree相关内测操作函数,简单总结如上,参考网络文章如下链接:
https://www.cnblogs.com/daryl-blog/p/11003127.html
https://www.cnblogs.com/lizhimin123/p/9964205.html
https://blog.youkuaiyun.com/guodongxiaren/article/details/44747719
本人才疏学浅,理解水平有限,如果有错误及不当之处,请批评指正。
如果对您有一点点帮助,请帮忙转发和关注工作号:hongmaolinux,感激不尽_!