天天看点

zmalloc内存管理(1)(Redis源码学习)zmalloc内存管理(1)(Redis源码学习)

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中会是下面这个值,

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模块时,

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.csdn.net/guodongxiaren/article/details/44747719

本人才疏学浅,理解水平有限,如果有错误及不当之处,请批评指正。

如果对您有一点点帮助,请帮忙转发和关注工作号:hongmaolinux,感激不尽_!

zmalloc内存管理(1)(Redis源码学习)zmalloc内存管理(1)(Redis源码学习)