• Redis阅读——内存分配


    前言

    按照https://www.zhihu.com/question/28677076中的推荐,从今天起开始Redis源码的阅读工作,第一步便是Redis的内存分配机制。代码都在zmalloc.h和zmalloc.c中。

    头文件

    接下来就是一步一步解读zmalloc.h,首先第一个

    #define __xstr(s) __str(s)
    #define __str(s) #s
    
    • 1
    • 2

    第二行中似乎有点奇怪,这个#s实际上就是在预处理阶段将s转换成字符串,s如果是1234,则__str(s)的结果就是"1234"。下面的:

    #if defined(USE_TCMALLOC)
    #elif defined(USE_JEMALLOC)
    
    • 1
    • 2

    这一部分是用来判断是否使用tcmalloc或者jemalloc作为内存分配器

    源码

    zmalloc

    正如其名,最关键的可能就是zmalloc函数,代码为

    void *zmalloc(size_t size) {
        void *ptr = ztrymalloc_usable(size, NULL);
        if (!ptr) zmalloc_oom_handler(size);
        return ptr;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    先看调用的第一个函数ztrymalloc_usable:

    void *ztrymalloc_usable(size_t size, size_t *usable) {
        ASSERT_NO_SIZE_OVERFLOW(size);//断言size是否溢出
        void *ptr = malloc(MALLOC_MIN_SIZE(size)+PREFIX_SIZE);
        if (!ptr) return NULL;
    
    • 1
    • 2
    • 3
    • 4

    这个函数第一行ASSERT_NO_SIZE_OVERFLOW是用来断言size是否溢出,通过assert((sz) + PREFIX_SIZE > (sz)),如果sz加上PREFIX_SIZE之后不大于sz,就说明已经溢出,因为sz和PREFIX_SIZE是无符号类型。断言成功的话就调用glib.c中的malloc函数申请内存,注意:size如果是0的话,申请的是sizeof(long)+PREFIX_SIZE大小的内存,否则是size+PREFIX_SIZE大小的内存。内存申请完毕,回到zmalloc函数,如果溢出,ptr是NULL,进入zmalloc_oom_handler函数报错,不溢出的话就返回内存地址。不过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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    函数体内,输出错误信息,冲刷stderr缓冲区,终止进程。除了z_trymalloc_usable还有一个函数为z_malloc_usable,该函数形式为:

    void *zmalloc_usable(size_t size, size_t *usable) {
        void *ptr = ztrymalloc_usable(size, usable);
        if (!ptr) zmalloc_oom_handler(size);
        return ptr;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    z_malloc_usable内部仍然是调用了ztrymalloc_usable,只不过内存没有申请成功的话会报错。

    zcalloc

    zcalloc函数的组织形式和zmalloc很相似,只不过:

    void *ptr = calloc(1, MALLOC_MIN_SIZE(size)+PREFIX_SIZE);
    
    • 1

    调用calloc函数时的第一个参数指定为1,并且为了和calloc类似,专门写了一个可以指定参数的zcalloc_num:

    void *zcalloc_num(size_t num, size_t size) {
        /* Ensure that the arguments to calloc(), when multiplied, do not wrap.
         * Division operations are susceptible to divide-by-zero errors so we also check it. */
        if ((size == 0) || (num > SIZE_MAX/size)) {
            zmalloc_oom_handler(SIZE_MAX);
            return NULL;
        }
        void *ptr = ztrycalloc_usable(num*size, NULL);
        if (!ptr) zmalloc_oom_handler(num*size);
        return ptr;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    zrealloc

    重新分配内存,realloc的升级版,这里同样解读最重要的一个函数:

    void *ztryrealloc_usable(void *ptr, size_t size, size_t *usable) {
        ASSERT_NO_SIZE_OVERFLOW(size);
    #ifndef HAVE_MALLOC_SIZE//如果定义了HAVE_MALLOC_SIZE,也就是申请内存中前几个字节是用于存储申请内存的大小
        void *realptr; 
    #endif
        size_t oldsize;
        void *newptr;
    
        /* not allocating anything, just redirect to free. */
        //size为0加上之前内容有效,相当于释放内存
        if (size == 0 && ptr != NULL) {
            zfree(ptr);
            if (usable) *usable = 0;
            return NULL;
        }
        /* 传入的ptr就是NULL,就相当于重新申请内存
        if (ptr == NULL)
            return ztrymalloc_usable(size, usable);
    
    #ifdef HAVE_MALLOC_SIZE
        oldsize = zmalloc_size(ptr);//获取实际使用的内存大小
        newptr = realloc(ptr,size);//调用C中的realloc
        if (newptr == NULL) {
            if (usable) *usable = 0;
            return NULL;
        }//内存分配失败,返回NULL
    
        update_zmalloc_stat_free(oldsize);//原子类型的减操作,use_memory = use_memory - oldsize
        size = zmalloc_size(newptr);
        update_zmalloc_stat_alloc(size);//原子类型的加操作,use_memory = use_memory + size
        if (usable) *usable = size;
        return newptr;
    #else //未定义HAVE_MALLOC_SIZE
        realptr = (char*)ptr-PREFIX_SIZE;//将当前的指针退PREFIX_SIZE大小
        oldsize = *((size_t*)realptr);//之前的大小
        newptr = realloc(realptr,size+PREFIX_SIZE);
        if (newptr == NULL) {
            if (usable) *usable = 0;
            return NULL;
        }
    
        *((size_t*)newptr) = size;
        update_zmalloc_stat_free(oldsize);
        update_zmalloc_stat_alloc(size);
        if (usable) *usable = size;
        return (char*)newptr+PREFIX_SIZE;
    #endif
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    函数中不断#ifdef HAVE_MALLOC_SIZE来判断是否定义了HAVE_MALLOC_SIZE这个宏,如果定义的话,说明可以直接通过zmalloc_size函数判断实际使用的内存,也就是调用malloc的话系统直接多申请size_t大小的内存用来存储申请内存的大小,不然的话我们必须要手动预留PREFIX_SIZE大小的内存。下面这两个函数有体现:

    #ifndef HAVE_MALLOC_SIZE
    size_t zmalloc_size(void *ptr) {
        void *realptr = (char*)ptr-PREFIX_SIZE;
        size_t size = *((size_t*)realptr);
        return size+PREFIX_SIZE;
    }
    size_t zmalloc_usable_size(void *ptr) {
        return zmalloc_size(ptr)-PREFIX_SIZE;
    }
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    也就是下面这个构成:
    在这里插入图片描述
    引用自:Redis底层详解(三) 内存管理
    以上就是Redis内存分配的主要内容,总的来说不难,但用了大量的宏定义,看着可能不是很习惯。

  • 相关阅读:
    [附源码]计算机毕业设计springboot健身房信息管理
    Codeforces Round #818 (Div. 2)
    处理流程设计-系统设计-人机界面设计
    日本购物网站的网络乞丐功能
    2023最新SSM计算机毕业设计选题大全(附源码+LW)之java学生信息管理系统ow05a
    2023.11.19使用flask制作一个文件夹生成器
    code blocks使用集
    【测开求职】2023秋招百度三面面经
    react-router-dom6 路由懒加载与组件懒加载
    从0到1图文教你如何将spring boot项目部署到minikube中去
  • 原文地址:https://blog.csdn.net/qq_36763031/article/details/125402779