redis源码学习之分配内存

1、redis 总体概况

Redis在内存分配方面,仅仅是对系统的malloc/free做了一层简单的封装,然后加上了异常处理功能和内存统计功能。其实现主要在zmalloc.c和zmalloc.h文件中

2、功能函数

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);  // 调用zfree释放内存空间
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); // 获取RSS信息(Resident Set Size)
size_t zmalloc_get_private_dirty(void); // 获得实际内存大小
size_t zmalloc_get_smap_bytes_by_field(char *field); // 获取/proc/self/smaps字段的字节数
size_t zmalloc_get_memory_size(void); // 获取物理内存大小
void zlibc_free(void *ptr); // 原始系统free释放方法

几个变量
static size_t used_memory = 0;  // 已使用内存的大小
static int zmalloc_thread_safe = 0; // 线程安全模式状态
pthread_mutex_t used_memory_mutex = PTHREAD_MUTEX_INITIALIZER; // 为此服务器

3、分步解析

  1. 异常处理函数
    如果分配失败,就将错误输出到标准错误输出字节流上面,进程终止
    将其赋值给函数指针static void (*zmalloc_oom_handler)(size_t) = zmalloc_default_oom
static void zmalloc_default_oom(size_t size) {
    fprintf(stderr, "zmalloc: Out of memory trying to allocate %Iu bytes\n",    WIN_PORT_FIX size);
    fflush(stderr);  //fflush用于清空缓冲流
    abort(); //abort()函数一定会使得进程终止
}

2.zmalloc函数
Redis的内存申请函数zmalloc本质就是调用了系统的malloc函数,然后对其进行了适当的封装,加上了异常处理函数和内存统计

void *zmalloc(size_t size) {
    // 调用malloc函数进行内存申请
    // 多申请的PREFIX_SIZE大小的内存用于记录该段内存的大小
    void *ptr = malloc(size+PREFIX_SIZE);

    // 如果ptr为NULL,则调用异常处理函数
    if (!ptr) zmalloc_oom_handler(size);
    // 以下是内存统计
    *((size_t*)ptr) = size;
    update_zmalloc_stat_alloc(size+PREFIX_SIZE); // 更新used_memory的值
    return (char*)ptr+PREFIX_SIZE;
}

思考:为什么要有PREFIX_SIZE?
由于malloc函数申请的内存不会标识内存块的大小,而我们需要统计内存大小,所以需要在多申请PREFIX_SIZE大小的内存,用于存放该大小

更新used_memory值的 函数以宏定义给出

#define update_zmalloc_stat_alloc(__n) do { 
    size_t _n = (__n); 
    if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1));   // 将_n调整为sizeof(long)的整数倍
    if (zmalloc_thread_safe) { // 如果启用了线程安全模式
        update_zmalloc_stat_add(_n);   // 调用原子操作加(+)来更新已用内存
    } else { 
        used_memory += _n;   // 不考虑线程安全,则直接更新已用内存
    } 
} while(0)

思考:为什么将将_n调整为sizeof(long)的整数倍?
因为要将其调整为8的倍数,根据内存池的配原理来

在上述函数中,线程安全的模式:对used_memory 这个资源的访问,通过加互斥锁实,保证了同一时刻只有唯一的一个线程对这个共享进行访问
改进:c++11中提供了一些原子操作,将used_memory 定义为原子操作数据,就无需借用锁机制

#define update_zmalloc_stat_add(__n) do { \
    pthread_mutex_lock(&used_memory_mutex); \
    used_memory += (__n); \
    pthread_mutex_unlock(&used_memory_mutex); \
} while(0)
  1. zcalloc 函数
    与malloc一样,zcalloc调用的是系统给的calloc()来申请内存。
void *zcalloc(size_t size) {
    void *ptr = calloc(1, size+PREFIX_SIZE);
    // 异常处理函数
    if (!ptr) zmalloc_oom_handler(size);
    // 内存统计函数
    *((size_t*)ptr) = size;
    update_zmalloc_stat_alloc(size+PREFIX_SIZE);
    return (char*)ptr+PREFIX_SIZE;
}

思考:为什么有了malloc 还会有calloc分配内存?
函数malloc()和calloc()都可以用来动态分配内存空间,但两者稍有区别。
malloc()函数有一个参数,即要分配的内存空间的大小:
void *malloc(size_t size);
calloc()函数有两个参数,分别为元素的数目和每个元素的大小,这两个参数的乘积就是要分配的内存空间的大小。
void *calloc(size_t numElements,size_t sizeOfElement);
如果调用成功,函数malloc()和函数calloc()都将返回所分配的内存空间的首地址。
但是, 函数malloc()和函数calloc()的主要区别是前者不能初始化所分配的内存空间,而后者能。如果由malloc()函数分配的内存空间原来没有被使用过,则其中的每一位可能都是0;反之,如果这部分内存曾经被分配过,则其中可能遗留有各种各样的数据,可能后面执行的时候会出错
函数calloc()会将所分配的内存空间中的每一位都初始化为零,也就是说,如果你是为字符类型或整数类型的元素分配内存,那麽这些元素将保证会被初始化为0;如果你是为指针类型的元素分配内存,那麽这些元素通常会被初始化为空指针;如果你为实型数据分配内存,则这些元素会被初始化为浮点型的零

  1. zrecalloc函数
    作用:调整内存
    定义的zrecalloc用于调整已申请内存的大小,其本质也是直接调用系统函数recalloc()
void *zrealloc(void *ptr, size_t size) {
    size_t oldsize;
    void *newptr;
    // 为空直接退出
    if (ptr == NULL) return zmalloc(size);
    // 找到内存真正的起始位置
    realptr = (char*)ptr-PREFIX_SIZE;
    oldsize = *((size_t*)realptr);
    // 调用recalloc函数
    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;
}

realloc(void *__ptr, size_t __size):更改已经配置的内存空间,即更改由malloc()函数分配的内存空间的大小
如果是将分配的内存扩大,则有以下情况:
1)如果当前内存段后面有需要的内存空间,则直接扩展这段内存空间,realloc()将返回原指针。
2)如果当前内存段后面的空闲字节不够,那么就使用堆中的第一个能够满足这一要求的内存块,将目前的数据复制到新的位置,并将原来的数据块释放掉,返回新的内存块位置。
3)如果申请失败,将返回NULL,此时,原来的指针仍然有效

  1. zfree函数
    内存释放也是调用系统的free()函数来实现内存释放
void zfree(void *ptr) {
    if (ptr == NULL) return;  // 为空直接返回
    realptr = (char*)ptr-PREFIX_SIZE; // 找到该段内存真正的起始位置
    oldsize = *((size_t*)realptr);
    update_zmalloc_stat_free(oldsize+PREFIX_SIZE);// 更新use_memory函数
    free(realptr); // 调用系统的内存释放函数
}

更新内存状态统计函数

#define update_zmalloc_stat_free(__n) do {
    size_t _n = (__n); 
    if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1));  // 将内存大小调整为sizeof(long)的整数倍
    if (zmalloc_thread_safe) {  // 如果开启了线程安全模式
        update_zmalloc_stat_sub(_n); // 更新use_memory值(与上述的update_zmalloc_stat_add这里就不赘述了)
    } else {
        used_memory -= _n; // 没有线程安全则直接减
    }
} while(0)
  1. zsize函数
    返回内存块的大小
size_t zmalloc_size(void *ptr) {
    void *realptr = (char*)ptr-PREFIX_SIZE; //内存起始地址
    size_t size = *((size_t*)realptr) ;
    if (size&(sizeof(PORT_LONG)-1)) size += sizeof(PORT_LONG)-(size&(sizeof(PORT_LONG)-1));//调整到long 的整数倍
    return size+PREFIX_SIZE;
}
  1. 字符串复制
char *zstrdup(const char *s) {
    size_t l = strlen(s)+1; 
    char *p = zmalloc(l); // 开辟一段新内存
    memcpy(p,s,l); // 调用字符串复制函数
    return p;
}
  1. 设置异常处理函数
    自行定义异常函数,参数:函数指针
void zmalloc_set_oom_handler(void (*oom_handler)(size_t)) {
    zmalloc_oom_handler = oom_handler; // 绑定自定义的异常处理函数
}
  1. 开启线程安全
void zmalloc_enable_thread_safeness(void) {
    zmalloc_thread_safe = 1;  // 此参数用来控制是否开启线程安全
}
  1. 获取已使用内存
size_t zmalloc_used_memory(void) {
    size_t um;
    // 如果开启了线程安全模式
    if (zmalloc_thread_safe) {
#if defined(__ATOMIC_RELAXED) || defined(HAVE_ATOMIC)
        um = update_zmalloc_stat_add(0);
#else
        pthread_mutex_lock(&used_memory_mutex);
        um = used_memory;
        pthread_mutex_unlock(&used_memory_mutex);
#endif
    }
    else {
        um = used_memory; // 未开启则直接使用used_memory
    }

    return um;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Redis是一个开源的、使用C语言编写的高性能键值对存储数据库,其源码注释主要包含了对Redis的各个组件、模块、函数等的详细说明,方便开发者理解和使用Redis。 在Redis 7.0的源码注释中,包含了对Redis的基本结构、数据类型、内存管理、数据库操作、网络通信等方面的注释。 在基本结构方面,源码注释详细介绍了Redis的整体架构和模块之间的关系,如服务器结构、客户端结构、数据库结构等。这些注释帮助开发者了解Redis的组成部分,有助于对Redis进行二次开发和定制化。 在数据类型方面,可以找到对Redis支持的各种数据类型(如字符串、列表、哈希表、有序集合等)的源码注释。这些注释详细说明了数据类型的实现原理和内部数据结构,以及对应操作的时间复杂度等信息,有助于开发者在使用Redis时了解其内部实现机制和使用方法。 在内存管理方面,Redis源码注释解释了Redis对内存的分配和释放,并介绍了部分内存管理的原理和策略,如对象引用计数、内存回收等。这些注释可以帮助开发者理解Redis在内存管理方面的设计和优化。 在数据库操作方面,Redis源码注释提供了对数据库的增删改查操作的详细注释,包括对集合操作、哈希表操作、有序集合操作等。这些注释可以帮助开发者了解Redis对数据的存储和操作方式,以及相应的数据结构和算法。 在网络通信方面,Redis源码注释包含了对网络连接、通信协议、命令解析等的详细说明。这些注释帮助开发者了解Redis与客户端之间的通信机制,以及如何解析和处理客户端发送的命令。 总之,Redis 7.0源码注释提供了对Redis各个方面的详细解释,帮助开发者理解Redis的内部结构、实现原理和使用方式,是开发者学习和使用Redis的重要参考资料。 ### 回答2: Redis 7.0是一款开源的、高性能的、支持多种数据结构的内存数据库。在Redis 7.0的源码注释中,涉及了许多关键的实现细节、数据结构定义和算法逻辑。 首先,Redis 7.0的源码注释提供了对各个函数和模块的详细解释。这些注释能够帮助开发人员更好地理解代码的功能和实现方式,提供了方便的参考和指导。例如,对于关键数据结构如字符串、哈希表、链表等的定义和使用都有详细的注释说明,帮助开发人员了解其内部实现原理和使用方式。 其次,Redis 7.0的源码注释还包含了算法的解释和优化思路。对于一些关键算法,如缓存淘汰算法、订阅与发布算法等,注释中详细解释了其实现原理和性能优化的思路。这对于开发人员来说是非常有价值的,可以帮助他们更好地理解和改进Redis的性能和功能。 此外,Redis 7.0的源码注释还包括了对一些重要模块和功能的解释。例如,对于多线程支持的相关代码和模块,注释中详细解释了其原理和使用方式,以及相关的并发控制策略。这可以帮助开发人员更好地理解和使用Redis的多线程功能,提高系统的并发处理能力。 总而言之,Redis 7.0源码注释对于开发人员来说是一份非常宝贵的文档。它提供了对代码的详细解释和说明,包括数据结构定义、算法实现和模块功能等方面。这些注释可以帮助开发人员更加深入地理解和使用Redis,并为他们在解决问题和优化性能时提供有价值的参考和指导。 ### 回答3: Redis 7.0 是 Redis 数据库的一个版本,以下是对该版本源码注释的简要解释: Redis 7.0 的源码注释主要用于解释 Redis 数据库中的各个函数、数据结构和算法的功能和实现原理。 在 Redis 7.0 的源码中,注释被用来解释函数的输入参数和返回值、函数的作用、关键算法的实现细节以及代码的逻辑结构等等。注释帮助开发者理解和使用源码,对于对 Redis 进行二次开发或者进行排错、优化也起到了重要作用。 源码注释提供了对 Redis 各个模块和功能的清晰解释,例如底层的数据类型实现、线程模型、COW(copy-on-write)策略等等。同时,注释还覆盖了 Redis 7.0 引入的新功能和改进。例如,Redis 7.0 可能引入了新的数据结构或者命令,这些新功能的实现细节可能在注释中有所描述。 此外,Redis 7.0 的源码注释还可能包含一些重要的注释块,其中包括一些算法和数据结构的详细解释,这有助于开发者深入了解 Redis 内部的运作方式。 需要注意的是,Redis 7.0 的源码注释是用来帮助开发者理解源码的,因此在实际运行时会被编译器忽略掉。因此,在阅读和使用源码时,我们需要同时参考源码注释和实际代码实现来全面理解 Redis 的工作机制。 总之,Redis 7.0 的源码注释对于开发者来说是一份重要的参考资料,能够帮助他们理解 Redis 的工作原理和实现方式,从而更好地使用和扩展 Redis 数据库
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值