• redis源码学习之zmalloc


    redis版本3.2.13

    toc

    函数声明速览

    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(size_t rss);
    size_t zmalloc_get_rss(void);
    size_t zmalloc_get_private_dirty(void);
    size_t zmalloc_get_smap_bytes_by_field(char *field);
    size_t zmalloc_get_memory_size(void);
    void zlibc_free(void *ptr);
    
    #ifndef HAVE_MALLOC_SIZE
    size_t zmalloc_size(void *ptr);
    #endif

    主要函数学习

    zmalloc函数

    zmalloc封装了内存分配策略,其试图依次使用tcmalloc、jemalloc、apple的malloc、以及其他系统自带的malloc

    #if defined(USE_TCMALLOC)
    #define ZMALLOC_LIB ("tcmalloc-" __xstr(TC_VERSION_MAJOR) "." __xstr(TC_VERSION_MINOR))
    ...
    #define HAVE_MALLOC_SIZE 1
    #define zmalloc_size(p) tc_malloc_size(p)
    ...
    #endif
    
    #elif defined(USE_JEMALLOC)
    #define ZMALLOC_LIB ("jemalloc-" __xstr(JEMALLOC_VERSION_MAJOR) "." __xstr(JEMALLOC_VERSION_MINOR) "." __xstr(JEMALLOC_VERSION_BUGFIX))
    ...
    #define HAVE_MALLOC_SIZE 1
    #define zmalloc_size(p) je_malloc_usable_size(p)
    ...
    #endif
    
    #elif defined(__APPLE__)
    ...
    #define HAVE_MALLOC_SIZE 1
    #define zmalloc_size(p) malloc_size(p)
    #endif
    
    #ifndef ZMALLOC_LIB
    #define ZMALLOC_LIB "libc"
    #endif

    由以上代码可知,tcmalloc、jemalloc、apple的malloc都有获取分配内存大小的函数,可直接使用;在其他系统下,redis通过额外分配一个头部来记录malloc的大小

    代码如下

    #ifdef HAVE_MALLOC_SIZE //tcmalloc、jemalloc、apple
    #define PREFIX_SIZE (0)
    #else
    ...
    #define PREFIX_SIZE (sizeof(size_t))
    #endif
    ...
    
    void *zmalloc(size_t size) {
        void *ptr = malloc(size+PREFIX_SIZE);
    
        if (!ptr) zmalloc_oom_handler(size);    //处理内存不足
    #ifdef HAVE_MALLOC_SIZE //tcmalloc、jemalloc、apple
        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
    }

    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)

    其中有这样一句:

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

    由于内存对齐的需要,系统分配的大小总是sizeof(long)的倍数,此处也是判断待记录的_n是否是sizeof(long)的整数倍,不是就拓展到sizeof(long)的整数倍,以此来精确记录已分配的内存。
    由于used_memory是个静态全局变量:

    static size_t used_memory = 0;
    static int zmalloc_thread_safe = 0;

    如果设置了zmalloc_thread_safe标志则按update_zmalloc_stat_add方式增加记录数量。此方式内部通过原子操作或互斥锁保证了线程安全。

    ...
    pthread_mutex_t used_memory_mutex = PTHREAD_MUTEX_INITIALIZER;
    ...
    #ifdef HAVE_ATOMIC
    #define update_zmalloc_stat_add(__n) __sync_add_and_fetch(&used_memory, (__n))
    ...
    #else
    #define update_zmalloc_stat_add(__n) do { 
        pthread_mutex_lock(&used_memory_mutex); 
        used_memory += (__n); 
        pthread_mutex_unlock(&used_memory_mutex); 
    } while(0)

    zfree

    zfree跟zmalloc是相反的操作,它从已申请内存中减去了被释放的内存大小以及头部大小,并从头部位置开始释放内存。

    ...
    #ifdef HAVE_ATOMIC
    ...
    #define update_zmalloc_stat_sub(__n) __sync_sub_and_fetch(&used_memory, (__n))
    #else
    ...
    #define update_zmalloc_stat_sub(__n) do { 
        pthread_mutex_lock(&used_memory_mutex); 
        used_memory -= (__n); 
        pthread_mutex_unlock(&used_memory_mutex); 
    } while(0)
    
    #endif
    
    #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)
    
    void zfree(void *ptr) {
    #ifndef HAVE_MALLOC_SIZE //tcmalloc、jemalloc、apple
        void *realptr;
        size_t oldsize;
    #endif
    
        if (ptr == NULL) return;
    #ifdef HAVE_MALLOC_SIZE //tcmalloc、jemalloc、apple
        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
    }

    zmalloc_used_memory

    zmalloc_used_memory根据zmalloc_thread_safe标志返回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;
    }

    zmalloc_get_private_dirty

    size_t zmalloc_get_private_dirty(void) {
        return zmalloc_get_smap_bytes_by_field("Private_Dirty:");
    }
    
    #if defined(HAVE_PROC_SMAPS)
    size_t zmalloc_get_smap_bytes_by_field(char *field) {
        char line[1024];
        size_t bytes = 0;
        FILE *fp = fopen("/proc/self/smaps","r");
        int flen = strlen(field);
    
        if (!fp) return 0;
        while(fgets(line,sizeof(line),fp) != NULL) {
            if (strncmp(line,field,flen) == 0) {
                char *p = strchr(line,'k');
                if (p) {
                    *p = '';
                    bytes += strtol(line+flen,NULL,10) * 1024;
                }
            }
        }
        fclose(fp);
        return bytes;
    }
    #else
    size_t zmalloc_get_smap_bytes_by_field(char *field) {
        ((void) field);
        return 0;
    }
    #endif

    在linux中,proc文件系统为每个进程都提供了一个smaps文件,这里列出相关的4个字段:
    Shared_Clean:和其他进程共享的未被改写的page的大小
    Shared_Dirty: 和其他进程共享的被改写的page的大小
    Private_Clean:未被改写的私有页面的大小。
    Private_Dirty: 已被改写的私有页面的大小

    在redis持久化函数内子进程中用到了这个函数,当redis通过子进程去持久化时,fork()仅为子进程创建了虚拟地址空间,仍与父进程共享同样的物理空间,当父子进程某一方发生写时,系统才会为其分配物理空间并复制一份副本以供其修改。当子进程被fork出来时,空间是Private_Clean的,运行一段时间后子进程对继承而来的内存进行了修改,修改的部分就不能共享了,需要分配真实物理空间,这部分就是Private_Dirty的,所以此函数是返回持久化时子进程中所占用的实际物理内存。

    参考:
    smaps文件 https://www.cnblogs.com/HeDaoYiWenZi/articles/2858043.html
    Copy On Write https://zhuanlan.zhihu.com/p/48147304





    原创不易,转载请注明出处,谢谢
  • 相关阅读:
    用 HTML 格式导出 Excel 时,如何保留显示网格线
    [转载] 让SciTE能够和IDE一样进行成员提示
    RedHat 上安装多个 mysql 实例并配置 django 连接的操作记录
    我的 SciTEGlobal.properties 配置文件
    FrameSet 不能支持透明
    RedHat 上安装 lighttpd 并配置 fastcgi + django 的记录
    系统设计与开发 辅助工具大集成终结版
    SQL Server Management Object(SMO)大大简化数据库工具的开发 几行代码开发功能强大的SQL工具
    Enterprise Solution 解决方案与源代码下载
    LLBL Gen 实体映射工具技术原理剖析
  • 原文地址:https://www.cnblogs.com/Keeping-Fit/p/14038846.html
Copyright © 2020-2023  润新知