前言
zmalloc.c和zmalloc.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
zmalloc、zcalloc和zrealloc内部的对原有内存管理函数的处理都是一样的,所以这里只讨论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:页数乘上每页的大小即可获得物理内存消耗大小。