继上一章的了解,我们继续了解我们的malloc。在上一章的了解到了,大部分的宏定义与解释。在这里我们将要看Malloc中Bins与Aerna的整体设计,和一些最新一版Malloc源码修改,不同之处。先看箱子的整体设计与阐述。

如下:

/*BinsThe bins are an array of pairs of pointers serving as theheads of (initially empty) doubly-linked lists of chunks, laid outin a way so that each pair can be treated as if it were in amalloc_chunk. (This way, the fd/bk offsets for linking bin headsand chunks are the same).Bins for sizes < 512 bytes contain chunks of all the same size, spaced8 bytes apart. Larger bins are approximately logarithmicallyspaced. (See the table below.)Bin layout:64 bins of size       832 bins of size      6416 bins of size     5128 bins of size    40964 bins of size   327682 bins of size  2621441 bin  of size what's leftThere is actually a little bit of slop in the numbers in bin_indexfor the sake of speed. This makes no difference elsewhere.The special chunks `top' and `last_remainder' get their own bins,(this is implemented via yet more trickery with the av array),although `top' is never properly linked to its bin since it isalways handled specially./*箱子容器是指针对的数组,用作(最初为空的)双链块列表的头在某种程度上,每一对都可以被视为在一个malloc_chunk。(这样,fd/bk偏移量用于连接料仓头块也是一样的)。大小箱;512字节包含相同大小的块,以空格分隔8字节间隔。较大的箱子近似对数间隔。(见下表)本布局:64个8号的箱子32个64号的箱子16个512号的箱子8个4096号的箱子4个32768号的箱子两个262144号的箱子剩下的是1箱大小bin_index中的数字实际上有一点倾斜为了速度。这在其他地方没有区别。特殊的块' top'和' last_remainder'有它们自己的箱子,(这是通过更多的av数组实现的),虽然' top'从来没有正确地链接到它的bin,因为它是总是特别处理。* /

 Bins 箱子被分为128个,前64个小于512字节的被称为小箱子。这一版的实现中,使用了数组结构来存放这些箱子。

Bin layout:

64 bins of size       8      
    32 bins of size      64
    16 bins of size     512
     8 bins of size    4096
     4 bins of size   32768
     2 bins of size  262144
     1 bin  of size what's left

注意了这里的 层级实现,在前64个箱子的中,ta们每个相邻箱子之间间隔8字节对齐。而后面的大箱子中从64往后的32个箱子以64字节间隔对齐,再往后以此类推,不断以对数叠加间隔。最后一个箱子多大? 有多大算多大。正如解释的那样(    1 bin  of size what's left)

8字节不是一个箱子的大小,是相邻箱子之间的间隔。

如下图中,去查阅了原作者的实现方式,默认的一个箱子最小是16字节。最后一个箱子 最大能分配到多少(2的31次方,也就是一个无符号int 的大小)

所以箱子的整体设计这个样子的。前64个按照约定的8字节对齐方式进行排列,不断叠加。再大于512字节之后,调整了对齐间隔,用64字节作为间隔。再往后就是512,4096,32768,262144为间隔,直到剩余最后一个箱子的大小。如malloc作者对malloc实现的详述。

这里提到了bin_index(sz) 也就是箱子的索引。 其实这个函数, 就是为了在这126箱子中,找一个合适的位置。根据申请的内存的大小,在这里不断的计算,最后得出会放在那个一个箱子里。注意了这里是126,不是128;bin(0),不存在,bin(1)为无序链表,所以这就是少了两个。

因为是双链表结构,头与尾被进行了特殊对待。这并不会浪费内存、在系统初始化时会调用到,个人使用时,并不会。头与尾用于了系统的扩容管理,记录下一块位置等等,这里在源代码中也有解释到。

/*Indexing into bins //索引到容器中
*/#define bin_index(sz)   //宏定义,这里表示了替换 bin_index(sz) 申请的内存大小                                                      \
(((((unsigned long)(sz)) >> 9) ==    0) ?       (((unsigned long)(sz)) >>  3): \((((unsigned long)(sz)) >> 9) <=    4) ?  56 + (((unsigned long)(sz)) >>  6): \((((unsigned long)(sz)) >> 9) <=   20) ?  91 + (((unsigned long)(sz)) >>  9): \((((unsigned long)(sz)) >> 9) <=   84) ? 110 + (((unsigned long)(sz)) >> 12): \((((unsigned long)(sz)) >> 9) <=  340) ? 119 + (((unsigned long)(sz)) >> 15): \((((unsigned long)(sz)) >> 9) <= 1364) ? 124 + (((unsigned long)(sz)) >> 18): \126)

我解释一下,这个函数在干什么,

#define bin_index(sz)   //首先宏定义,这里表示了替换 bin_index(sz) 申请的内存大小 ,函数体 unsigned long (sz) 意思就是,不管传进来的数值为多少,都会被替换为 无符号的long类型,然后右移>>9 == 0  ? (((unsigned long)(sz)) >>  3): \ 三目运算符表达式, 后面一大串实现, 宏定义中 \ 表示连续。最终求得一个箱子的具体数值。代码已经被我单独拎出来了,可以自己测。

这里一长串的括号其实可以先不管,我们要的是具体实现。这里注意的是,代码中看似有很强的数学概念,但其实都是经验之得,得出来的一个结论。包括上面的中索引数字,箱子在内存的设计。所以,这是一份经得起推敲,和不断地实践得出来的一份代码。我看到的这一版中的代码算是比较老旧的一版,代码结构比较清楚,看起来能尽快理解。不过这份老代码中包含了malloc的初衷与设计,也正是学习的不错例子。

我们再来看一眼,最新一版的实现与设计。这一部分的注释没变,核心思想没变,实现方式大大不同。

这里的bin_inde写了3个,其实就是在区分平台,不同的32与64位机器,对应不同的索引。当时看到了这里,想怎么不用模板实现一下呢?后来一想这是C,还有就是函数实现大致相同,其中有个别不同,区别就在这里的数值上,如下所示:

但,这部分代码后期,或者将来肯定要被优化掉。重复性代码就不应该出现在源代码中。

继续往下看,找箱子,我找来找去,箱子呢?what? binmap?是的,就是这个玩意,在我目前看到的最新版中,这也算当下最新的一版了。箱子被设计成binmap这样的数据结构。这会更快吗?是的。

找着找着,我就发现了,新奇的东西,发现了新大陆,这不就是大名鼎鼎的 Fastbins吗。网上一大堆关于Fastbin 的漏洞攻击。这里又不得不说一下关于Fastbin 攻击的事情了。fastbin attack 存在的原因在于 fastbin 是使用单链表来维护释放的堆块的,并且由 fastbin 管理的 chunk 即使被释放,其 next_chunk 的 prev_inuse 位也不会被清空。不知道是关于Fastbin 采用了的不够成熟的思想,还是本来就有的漏洞,成了被人攻击的把柄。所以,还是看一下源代码中的解释吧!

/*FastbinsAn array of lists holding recently freed small chunks.  Fastbinsare not doubly linked.  It is faster to single-link them, andsince chunks are never removed from the middles of these lists,double linking is not necessary. Also, unlike regular bins, theyare not even processed in FIFO order (they use faster LIFO) sinceordering doesn't much matter in the transient contexts in whichfastbins are normally used.Chunks in fastbins keep their inuse bit set, so they cannotbe consolidated with other free chunks. malloc_consolidatereleases all chunks in fastbins and consolidates them withother free chunks.Fastbins一个包含最近释放的小块的列表数组。Fastbins不是双联的。单链接它们更快,而且由于数据块永远不会从这些列表的中间删除,不需要双重连接。而且,不像普通的垃圾桶,它们甚至没有按照先进先出的顺序处理(他们使用更快的后进先出),因为顺序在瞬态环境中并不重要Fastbins通常被使用。fastbins中的块保持它们的使用位设置,所以它们不能与其他自由块合并。malloc_consolidate释放fastbins中的所有块并将它们合并其他自由块。*/typedef struct malloc_chunk *mfastbinptr;
#define fastbin(ar_ptr, idx) ((ar_ptr)->fastbinsY[idx])

这里,说到Fastbins是一个用于释放chunk的单链表,为什么不是双链表,因为单链表更快。说的是简单又快捷。

下面一段是原作者(Doug Lea)原话:对于缓存在内存分配中应用。我想应该能解释对于Fastbins的设计


缓存

在基本算法的最直接版本中, 每个释放的块都会立即与邻居合并到 形成可能的最大未使用块。同样,块 仅在以下情况下创建(通过拆分较大的块) 明确要求。

拆分和合并块的操作需要时间。这一次 有时可以通过同时使用两者之一来避免开销 两种缓存策略:

延迟合并

与其合并释放的块,不如将它们留在他们的 当前大小希望另一个请求相同的大小 很快就会出现。这节省了合并,后来的分裂, 以及找到不完全匹配的块所需的时间 来拆分。

预分配

而不是逐个拆分新块,而是预先拆分 一次很多。这通常比一次执行一个要快。

因为分配器中的基本数据结构允许 随时合并,在 、 或 对应的缓存中的任何一个 启发式方法易于应用。mallocfreerealloc

缓存的有效性显然取决于 相对于工作进行拆分、合并和搜索 需要跟踪缓存的区块。此外,有效性 不太明显地取决于在决定何时使用的策略 缓存与合并它们。.

缓存在连续的程序中可能是一个好主意 分配和释放只有几个大小的区块。 例如,如果您编写一个程序 分配和释放许多树节点,您可能会决定 值得缓存一些节点,假设你知道一种快速的方法 来做到这一点。但是,如果不了解该程序,就无法知道它是否是一个好东西 合并缓存的小块以满足 较大的请求,或者是否应接受较大的请求 从其他地方。而且分配者很难 对此事做出更明智的猜测。例如,它 对于分配器来说,确定总计多少成本同样昂贵 连续的空间将通过合并块来获得,因为它 只是将它们合并,然后重新拆分它们。malloc

以前版本的分配器使用了一些 搜索排序启发式方法,可以充分猜测 缓存,尽管偶尔会出现糟糕的最坏情况 结果。但随着时间的推移,这些启发式方法似乎是 在实际负载下效率降低。这可能是因为 严重依赖malloc的实际程序越来越趋向于发展 以使用更多种类的块大小。例如,在C++ 程序,这可能对应于程序的趋势 使用越来越多的类。不同的类倾向于 具有不同的大小。


距离现在已有30年左右了。今天正好是23年4月1日,愚人节。笑死,我真希望上面所说的漏洞已经在最新版中修复了。 在上面的原话中,可以看到作者把自己的想法都实现了。这里关键点,在于对释放内存的处理,作者是这样想的,在对一个刚刚释放的内存块,并没有对ta进行立即的回收,而是把ta留在了与ta相同大小的内存块中。这样做有什么好处?那就如上所说,省略的这一次合并节省了合并,再分裂,再搜索的时间,这样一步换三步的策略。明显带来一个飞快的提升。那么这个策略就叫做 延迟合并,所以并不是不合并,而是等等再合并。这就cache缓存技术了。 但,这里的这一步的省略却被搞漏洞的选手抓到了。这又要窜到安全领域,那些搞汇报的那些叼毛身上了,那么关于fastbin attack 我会在下面放一个链接。其实,原理很简单,就是在利用指针对内存进行修改,为什么能被修改到呢?因为有了延迟合并,重复的释放指针,会指向同一块内存。所以,应该很明白了吧!你说什么?源代码还能有这样错误??? 关于指针重复释放的问题,在初学就已经知道。但,避免却成了终身的问题。 可见 指针 真是个万恶的东西。这帮人还帮ta起了个名字 。

指针又是一种远超出于语言本身的一种东西,ta的强大之处,远超出的语言本身对ta 的限定。大致就这些吧! 不足的地方,再上来补。

关于fastbin attack攻击的链接:

https://ctf-wiki.org/pwn/linux/user-mode/heap/ptmalloc2/fastbin-attack/

还是不要看了,看了之后不敢再用Malloc了。里面详细指出了malloc源码中每一处被利用的细节。没办法开源的代码,谁都能读。也许这能使malloc源码的变的更加强壮。

值得注意的是,jemalloc中好像并没有此问题。更好的处理了内存碎片的问题。那么到如今已经有不少厂家在实现自己的malloc 了。所以上面的问题,也并不会成为多大的问题。

Arena (竞技场)

常言道:堆为何物?写了几十年代码,不知道堆长啥样。 源代码对ta进行了简单明了的解释。

堆,一块连续的虚拟内存空间,包含可合并的malloc chunk,由mmap映射。

可见mmap是 比malloc还要更高一级的系统函数。malloc中会用到很多系统函数,就不一一列举了。暂时就到这里吧..

#define top(ar_ptr) ((ar_ptr)->top)/* A heap is a single contiguous memory region holding (coalesceable)malloc_chunks.  It is allocated with mmap() and always starts at anaddress aligned to HEAP_MAX_SIZE.  */typedef struct _heap_info
{mstate ar_ptr; /* Arena for this heap. */struct _heap_info *prev; /* Previous heap. */size_t size;   /* Current size in bytes. */size_t mprotect_size; /* Size in bytes that has been mprotectedPROT_READ|PROT_WRITE.  */size_t pagesize; /* Page size used when allocating the arena.  *//* Make sure the following data is properly aligned, particularlythat sizeof (heap_info) + 2 * SIZE_SZ is a multiple ofMALLOC_ALIGNMENT. */char pad[-3 * SIZE_SZ & MALLOC_ALIGN_MASK];
} heap_info;/* Get a compile-time error if the heap_info padding is not correctto make alignment work as expected in sYSMALLOc.  */
extern int sanity_check_heap_info_alignment[(sizeof (heap_info)+ 2 * SIZE_SZ) % MALLOC_ALIGNMENT? -1 : 1];

Malloc源码解读三——Bins与Arena相关推荐

  1. ORB SLAM2源码解读(三):Frame类

    文章目录 前言 构造函数 双目相机 RGBD相机 单目相机 ExtractORB:提取特征点 ComputeBoW:计算词袋数据 SetPose:设置相机外参 isInFrustum:判断一个MapP ...

  2. iOS内存管理和malloc源码解读

    0. iOS内存基本原理 在接触iOS开发的时候,我们都知道"引用计数"的概念,也知道ARC和MRR,但其实这仅仅是对堆内存上对象的内存管理.用WWDC某Session里的话说,这 ...

  3. 《Malloc 源码解读》

    拜读完malloc的部分源码,影响颇为深刻.也验证了一些自己的想法.先把源码中的一部分拿出来,这不过仅仅是个开始. /* V2.6.4-pt2 Sat Dec 14 1996This work is ...

  4. jQuery源码解读三选择器

    直接上jQuery源码截取代码 // Map over jQuery in case of overwrite _jQuery = window.jQuery, // Map over the $ i ...

  5. BaseRecyclerViewAdapterHelper源码解读(四) 上拉加载更多

    上拉加载 上拉加载无需监听滑动事件,可自定义加载布局,显示异常提示,自定义异常提示. 此篇文章为BaseRecyclerViewAdapterHelper源码解读第四篇,开源库地址,如果没有看过之前3 ...

  6. Bert系列(三)——源码解读之Pre-train

    https://www.jianshu.com/p/22e462f01d8c pre-train是迁移学习的基础,虽然Google已经发布了各种预训练好的模型,而且因为资源消耗巨大,自己再预训练也不现 ...

  7. faster rcnn源码解读(三)train_faster_rcnn_alt_opt.py

    转载自:faster rcnn源码解读(三)train_faster_rcnn_alt_opt.py - 野孩子的专栏 - 博客频道 - CSDN.NET http://blog.csdn.net/u ...

  8. faster rcnn fpn_Faster-RCNN详解和torchvision源码解读(三):特征提取

    我们使用ResNet-50-FPN提取特征 model = torchvision.models.detection.fasterrcnn_resnet50_fpn(pretrained=True) ...

  9. 目标检测之DarkNet-DarkNet源码解读<一>测试篇

    目标检测-DarkNet源码解读 DarkNet源码解读 1.一些思考  1.1 DarkNet的本质  1.2 深度学习分为两条线  1.3 检测任务的步骤 2.代码走读  2.1 程序入口  2. ...

最新文章

  1. ios pusher使用_如何使用JavaScript和Pusher构建实时评论功能
  2. Hadoop参数汇总
  3. 《JavaScript权威指南》学习笔记——Day2
  4. mysql server安装不成功,解决Mysql5.7.17在windows下安装启动时提示不成功问题
  5. 你见过的最全面的 Python 重点
  6. 为什么SOFA RPC调用30s还不超时?
  7. 游戏市场阴影下的手游厂商,和他们无法触碰的未来
  8. 如何保证执行异步方法时不会遗漏 await 关键字
  9. php与mysql手册下载地址_PHP与Mysql的连接
  10. 城市运行一网统管_全国率先!“一屏观天下、一网管全城”,临港城市运行“一网统管”平台启动建设...
  11. android sim卡分析,Android 判断SIM卡属于哪个移动运营商详解及实例
  12. 如何完全卸载oracle和删除oracle在注册表中的注册信息
  13. android逆向基础教程一
  14. web前端期末大作业 HTML+CSS+JavaScript仿唯品会购物商城网页设计实例 企业网站制作
  15. Paypal联手信用卡Discover 打压Square和星巴克威风
  16. 春风十里不如Node中的一股清流
  17. 关于树莓派4B的屏幕输入信号源由HDMI变为AV2的处理方法
  18. 2017秋招第一面--阿里巴巴
  19. 使用PyCharm编写Scrapy爬虫程序,爬取古诗词网站
  20. Echarts formatter

热门文章

  1. AMBA总线协议 之 APB总线协议
  2. Mybatis--SqlSession对象创建过程
  3. python颜色填充代码_python中如何给图形填充颜色
  4. 创建或打开android模拟器时遇到的问题,以及打开后遇到的Failed to install FragmentTest.apk on device 'emulator-5554': timeout
  5. 七彩虹战斧C.AB350M-HD魔音版V14A刷Bios教程
  6. sop8封装的8脚蓝牙芯片KT6368A的低功耗测试说明
  7. win10推送_win10无线镜像投屏电视
  8. 在线炒股天载分析市场呈现出一片跌势
  9. 将React Native 集成进现有OC项目中(过程记录) 、jsCodeLocation 生成方式总结
  10. 前缀和与差分——最大加权矩形