美文网首页
Redis源码阅读之zmalloc内存分配

Redis源码阅读之zmalloc内存分配

作者: miaoiao | 来源:发表于2019-03-03 18:41 被阅读0次

    本文内容绝大部分来自:https://blog.csdn.net/guodongxiaren/article/details/44747719,果冻虾仁,中间根据自己的知识盲点补充了一些资料,具体可查看参考资料。

    zmalloc是redis自己实现的内存分配,是对linux中malloc,free,relloc这3个函数的一个封装。

    一. zmalloc定义的函数

    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);/* 释放空间方法,并更新used_memory的值 */
    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);/* 原始系统free释放方法 */
    
    其中最重要的函数是zmalloc()、zfree()、zcalloc()、zrelloc()、zstrdup()。

    在介绍函数之前,首先说一说#if,#if define,#if defined.

    #if的使用说明
    #if的后面接的是表达式
    #if (MAX==10)||(MAX==20) code... #endif
    它的作用是:如果(MAX==10)||(MAX==20)成立,那么编译器就会把其中的#if 与 #endif之间的代码编译进去>(注意:是编译进去,不是执行!!)
    
    #if defined的使用
    #if后面接的是一个宏。
    #if defined (x) ...code... #endif
    这个#if defined它不管里面的“x”的逻辑是“真”还是“假”它只管这个程序的前面的宏定义里面有没有定义“x”这个>宏,如果定义了x这个宏,那么,编译器会编译中间的…code…否则不直接忽视中间的…code…代码。
    另外 #if defined(x)也可以取反,也就用 #if !defined(x)
    
    #ifdef的使用
    #ifdef的使用和#if defined()的用法一致
    #ifndef又和#if !defined()的用法一致。
    

    最后强调两点:

    • 第一:这几个宏定义只是决定代码块是否被编译!
    • 第二:别忘了#endif

    下面开始详细介绍具体函数

    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));
       return ptr;
    #else
       *((size_t*)ptr) = size;
       update_zmalloc_stat_alloc(size+PREFIX_SIZE);
       return (char*)ptr+PREFIX_SIZE;
    #endif
    }
    

    zmalloc的函数很简短,size是我们需要分配的内存大小,但是在调用zmalloc的时候,实际上还会多分配一个PREFIX_SIZE。PREFIX_SIZE是以个条件编译的宏,根据不同的操作系统有不同的大小,这个具体可以查看参考资料[4]。在Linux系统中PREFIX_SIZE = sizeof(size_t)。如果malloc失败,就进入zmalloc_oom_handler函数,输出OOM的报错信息并终止程序。后面代码可以看到多分配一个PREFIX_SIZE的目的是用于储存size的值。

    接下来是宏的条件编译,

    • 第一行就是在已分配空间的第一个字长(前8个字节)处存储需要分配的字节大小(size)。
    • 第二行调用了update_zmalloc_stat_alloc()【宏函数】,它的功能是更新全局变量used_memory(已分配内存的大小)的值(源码解读见下一节)。
    • 第三行返回的(char *)ptr+PREFIX_SIZE。就是将已分配内存的起始地址向右偏移PREFIX_SIZE * sizeof(char)的长度(即8个字节),此时得到的新指针指向的内存空间的大小就等于size了。

    接下来分析 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)
    

    这个宏函数最外圈有一个do{...}while(0)循环看似毫无意义,实际上大有深意,具体作用可以搜索宏定义时的do while,主要作用是保证代码安全性。
    因为 sizeof(long) = 8 【64位系统中】,所以上面的第一个if语句,可以等价于以下代码:

    • if(_n&7) _n += 8 - (_n&7);
      
    • 这段代码就是判断分配的内存空间的大小是不是8的倍数。如果内存大小不是8的倍数,就加上相应的偏移量使之变成8的倍数。_n&7 在功能上等价于 _n%8,不过位操作的效率显然更高。
    • malloc()本身能够保证所分配的内存是8字节对齐的:如果你要分配的内存不是8的倍数,那么malloc就会多分配一点,来凑成8的倍数。所以update_zmalloc_stat_alloc函数(或者说zmalloc()相对malloc()而言)真正要实现的功能并不是进行8字节对齐(malloc已经保证了),它的真正目的是使变量used_memory精确的维护实际已分配内存的大小.
    • 第2个if的条件是一个整型变量zmalloc_thread_safe。顾名思义,它的值表示操作是否是线程安全的,如果不是线程安全的(else),就给变量used_memory加上n。used_memory是zmalloc.c文件中定义的全局静态变量,表示已分配内存的大小。如果是内存安全的就使用update_zmalloc_stat_add来给used_memory加上n。
    • update_zmalloc_stat_add也是一个宏函数(Redis效率之高,速度之快,这些宏函数可谓功不可没)。它也是一个条件编译的宏,依据不同的宏有不同的定义,这里我们来看一下#else后面的定义的源码【zmalloc.c有多处条件编译的宏,为了把精力都集中在内存管理的实现算法上,这里我只关注Linux平台下使用glibc的malloc的情况】。

    参考资料

    [1] 《redis设计与实现》, 黄建宏
    [2] https://blog.csdn.net/Androidlushangderen/article/details/40659331, Android路上的人, 2014-10-31
    [3] https://www.cnblogs.com/wuchanming/p/4057630.html, Jessica要努力了, 2014-10-28
    [4] https://blog.csdn.net/ylo523/article/details/38756047 他并不是原作者,就不贴名字了......
    [5] https://blog.csdn.net/guodongxiaren/article/details/44747719, 果冻虾仁, 2015-3-31

    相关文章

      网友评论

          本文标题:Redis源码阅读之zmalloc内存分配

          本文链接:https://www.haomeiwen.com/subject/wdfduqtx.html