redis源码分析之zmalloc

1,299 阅读3分钟

前言

zmalloc.czmalloc.h主要功能就是对原有库里的内存分配函数进行封装,形成独立的一套内存管理函数。由于redis要求满足跨平台性,而每个平台又会有自己的内存管理函数,所以在这两个文件中,将会看到大量的#ifdef,根据系统的不同,使用不同的内存管理函数,而封装接口都是一致的--zmalloc。下面将会对主要的几个函数进行源码分析。

update_zmalloc_stat_alloc

这个宏定义的功能就是计算当前使用的内存的大小。atomicIncr是一个原子操作,执行的是一个加法运算,used_memory是一个static变量,其数值代表所用的内存大小。在这之前对_n的修改是为了进行字节对齐。宏update_zmalloc_stat_free则执行内存减少的记录。

#define update_zmalloc_stat_alloc(__n) do { \
    size_t _n = (__n); \                                
    if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1)); \
    atomicIncr(used_memory,__n); \
} while(0)

zmalloc

zmalloczcalloczrealloc内部的对原有内存管理函数的处理都是一样的,所以这里只讨论zmalloc

 void *zmalloc(size_t size) {
    void *ptr = malloc(size+PREFIX_SIZE);         //#1
    if (!ptr) zmalloc_oom_handler(size);          //#2
#ifdef HAVE_MALLOC_SIZE                           //#3
    update_zmalloc_stat_alloc(zmalloc_size(ptr)); //#4
    return ptr;
#else                                             //#5
    *((size_t*)ptr) = size;                       //#6
    update_zmalloc_stat_alloc(size+PREFIX_SIZE);  //#7
    return (char*)ptr+PREFIX_SIZE;                //#8
#endif
}
  • #1:当需要分配内存时,需要多分配PREFIX_SIZE个字节用于记录本次内存分配的大小。有的系统内存管理函数自带计算分配字节的函数,所以PREFIX_SIZE有时是0。
  • #2: zmalloc_oom_handler为错误处理函数。
  • #3#4:这部分就是因为有的系统自带计算内存大小的函数,所以PREFIX_SIZE为0,直接返回指针就行。
  • #5#6:当需要手动计算内存大小时,就在指针首端存放内存大小信息。
  • #7:修改used_memory
  • #8:需要返回实际分配的内存开始地址,所以需要加上PREFIX_SIZE

zfree

内存释放函数,和zmalloc函数操作相反。为了直观的说明程序,只展示主要部分。

void zfree(void *ptr) {
    ...
    realptr = (char*)ptr-PREFIX_SIZE;              //#1
    oldsize = *((size_t*)realptr);                 
    update_zmalloc_stat_free(oldsize+PREFIX_SIZE); 
    free(realptr);  							   
    ...
}
  • #1: 需要注意的是ptr指针不能直接free,因为实际分配内存的大小需要减去PREFIX_SIZE,所以应该free减去过后的指针。

zmalloc_get_rss

该函数的功能返回该进程实际消耗的物理内存的大小,这也是rss的意思。为了直观的说明程序,只展示主要部分。

size_t zmalloc_get_rss(void) {
    int page = sysconf(_SC_PAGESIZE);                 //#1
    ...
    snprintf(filename,256,"/proc/%d/stat",getpid());  //#2
    if ((fd = open(filename,O_RDONLY)) == -1) return 0;
    if (read(fd,buf,4096) <= 0) {
       ...
    }
    close(fd);
    p = buf;                                         //#3
    count = 23; /* RSS is the 24th field in /proc/<pid>/stat */
    while(p && count--) {                            //#4
        p = strchr(p,' ');
        if (p) p++;
    }
    x = strchr(p,' ');
    *x = '\0';                                       //#5
    rss = strtoll(p,NULL,10);                        //#6
    rss *= page;                                     //#7
    return rss;
}
  • #1: 这是一个系统调用,系统中的内存是以页为单位,该系统调用返回一页的字节大小。
  • #2/proc/<pid号>/stat文件的第24行存放着进程的RSS。
  • #3:从文件中读取所有的内容。
  • #4~#6:主要就是找到第24行的内容,然后读取内容该行内容转化成整型。为了清除理解为啥要想这样计算,我随便打开了一个进程的stat文件,显示如下内容: 可见每一行之间其实只有一个' '字符相隔。所以只要找到第24个字符串,然后在结尾加上\0,通过strtoll函数将这个字符串转化成整型即可。
  • #7:页数乘上每页的大小即可获得物理内存消耗大小。

end