【redis6.0.6】redis源码慢慢学,慢慢看 -- 第二天:空间配置(zmalloc)

举报
看,未来 发表于 2020/12/30 00:42:53 2020/12/30
【摘要】 文章目录 前言:allocatorzmalloc.h中的函数声明 alloczmalloc注1:PREFIX_SIZE注2:oom处理方法注3:update_zmalloc_stat_alloc方法注4:atomicIncr方法 zcalloczrealloc注5:zfree方法注6:zmalloc_size方法注7:update_zmalloc_stat...

在这里插入图片描述

前言:

先附上人家的版权:

  • Copyright © 2009-2010, Salvatore Sanfilippo
  • All rights reserved.

好极,再附上我准备的配套资源:

为了大家看文中那一堆的“#”不至于晕掉,建议先看一下这篇:讲通C/C++预编译/条件编译指令 #ifdef,#ifndef,#endif,#define,…

为了大家更好的理解文中各类名词与思想,建议大家看一下这篇比较成熟的空间配置器:走进STL - 空间配置器取材于侯捷老师的《STL源码剖析》,STL的空间配置比redis要复杂,不过这篇走进STL - 空间配置器以我能看得懂的方式讲述了STL空间配置器的精妙。


分割线


allocator

Redis在这个版本使用三种选择作为allocator,
a) tcmalloc:由google用于优化C++多线程应用而开发。Redis 需要1.6以上的版本。
b) jemalloc:第一次用在FreeBSD 的allocator,于2005年释出的版本。强调降低碎片化,可扩展的并行支持。Redis需要2.1以上版本。
c) libc:最常使用的libc库。GNU libc,默认使用此allocator。

来看一下zmalloc.h中关于这一部分的代码:

#if defined(USE_TCMALLOC)
	#define ZMALLOC_LIB ("tcmalloc-" __xstr(TC_VERSION_MAJOR) "." __xstr(TC_VERSION_MINOR))
	#include <google/tcmalloc.h>
	#if (TC_VERSION_MAJOR == 1 && TC_VERSION_MINOR >= 6) || (TC_VERSION_MAJOR > 1)
		#define HAVE_MALLOC_SIZE 1
		#define zmalloc_size(p) tc_malloc_size(p)
	#else
		#error "Newer version of tcmalloc required"
	#endif

#elif defined(USE_JEMALLOC)
	#define ZMALLOC_LIB ("jemalloc-" __xstr(JEMALLOC_VERSION_MAJOR) "." __xstr(JEMALLOC_VERSION_MINOR) "." __xstr(JEMALLOC_VERSION_BUGFIX))
	#include <jemalloc/jemalloc.h>
	#if (JEMALLOC_VERSION_MAJOR == 2 && JEMALLOC_VERSION_MINOR >= 1) || (JEMALLOC_VERSION_MAJOR > 2)
		#define HAVE_MALLOC_SIZE 1
		#define zmalloc_size(p) je_malloc_usable_size(p)
	#else
		#error "Newer version of jemalloc required"
	#endif

	#elif defined(__APPLE__)
		#include <malloc/malloc.h>
		#define HAVE_MALLOC_SIZE 1
		#define zmalloc_size(p) malloc_size(p)
#endif

#ifndef ZMALLOC_LIB
	#define ZMALLOC_LIB "libc"
	#ifdef __GLIBC__
		#include <malloc.h>
		#define HAVE_MALLOC_SIZE 1
		#define zmalloc_size(p) malloc_usable_size(p)
	#endif
#endif

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

由宏USE_TCMALLOC,USE_JEMALLOC和__APPLE__控制要编译进Redis的allocator。

前两个宏从make 传入,后面一个是操作系统宏,若是Apple,则可以提供一个 malloc_size (),用于查看指针指向内存的大小。此函数在jemalloc和tcmalloc中都有提供,但glibc中不提供此函数,宏HAVE_MALLOC_SIZE即是用于控制此函数。

zmalloc.h中的函数声明

zmalloc.h中出了allocator的选择,还有alloc的函数声明:
(截取)

void *zmalloc(size_t size); /* 调用zmalloc申请size个大小的空间 */
void *zcalloc(size_t size); /* 调用系统函数calloc函数申请空间 */
void *zrealloc(void *ptr, size_t size); /* 原内存重新调整空间为size的大小 */
void zfree(void *ptr); /* 释放空间方法,并更新used_memory的值 */
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_private_dirty(void); /* 获取私有的脏数据大小 */
void zlibc_free(void *ptr);  /* 原始系统free释放方法 */

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

在这里还要介绍几个概念和变量:
在zmalloc.c中可以找到它们。

static size_t used_memory = 0;	//系统已经使用了多少的内存
static int zmalloc_thread_safe = 0;	//这指的是线程安全模式状态
pthread_mutex_t used_memory_mutex = PTHREAD_MUTEX_INITIALIZER;	//锁

  
 
  • 1
  • 2
  • 3

alloc

Redis中使用两个函数分配内存:zmalloc和zcalloc。

zmalloc

/* 调用zmalloc申请size个大小的空间 */
void *zmalloc(size_t size) { void *ptr = malloc(size+PREFIX_SIZE);	//实际调用的还是malloc函数 //注1:PREFIX_SIZE if (!ptr) zmalloc_oom_handler(size);	//如果申请的结果为null,说明发生了oom,调用oom的处理方法
		//注2:oom处理方法

#ifdef HAVE_MALLOC_SIZE		//HAVE_MALLOC_SIZE,往前翻翻
	update_zmalloc_stat_alloc(zmalloc_size(ptr));	//更新used_memory的大小	zmalloc_size这个函数后面会讲 return ptr; //注3:update_zmalloc_stat_alloc
#else
	*((size_t*)ptr) = size;
	update_zmalloc_stat_alloc(size+PREFIX_SIZE);
	return (char*)ptr+PREFIX_SIZE;
#endif
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

注1:PREFIX_SIZE

#ifdef HAVE_MALLOC_SIZE
	#define PREFIX_SIZE (0)
#else
	#if defined(__sun) || defined(__sparc) || defined(__sparc__)
		#define PREFIX_SIZE (sizeof(long long))
	#else
		#define PREFIX_SIZE (sizeof(size_t))
	#endif
#endif

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

这么做的原因是因为:(内存对齐)针对linux和sun平台要记录分配空间大小。对于linux,使用sizeof(size_t)定长字段记录;对于sun os,使用sizeof(long long)定长字段记录。因此当宏HAVE_MALLOC_SIZE没有被定义的时候,就需要在多分配出的空间内记录下当前申请的内存空间的大小。

注2:oom处理方法

static void (*zmalloc_oom_handler)(size_t) = zmalloc_default_oom;	

static void zmalloc_default_oom(size_t size) { fprintf(stderr, "zmalloc: Out of memory trying to allocate %zu bytes\n", size); fflush(stderr); abort();
}
//其实我并不知道为什么要分成两个函数来写

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

注3: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)); \ atomicIncr(used_memory,__n); \		//注4:atomicIncr
} while(0)

  
 
  • 1
  • 2
  • 3
  • 4
  • 5

函数解释:
因为sizeof(long) == 8 [64位系统中],所以其实第一个if的代码等价于if(_n&7) _n += 8 - (_n&7); 这段代码就是判断分配的内存空间的大小是不是8的倍数。如果内存大小不是8的倍数,就加上相应的偏移量使之变成8的倍数。
这是为了精确计算系统分配的空间,因为malloc会自动做内存对齐,分配空间可能会比实际需要数值略多一点。

注4:atomicIncr方法

位于atomicvar.h

/* atomicIncr(var,count) -- Increment the atomic counter  增加原子计数器*/

  
 
  • 1

我就直接选那个默认的了,前边儿还有俩。

#define atomicIncr(var,count) do { \ pthread_mutex_lock(&var ## _mutex); \	//线程安全,两个 ## 配套资料中有说 var += (count); \ pthread_mutex_unlock(&var ## _mutex); \
} while(0)

  
 
  • 1
  • 2
  • 3
  • 4
  • 5

分割线


zcalloc

zcalloc同样也是用来分配内存的,但是它调用了calloc,会分配n个尺寸为size大小的空间,且返回分配的空间头部指针。源码中n为1,size为size+PREFIX_SIZE。calloc和malloc的区别在于calloc会把分配的空间初始化为0,而malloc不会。

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
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

就不多家赘述了。


分割线


zrealloc

zrealloc函数是修改所指向空间的大小。这里首先获取原空间的大小oldsize,再分配尺寸为size的新空间。如果成功,则老空间所指内容会被复制进新空间,且老空间指针失效,新空间指针为newptr。函数会根据HAVE_MALLOC_SIZE宏的定义确定是否需要额外分配PREFIX_SIZE大小设置空间的长度。

/*调用zrealloc重新分配空间*/
void *zrealloc(void *ptr, size_t size) {
#ifndef HAVE_MALLOC_SIZE void *realptr;
#endif size_t oldsize; void *newptr; if (size == 0 && ptr != NULL) {	//如果是要清空 zfree(ptr);		//注5:zfree return NULL; } if (ptr == NULL) return zmalloc(size);	//如果是要给一个空指针分配空间

//接下来才是正事
#ifdef HAVE_MALLOC_SIZE oldsize = zmalloc_size(ptr);	//注6:zmalloc_size,前面注释太多了,负载均衡一下哈哈哈 newptr = realloc(ptr,size);		//最终还是调用了realloc函数 if (!newptr) zmalloc_oom_handler(size); update_zmalloc_stat_free(oldsize);	//注7:update_zmalloc_stat_free update_zmalloc_stat_alloc(zmalloc_size(newptr));	//注8:update_zmalloc_stat_alloc return newptr;
#else realptr = (char*)ptr-PREFIX_SIZE;	//将当前指针 ptr 向前偏移 PREFIX_SIZE 个字节,得到真正内存分配的起始地址 realptr; oldsize = *((size_t*)realptr);		//取 realptr 位置上的值作为该连续内存块的大小,并且记录在 oldsize 中 newptr = realloc(realptr,size+PREFIX_SIZE); //realloc 在 realptr 的位置分配 size+PREFIX_SIZE 的空间,返回 newptr。size 的值有可能比 oldsize 大或小,newptr 和 ptr 的值可能相同也可能不同,这个完全取决于 realloc 的实现。 if (!newptr) zmalloc_oom_handler(size); *((size_t*)newptr) = size;	//将 size 记录在 newptr 指向的位置上。 update_zmalloc_stat_free(oldsize+PREFIX_SIZE); update_zmalloc_stat_alloc(size+PREFIX_SIZE); return (char*)newptr+PREFIX_SIZE;
#endif
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

注5:zfree方法

在使用jemalloc 和 tcmalloc时,内存申请时的长度不加上PREFIX_SIZE,直接free ()即可,而glibc 要将指针偏移回PREFIX_SIZE,再调用 free ():

void zfree(void *ptr) {
#ifndef HAVE_MALLOC_SIZE void *realptr; size_t oldsize;
#endif if (ptr == NULL) return;
#ifdef HAVE_MALLOC_SIZE 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
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

注6:zmalloc_size方法

/* Provide zmalloc_size() for systems where this function is not provided by
 * malloc itself, given that in that case we store a header with this
 * information as the first bytes of every allocation. */

  
 
  • 1
  • 2
  • 3

英语自己看,翻译那六篇 redis.conf 已经耗尽了我近期对英语的热情。

大概意思就是:这个函数是为glibc定制的,只有用这个库时,才能使用这个函数。

#ifndef HAVE_MALLOC_SIZE	//呐,看到没
size_t zmalloc_size(void *ptr) { void *realptr = (char*)ptr-PREFIX_SIZE; size_t size = *((size_t*)realptr); /* Assume at least that all the allocations are padded at sizeof(long) by * the underlying allocator. */ if (size&(sizeof(long)-1)) size += sizeof(long)-(size&(sizeof(long)-1)); return size+PREFIX_SIZE;
}
size_t zmalloc_usable(void *ptr) { return zmalloc_size(ptr)-PREFIX_SIZE;
}
#endif

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

原理即时将指针偏移PREFIX_SIZE ,得到块大小,再加上PREFIX_SIZE长度即得到真实的内存大小。

注7:update_zmalloc_stat_free方法

update_zmalloc_stat_free会首先根据long型对齐占用的空间大小,然后对used_memery减去计算好的尺寸。

#define update_zmalloc_stat_free(__n) do { \ size_t _n = (__n); \ if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1)); \ atomicDecr(used_memory,__n); \			//注9:atomicDecr,还负载均衡呢。。。
} while(0)

  
 
  • 1
  • 2
  • 3
  • 4
  • 5

注8:update_zmalloc_stat_alloc方法

update_zmalloc_stat_free 的作用和 update_zmalloc_stat_alloc 正好相反,都是操作 use_memory 这个静态变量的。free 是减, alloc 是加。

#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); \	//atomicIncr前面讲过了
} while(0)

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
atomicDecr方法

不多说,放码过去。

#define atomicDecr(var,count) do { \ pthread_mutex_lock(&var ## _mutex); \ var -= (count); \ pthread_mutex_unlock(&var ## _mutex); \
} while(0)

  
 
  • 1
  • 2
  • 3
  • 4
  • 5

分割线


可累死我了,还有啥?还有啥!!!

还有几个零零碎碎的。。

剩下的打包一下

zstrdup

dup()和dup2()不陌生吧。
zstrdup函数是把一份空间的内容,分配并拷贝内容至新空间,并返回新空间的指针。

char *zstrdup(const char *s) { size_t l = strlen(s)+1; char *p = zmalloc(l); memcpy(p,s,l); return p;
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

zmalloc_used_memory

zmalloc_used_memory 函数用来获取当前使用的内存总量,其中__sync_add_and_fetch就是宏update_zmalloc_stat_add。

size_t zmalloc_used_memory(void) { size_t um; atomicGet(used_memory,um); return um;
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
#define atomicGet(var,dstvar) do { \ pthread_mutex_lock(&var ## _mutex); \ dstvar = var; \ pthread_mutex_unlock(&var ## _mutex); \
} while(0)

  
 
  • 1
  • 2
  • 3
  • 4
  • 5

do…while(0)这个我之前还真没想到,所以配套资料里面没有,具体看这里:do…while(0)

zmalloc_get_rss

这个函数可以获取当前进程实际所驻留在内存中的空间大小,即不包括被交换(swap)出去的空间。该函数大致的操作就是在当前进程的 /proc//stat 【表示当前进程id】文件中进行检索。该文件的第24个字段是RSS的信息,它的单位是pages(内存页的数目)。如果没从操作系统的层面获取驻留内存大小,那就只能绌劣的返回已经分配出去的内存大小。

代码咱就不看了,咱看一下警告:

/* WARNING: the function zmalloc_get_rss() is not designed to be fast
 * and may not be called in the busy loops where Redis tries to release
 * memory expiring or swapping out objects.
 *
 * For this kind of "fast RSS reporting" usages use instead the
 * function RedisEstimateRSS() that is a much faster (and less precise)
 * version of the function. */

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

休息了,休息啦,累啊,不过是真的爽。

如果觉得我写的还过得去,欢迎各位继续跟进哦,这是我的学习路线:【redis6.0.6】redis6.0.6源码,慢慢看,慢慢聊 – 第一天:学习路线

也欢迎大家赞评收,顺手点个关注那我就特别开心了。

在这里插入图片描述

文章来源: lion-wu.blog.csdn.net,作者:看,未来,版权归原作者所有,如需转载,请联系作者。

原文链接:lion-wu.blog.csdn.net/article/details/108278852

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。