Redis内存管理(二)

上一遍详细的写明了Redis为内存管理所做的初始化工作,这篇文章写具体的函数实现。

1、zmalloc_size,返回内存池大小函数,因为库不同,所以这个函数在内部有很多的宏定义,通过具体使用的库来确定到底用哪个。

#define zmalloc_size(p) tc_malloc_size(p)//TCMalloc
#define zmalloc_size(p) je_malloc_usable_size(p)//Jemalloc
#define zmalloc_size(p) malloc_size(p)//Mac
//使用系统库的情况下,要单独写返回函数 #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

2、zmalloc,内存分配函数

void *zmalloc(size_t size) 
{/*普通的申请内存大小函数,申请的时候,内存的头部加上一个longlong长度的头部*/
    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
}

3.zcalloc

/*重定义calloc函数,单每次只能申请一块内存,放弃了申请多块内存的功能,实际功能和zmalloc类似*/
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
}

4、zrealloc,该函数放弃了按内存块大小成倍申请的功能,最后功能类似zmalloc

 1 void *zrealloc(void *ptr, size_t size) 
 2 {
 3 #ifndef HAVE_MALLOC_SIZE
 4     //使用成熟库函数的情况下直接修改大小就好。最后更新使用内存量的大小。
 5     void *realptr;
 6 #endif
 7 
 8     size_t oldsize;
 9     void *newptr;
10 
11     if (ptr == NULL) return zmalloc(size);
12 #ifdef HAVE_MALLOC_SIZE
13     oldsize = zmalloc_size(ptr);
14     newptr = realloc(ptr,size);
15     if (!newptr) zmalloc_oom_handler(size);
16 
17     update_zmalloc_stat_free(oldsize);
18     update_zmalloc_stat_alloc(zmalloc_size(newptr));
19     return newptr;
20 #else
21     /*没有使用现成库的情况下,扩展内存
22     将指针前移,找到真正开始的头部并记录下真正的开始,
23     按照新的大小重新申请内存,然后释放旧的,最后更新使用内存量的大小。
24     */
25     realptr = (char*)ptr-PREFIX_SIZE;
26     oldsize = *((size_t*)realptr);
27     newptr = realloc(realptr,size+PREFIX_SIZE);
28     if (!newptr) zmalloc_oom_handler(size);
29 
30     *((size_t*)newptr) = size;
31     update_zmalloc_stat_free(oldsize);
32     update_zmalloc_stat_alloc(size);
33     return (char*)newptr+PREFIX_SIZE;
34 #endif
35 }

5、zfree,内存释放函数

 1 void zfree(void *ptr) {
//这个也是分两部分,如果使用了成熟库,直接调用释放就好,如果使用系统的,还要找到真的地址开始的头部然后再释放,最后更新内存使用量的大小。
2 #ifndef HAVE_MALLOC_SIZE 3 void *realptr; 4 size_t oldsize; 5 #endif 6 7 if (ptr == NULL) return; 8 #ifdef HAVE_MALLOC_SIZE 9 update_zmalloc_stat_free(zmalloc_size(ptr)); 10 free(ptr); 11 #else 12 realptr = (char*)ptr-PREFIX_SIZE; 13 oldsize = *((size_t*)realptr); 14 update_zmalloc_stat_free(oldsize+PREFIX_SIZE); 15 free(realptr); 16 #endif 17 }

6、zstrdup

1 /*字符串复制函数,给一个指定的字符串在堆上分配内存,注意,这个地方只是给一个字符串分配了内存,并没有转换成系统内统一的sds字符串,强行按sds使用会出错。*/
2 char *zstrdup(const char *s) {
3     size_t l = strlen(s)+1;
4     char *p = zmalloc(l);
5 
6     memcpy(p,s,l);
7     return p;
8 }

7、zmalloc_used_memory,获取已经使用的内存的大小

 1 size_t zmalloc_used_memory(void) 
 2 {
 3     size_t um;
 4   //如果是线程安全情况下,读取当前使用内存量时要加互斥锁,不然可能会出现并发问题
 5     if (zmalloc_thread_safe) 
 6     {
 7         #if defined(__ATOMIC_RELAXED) || defined(HAVE_ATOMIC)
 8             um = update_zmalloc_stat_add(0);
 9         #else
10             pthread_mutex_lock(&used_memory_mutex);
11             um = used_memory;
12             pthread_mutex_unlock(&used_memory_mutex);
13         #endif
14     }
15     else
16     {//如果没设定线程安全,直接读取
17         um = used_memory;
18     }
19 
20     return um;
21 }

8、zmalloc_enable_thread_safeness,设置线程安全,多线程模式下,最好是设置了,系统好像没提供解除的函数,也就是说系统一但确定了模式将不能改变。

void zmalloc_enable_thread_safeness(void) 
{
    zmalloc_thread_safe = 1;
}

9、zmalloc_set_oom_handler,设置内存异常处理函数,如果不设置,系统有一个默认的。

void zmalloc_set_oom_handler(void (*oom_handler)(size_t)) 
{
    zmalloc_oom_handler = oom_handler;
}

10、zmalloc_get_rss,获取进程总的虚拟内存的大小

#if defined(HAVE_PROC_STAT)
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

/*计算使用内存的总值*/
size_t zmalloc_get_rss(void) {
    int page = sysconf(_SC_PAGESIZE);/*获取系统中一页内存的大小*/
    size_t rss;
    char buf[4096];
    char filename[256];
    int fd, count;
    char *p, *x;
    /*从/proc/PID/stat文件中读取Virtual memory size的大小,指的是虚拟内存的页数*/
    snprintf(filename,256,"/proc/%d/stat",getpid());
    if ((fd = open(filename,O_RDONLY)) == -1) return 0;
    if (read(fd,buf,4096) <= 0) {
        close(fd);
        return 0;
    }
    close(fd);

    p = buf;
    count = 23; /* stat中第24个值记录的是虚拟内存的页数*/
    while(p && count--) 
    {
        p = strchr(p,' ');
        if (p) p++;
    }
    if (!p) return 0;
    x = strchr(p,' ');
    if (!x) return 0;
    *x = '\0';

    rss = strtoll(p,NULL,10);/*字符串转为数值类型*/
    rss *= page;//每个页的大小乘以页数,就是总的内存数。
    return rss;
}
#elif defined(HAVE_TASKINFO)
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/sysctl.h>
#include <mach/task.h>
#include <mach/mach_init.h>

size_t zmalloc_get_rss(void) {
    task_t task = MACH_PORT_NULL;
    struct task_basic_info t_info;
    mach_msg_type_number_t t_info_count = TASK_BASIC_INFO_COUNT;

    if (task_for_pid(current_task(), getpid(), &task) != KERN_SUCCESS)
        return 0;
    task_info(task, TASK_BASIC_INFO, (task_info_t)&t_info, &t_info_count);

    return t_info.resident_size;
}
#else
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();
}
#endif

11、zmalloc_get_fragmentation_ratio,计算某块内存占已经使用的内存的百分比。

float zmalloc_get_fragmentation_ratio(size_t rss) 
{
    return (float)rss/zmalloc_used_memory();
}

12、最后两个函数不知道干嘛用的,所以没分析

#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 = '\0';
                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

size_t zmalloc_get_private_dirty(void) {
    return zmalloc_get_smap_bytes_by_field("Private_Dirty:");
}

内存管理模块基本上分析完了,最后两个函数不知道是干嘛的等回头用的时候再说,总的来看,内存管理不是很麻烦,只给内存块加了一个头部来记录快大小,模块也充分考虑了性能,优先使用成熟的内存管理池,如果没有的话就优先使用编译器支持的原子操作,最后再都不支持的情况下才会使用互斥锁,如果在Redis使用的过程中互斥锁降低了性能,可以考虑升级gcc版本,或者安装两个成熟的内存管理库来提高性能。

posted @ 2016-03-13 21:07  扫地猿  阅读(253)  评论(0编辑  收藏  举报