redis源码阅读理解,及相关语言细节---zmalloc.c

malloc是一个十分常用的用来申请内存的函数,redis对于内存的申请分配有着自己定义的库文件,今天我们就来看看相关的代码zmalloc.c里面的内容。首先,依旧是先贴代码。
#ifndef __ZMALLOC_H
#define __ZMALLOC_H

/* Double expansion needed for stringification of macro values. */
#define __xstr(s) __str(s)
#define __str(s) #s

#if defined(USE_TCMALLOC)
#define ZMALLOC_LIB ("tcmalloc-" __xstr(TC_VERSION_MAJOR) "." __xstr(TC_VERSION_MINOR))
#include <google/tcmalloc.h>
#if (TC_VERSION_MAJOR == 1 && TC_VERSION_MINOR >= 6) || (TC_VERSION_MAJOR > 1)
#define HAVE_MALLOC_SIZE 1
#define zmalloc_size(p) tc_malloc_size(p)
#else
#error "Newer version of tcmalloc required"
#endif

首先是一些代码的宏定义,其中代码运用了#if defined(X)这样的写法,这样定义的目的是为了判断X是否已经编译过,如果已经在之前编译过,那么对于中间的代码才会执行,如果是没有定义X那么将会忽略中间的代码部分,同时还有另外一种写法,#ifdnf与上面的写法的作用是一样的,同时我们也可以这样写,#if !defined(X),与#ifndef这两者的意思是若之前没有对这个进行编译,那么就执行中间的代码。

后面还有一些定义,我们可以先忽略掉,直接进入与本文相关的定义

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(void);
size_t zmalloc_get_rss(void);
size_t zmalloc_get_private_dirty(void);
void zlibc_free(void *ptr);

在定义中出现了一个新的数据类型size_t,这种数据类型可以简单理解为unsigned int,即这种类型有足够大的大小去存储整数,首先第一个函数就是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是我们需要的数据大小,而后面的PREFIX_SIZE再不同的环境中有着不一样的大小,这个参数是一个宏定义变量,接下来返回的指针用来判断是否为空,如果返回的是空指针,那么将会跳转到后面的zmalloc_oom_handler函数,这个函数的主要目的是打印相关出错信息,代码如下:

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 %zu bytes\n",
        size);
    fflush(stderr);
    abort();
}

这样我们可以十分清楚的看到,其实zmalloc_oom_handler其实已经变成了函数指针,而实际执行的代码是zmalloc_default_oom,其中的内容则是打印出信息,并且终止当前的服务(abort()函数),接下来的函数就是宏定义的函数

,无论是#if还是#else部分都涉及到了一个函数—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) 这个部分是十分炫酷的写法,总之一句话概括就是—它能确保宏的行为总是相同的, 如果想看详细的解释,可以移步到这个网址 点击打开链接,然后这段代码就是判断分配的内存空间的大小是不是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效率之高,速度之快,这些宏函数可谓功不可没)。它也是一个条件编译的宏,依据不同的宏有不同的定义, 代码如下
#define update_zmalloc_stat_add(__n) do { \
    pthread_mutex_lock(&used_memory_mutex); \
    used_memory += (__n); \
    pthread_mutex_unlock(&used_memory_mutex); \
} while(0)

pthread_mutex_lock()和pthread_mutex_unlock()使用互斥锁(mutex)来实现线程同步,前者表示加锁,后者表示解锁,它们是POSIX定义的线程同步函数。当加锁以后它后面的代码在多线程同时执行这段代码的时候就只会执行一次,也就是实现了线程安全。
-----------------------------------------------------------------------------------------------------
      ------这段话是我转的,本来我很想以自己的理解写下这一块,但是这个博主实在是解释的太好了,所以我把这段话直接搬过来,也好有一个更清晰的认识。

接下来的函数包括zcalloc()和zrealloc()两个函数,首先我们能够知道两个函数的原型分别是calloc()与realloc(),接下来看一下这链各个函数与malloc()的区别,首先是calloc()的malloc()的却别是前者在分配内存之后需要将里面的数据初始化,而malloc()是不需要的,也就是说malloc()分配出来的值很有可能是不确定的值,而calloc()则是确定的值,realloc()的作用是重新分配已经分配好的内存,说起来可能比较的拗口,但是根本的作用就是改变原有的内存大小,如果需要添加数据那么realloc()的会先检查尾部是否满足需求,若不满足,那么将会开辟新的内存出来,同样的在redis里面的这三个函数除了调用的不同之外,其余的部分都是一样的逻辑,我就不在重复的去贴代码解释了,

扫描二维码关注公众号,回复: 293725 查看本文章

接下来的函数就是与malloc()对应的函数free(),redis的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
    realptr = (char*)ptr-PREFIX_SIZE;
    oldsize = *((size_t*)realptr);
    update_zmalloc_stat_free(oldsize+PREFIX_SIZE);
    free(realptr);
#endif
}

首先对于宏定义部分可以只关注#else的部分,结合之前的zmalloc()分析,我们可以知道realptr 意味着之前的真正内存地址,之后对于取出真正分配内存的大小oldsize = *((size_t*)realptr);之后运用函数将器释放,我们可以看出,redis的作者在处理这一部分的代码上条理十分的清晰,真的是感觉redis的性能这么好不是白来的。

其他部分的代码相对简单,贴在下面弓大家理解:

size_t zmalloc_get_rss(void) {
    /* If we can't get the RSS in an OS-specific way for this system just
     * return the memory usage we estimated in zmalloc()..
     *
     * Fragmentation will appear to be always 1 (no fragmentation)
     * of course... */
    return zmalloc_used_memory();
}

size_t zmalloc_used_memory(void) {
    size_t um;

    if (zmalloc_thread_safe) {
#ifdef HAVE_ATOMIC
        um = __sync_add_and_fetch(&used_memory, 0);
#else
        pthread_mutex_lock(&used_memory_mutex);
        um = used_memory;
        pthread_mutex_unlock(&used_memory_mutex);
#endif
    }
    else {
        um = used_memory;
    }

    return um;
}

void zmalloc_enable_thread_safeness(void) {
    zmalloc_thread_safe = 1;
}


猜你喜欢

转载自blog.csdn.net/zhuangtongy/article/details/80239620