美文网首页redis源码
redis源码1---内存管理(zmalloc)

redis源码1---内存管理(zmalloc)

作者: QaoKi | 来源:发表于2020-04-24 10:52 被阅读0次

    打算学习一下redis源码,结果刚开始看sds就发现一个陌生的词汇,zmalloc,查看zmalloc的实现,发现是对malloc的封装,并且还引出了ptMalloc和tcMalloc等知识,关于malloc库和redis的其他内存管理的知识,后续查看了后再谈

    字长与字节对齐

    首先要了解一个操作系统的基础,字长和字节对齐,字长是指 CPU一次性能读取数据的二进制位数,也就是我们通常所说的32位系统(字长4个字节)、64位系统(字长8个字节)的由来。所谓的字节对齐,简单的介绍可以查看 https://blog.csdn.net/ldw662523/article/details/79623404,总之,当我们向系统申请内存的时候,系统会返回给我们 n倍个字长的字节数,比如我们申请10字节,64位系统下会返回给我们2*8个字节
    long类型变量和指针类型变量占用一字长的字节数,在32为系统下,sizeof(char *) 和 sizeof(long) 是4,在64位下是8,本文接下来内容都用64位,也就是8字节对齐为主

    主要变量和函数

    全局变量

     //定义当前进程已使用的内存总量
    static size_t used_memory = 0;  
    //标识是否线程安全,值为1时线程安全
    static int zmalloc_thread_safe = 0;  
     //如果是线程安全的,修改used_memory 时的互斥锁
    pthread_mutex_t used_memory_mutex = PTHREAD_MUTEX_INITIALIZER; 
    //zmalloc_oom_handler 为函数指针,当内存出错时调用,默认的调用函数是 zmalloc_default_oom
    //oom  是out of memory
    static void (*zmalloc_oom_handler)(size_t) = zmalloc_default_oom;
    

    主要函数

    void *zmalloc(size_t size);  //分配内存空间
    void zcalloc(size_t size);  //分配内存并初始化为0
    void zrealloc(void *ptr,size_t size);  //重新分配内存空间的大小
    void zfree(void *ptr);  //释放zmalloc所分配的内存空间
    char *zstrdup(const char *s);  //字符串复制
    void zlibc_free(void *ptr);  //同free()
    

    //linux的glibc下是sizeof(size_t),64位系统为8字节
    //当zmalloc申请内存时,多分配PREFIX_SIZE 个字节
    PREFIX_SIZE
    //若使用tcmalloc、jemalloc或Mac系统则定义此宏,linux的glibc不定义
    HAVE_MALLOC_SIZE
    

    宏函数

    //分配内存空间后更新used_memory的值
    update_zamlloc_stat_alloc
    //释放内存空间后更新used_memory的值
    update_zamlloc_stat_free
    //线程安全地used_memory增加操作
    update_zamlloc_stat_add
    //线程安全地used_memory减少操作
    update_zamlloc_stat_sub
    

    zmalloc

    void *zmalloc(size_t size) {
        //size是要分配的内存大小,多分配了PREFIX_SIZE个字节,用于存放size的大小
        void *ptr = malloc(size + PREFIX_SIZE);
    
        //如果内存分配失败,调用函数指针zmalloc_oom_handler所指向的函数
        if (!ptr) zmalloc_oom_handler(size);
    #ifdef HAVE_MALLOC_SIZE
        update_zmalloc_stat_alloc(zmalloc_size(ptr));
        return ptr;
    #else
        //前sizeof(size_t) 个字节,存储要分配的内存大小
        *((size_t*)ptr) = size;
        update_zmalloc_stat_alloc(size+PREFIX_SIZE);
        //只返回给用户可用的内存空间
        return (char*)ptr+PREFIX_SIZE;
    #endif
    }
    

    PREFIX_SIZE是一个条件编译的宏,不同的平台有不同的结果,在Linux中其值是sizeof(size_t),所以我们多分配了一个字长(8个字节)的空间,用于存放size的值

    zmalloc_oom_handler指向内存错误处理函数,默认是指向函数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();
    }
    

    linux下不定义宏 HAVE_MALLOC_SIZE,所以走 #else的代码

    //前sizeof(size_t) 个字节,存储要分配的内存大小
    *((size_t*)ptr) = size;
    update_zmalloc_stat_alloc(size+PREFIX_SIZE);
    //只返回给用户可用的内存空间
    return (char*)ptr+PREFIX_SIZE;
    

    前sizeof(size_t),也就是PREFIX_SIZE个字节,存放所申请的内存大小size。我们先看一下

    return (char*)ptr+PREFIX_SIZE;
    

    将指针后移 PREFIX_SIZE 位,只给用户返回可用的内存空间,所以malloc出来的内存被分为了两部分,一部分用来存放size的值,一部分返回给用户。
    然后调用了宏函数update_zmalloc_stat_alloc

    update_zmalloc_stat_alloc

    #define update_zmalloc_stat_alloc(__n) do { \
        size_t _n = (__n); \
        //判断 _n是否是8的倍数
        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)
    

    其中

    if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1)); \
    

    用位运算判断 _n 是否是8的倍数,如果不是,加上所需的数值,使之能被8整除,比如,申请20bytes, 20 & (sizeof (long) - 1) = 20 & 7 = 4,非零,不能整除,即缺 8 - 4 = 4 bytes,于是申请的20 bytes多申请4bytes,24 mod 8 = 0正好

    其实malloc出来的内存,已经是8的倍数,系统自动帮我们完成字节对齐,我们之所以还要判断一次,是为了保证used_memory的值是实际分配的内存大小。比如用户申请20字节,malloc以后,系统分配了24字节,但是我们并不知道分配了24字节,所以我们要像系统一样,去做一次字节对齐,这样能够知道系统具体分配了多少

    接下来判断是否是线程安全的,zmalloc_thread_safe默认为0,也就是走else下面,直接给used_memory 加上值。如果是线程安全的,调用宏函数 update_zmalloc_stat_add

    update_zmalloc_stat_add

    #define update_zmalloc_stat_add(__n) do { \
        pthread_mutex_lock(&used_memory_mutex); \
        used_memory += (__n); \
        pthread_mutex_unlock(&used_memory_mutex); \
    } while(0)
    

    其实就是调用了互斥锁来保证增加used_memory 值的时候是线程安全的,注意到一点是宏函数外层都用 do while(0) 包裹着,其实并不是额外加了什么功能,只是为了保持宏函数实现的时候语意和我们所写的代码一致,防止大括号以及分号的干扰,详见 https://blog.csdn.net/Move_now/article/details/73480195

    zfree

    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
        //将指针前移 PREFIX_SIZE 位
        realptr = (char*)ptr-PREFIX_SIZE;
        //取出当zmalloc时返回给用户的可用的内存空间大小
        oldsize = *((size_t*)realptr);
        update_zmalloc_stat_free(oldsize+PREFIX_SIZE);
        free(realptr);
    #endif
    }
    

    我们还是关注没有定义 HAVE_MALLOC_SIZE 的部分,定义指针realptr,让其指向最初malloc返回的地址,用oldsize 保存用户所申请的内存大小
    update_zmalloc_stat_free 和 update_zmalloc_stat_alloc 一样是宏函数,不同之处是前者在给变量used_memory减去分配的空间,而后者是加上该空间大小

    update_zmalloc_stat_free

    #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)
    

    update_zmalloc_sub与zmalloc()中的update_zmalloc_add相对应,但功能相反,提供线程安全地used_memory减法操作

    update_zmalloc_stat_sub

    #define update_zmalloc_stat_sub(__n) do { \
       pthread_mutex_lock(&used_memory_mutex); \
       used_memory -= (__n); \
       pthread_mutex_unlock(&used_memory_mutex); \
    } while(0)
    

    zcalloc

    zcalloc()的实现基于calloc(),但是两者编程接口不同

    void *calloc(size_t nmemb, size_t size);
    void *zcalloc(size_t size);
    

    calloc()的功能是也是分配内存空间,与malloc()的不同之处有两点:
    1.它分配的空间大小是 size * nmemb。比如calloc(10,sizoef(char)); // 分配10个字节
    2.calloc()会对分配的空间做初始化工作(初始化为0),而malloc()不会

    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
    }
    

    zaclloc和zmalloc原理是一样的,只不过申请内存时malloc换成了calloc,和zmalloc相比就是申请的内存被初始化了

    zrealloc

    zrealloc()和realloc()具有相同的编程接口:

    void *realloc (void *ptr, size_t size);
    void *zrealloc(void *ptr, size_t size);
    

    realloc()要完成的功能是给首地址ptr的内存空间,重新分配大小。如果失败了,则在其它位置新建一块大小为size字节的空间,将原先的数据复制到新的内存空间,并返回这段内存首地址【原内存会被系统自然释放】。 zrealloc()要完成的功能也类似。

    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);
    #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
    }
    

    zstrdup

    复制字符串s的内容,申请新的内存空间存储字符串。并将这段新的字符串地址返回

    char *zstrdup(const char *s) {
      //strlen()函数是不统计'\0'的,所以最后要加1
      size_t l = strlen(s)+1;
      char *p = zmalloc(l);
       //调用memcpy来完成复制
      memcpy(p,s,l);
      return p;
    }
    

    zmalloc_size

    这个函数是为glibc定制的,只有用这个库时,才能使用这个函数

    #ifndef HAVE_MALLOC_SIZE
      size_t zmalloc_size(void *ptr) {
      void *realptr = (char*)ptr-PREFIX_SIZE;
      size_t size = *((size_t*)realptr);
    
      if (size&(sizeof(long)-1)) size += sizeof(long)-(size&(sizeof(long)-1));
      return size+PREFIX_SIZE;
    }
    #endif
    

    得到zmalloc像系统申请的真实的内存大小

    参考资料:https://blog.csdn.net/guodongxiaren/article/details/44747719
    https://blog.csdn.net/u012842205/article/details/50392119

    相关文章

      网友评论

        本文标题:redis源码1---内存管理(zmalloc)

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