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版本,或者安装两个成熟的内存管理库来提高性能。