开始进入zmalloc的实现,以下代码位于zmalloc.c


image.png
1553937439992626.png

位于最开头的是zlibc_free,注释里讲得很明白,在包含zmalloc.h之前,先定义zlibc_free并且包含glibc中的stdlib.h,这样可以给我们提供了至少glibc的free实现,如果zmalloc.h中不是用的glibc那一套malloc即ptmalloc,那么在zmlloc.h头文件被展开后,glibc的free实现就会被jemalloc或者其他标准内存分配器所隐藏,但是如果其他非标准内存分配器没有free,这样起码还有一个free能用。

zmalloc的其他依赖如下

image.png
1553938436490109.png

其中atomicvar.h前面已经剖析过,他里面封装了三种原子操作,pthread库在此是因为:在atomicvar.h中的原子操作为了代码简洁,统一都传一个mutex,虽然用不用是另外一回事。


image.png
1553938386144841.png

还记得前面说过的HAVE_MALLOC_SIZE宏吗?这个宏决定了当前使用的malloc函数是否提供了获取某个指针所管理内存的大小的函数,如果没有定义HAVE_MALLOC_SIZE宏的话,我们就需要一个非0的PREFIX_SIZE长度的一段空间作为内存块的前缀,一旦我们需要zmalloc一段大小为size字节的内存,我们就必须malloc size+PREFIX_SIZE字节的内存,其中开头PREFIX_SIZE字节的前缀就是用于记录这一块内存的大小,相反,如果定义了HAVE_MALLOC_SIZE宏的话,我们就让PREFIX_SIZE不存在即可,实际做法就是把它设定为0,而不是直接不定义,因为如果不定义的话就无法用一份代码统计后面的一些操作,下面会看到。

image.png
1553940528816712.png

对于tcmalloc与jemalloc应该显式隐藏原glibc自带的malloc、calloc、reallo、free等函数

image.png
1553939651308203.png

三个全局变量,其作用的都非常显然,分别用于记录当前已使用的内存大小、否需要线程安全、一把用于更新used_memory线程安全的锁(下面就可以看到这把锁就是用于原子操作)

image.png
1553939877500246.png

这两个宏函数alloc用于增加已使用的内存大小,free同于减少已使用的内存大小,并且可以支持线程安全


image.png
1553940218589385.png

当系统无法提供更多内存时,即zmalloc失败会调用一个处理函数,这个处理函数可以是简单的报错,也可以是去做一些释放的工作,zmalloc中的做法显然就是前者,不过这也是可以自己拓展的


image.png
1553940314344474.png

开辟一段内存

关于PREFIX_SIZE与HAVE_MALLOC_SIZE、zmalloc_oom_handler都已经很清楚的讲过,每一步都很清晰了,对于HAVE_MALLOC_SIZE和zmalloc_size还是有疑惑的话建议好好看看上一篇。


image.png
1553940450676234.png

通过calloc开辟一段内存。其他同上,每一步都很清晰,注意对used_memory的更新。


image.png
1553943891896173.png

zrealloc用于扩容或除此分配,每一步也都非常清晰,注意对used_memory的更新。


image.png
1553944121139294.png

如果你还没有把HAVE_MALLOC_SIZE宏与zmalloc_size宏函数的话,建议好好看看这段注释

这里定义个zmalloc_size函数实际上就是为了当zmalloc_size宏没有在zmalloc.h中被定义的时(事实上并不会,只是提供了扩展性)作为zmalloc_size,毕竟再zmalloc_size宏函数使用的地方编译器并不知道这是一个被宏替换的函数还是本来就是一个函数,因为那是预处理器做的事,这是C中常用的宏技巧之一。

看看这个

image.png
1553944722722665.png

看过一些源码的人对这种玩意对这样的东西都会挺有感觉的,一眼就知道这必然是在做什么补齐操作,STL中空间配置器对于下标处理也有一个往8的倍数补齐的类似操作,那么在者这是在做什么呢?

答案是将一个小于sizeof(long)的size补齐到sizeof(long)的大小,至于为什么,我认为这个很有必要留给你自己思考。


image.png
1553944629338253.png

对于没有定义HAVE_MALLOC_SIZE宏说明储存了PREFIX_SIZE头部,我们需要往前跳PREFIX_SIZE找到这段内存块真正的开头,释放的时候务必要记得加上PREFIX_SIZE。


image.png
1553945449973250.png

获取当前的used_memory,支持线程安全


image.png
1553945497156063.png

开启线程安全与设定一个内存溢出处理函数


首先说明一下RSS内存

RSS即Resident Set Size,常称为常驻物理内存,这部分内存除了进程本身占有的物理内存外还有他的所使用的共享内存加动态库占用的内存

PS:然而这个并不能很真实的衡量一个进程的内存使用情况,毕竟动态库的内存只用被加载一次,所有进程对于同一个动态库共用这一份内存,真正能直接反应一个进程的物理内存个人认为的还是USS,这个里面全部是一个进程独占的物理内存,很适合用于观测一个进程的内存使用情况

zmalloc中有一个函数用于获取RSS内存(RSS当然也不是完全没参考性),关于其实现说法如下

image.png
1553946281457765.png

也就是说咱们这个函数不追求性能,能获取就行,在真正繁忙、追求性能的情境下就不会去用这个函数。

这个函数的实现必须根据不同的操作系统进行相应实现,具体有这几种实现:

image.png
1553946421742467.png

这种方式在大多数GNU linux都适用,在/proc//stat中的对应字段去获取,我以前就用过这种方法读RSS等信息。

image.png
1553946573476927.png

该方法用于在有task_info的mac系统,不是搞苹果开发的一般都没见过这个

image.png
1553946748843345.png

最后在当前平台实在没有方法的话,咱们就用used_memory凑活着用吧,注释里说了,这个后果就是后面在计算碎片率的时候碎片率永远为1,如下

image.png
1553946943630865.png

这个函数就是用于计算当前内存的碎片率,如果我们获取RSS直接用的used_memory,那么碎片率当然是1……


在linux下,/proc//smaps这个文件有进程ID为pid的进程的内存非常详细的使用情况(若只用self代替则/proc/self/smaps是当前进程的smaps文件,非常好用了),具体到了每一段地址空间,任意在一个进程的smaps文件中截取一段地址空间的情况如下

image.png
1553950084591325.png

我们可以看到这算地址空间是它的栈地址空间,我们又看到了我们熟悉的RSS,关于smaps更多的具体字段在此就不展开了

zmalloc封装了一个获取指定进程的smaps中指定字段的函数

image.png
1553950371778936.png

若pid==-1则获取本进程的smaps中的指定字段值,否则就是获取进程ID为pid的进程的smaps中的指定字段值,我们可以看到若是没有定义smaps的宏,则该函数是无能为力的。

下面这个获取进程ID为pid的smaps中Private_Dirty字段值的函数便是调用上面的zmalloc_get_smap_bytes_by_field函数,Private_Dirty是私有内存中的脏页

image.png
1553950542989734.png


image.png
1553950967501210.png

最后这个函数看得人相当汗颜,它用于获取当前所在机器的RAM以字节的形式返回,这个函数支持FreeBSD、GNULinux、mac等,要获取一台机器的RAM并不是一个和普通或者常见的事,一般只有从事嵌入式开发的人才会去了解这个,这个函数中的某些地方的返回值连到现在都不是很确定其意义,可以看到其中有两个Failed?,虽然这个函数是后面一起维护的人写的,但是至少连作者都不是很明白这个……

最后,zmalloc也是一个可以直接用来使用的封装库,如果需要一个定制化的多版本malloc库,可以考虑对zmalloc进行改造。