VC6_SBH 概述
SBH 是 Small Block Heap 的缩写,进行操作系统之上的小区块内存的管理。在 VC6 中可找到源码实现,升级版本 VC10 中统一使用系统API进行内存申请, SBH 被整合到了操作系统内部。
VC6 部分实现
VC10 部分实现
注意: __heap_init
和 __ioinit
在 VC6 和 VC10 中都存在
VC6_SBH 分解
heap_init(...)
_heap_init(...)
、__sbh_heap_init()
说明 :
CRT 会先为自己创建一个 _ctrheap "独特"内存(HeapCreate), 然后从中配置 SBH 所需的 headers, regions(__sbh_heap_init).应用程序内存申请时如果 size > threshold 就以 HeapAlloc() 从 _crtheap 取.若size <= thread 就从 SBH 取(实际区块来自 VirtualAlloc)
1. 操作系统内存管理的一些概念:可创建一块独特的空间并给其命名,之后相关操作所需的内存都取自这里【逻辑上分类】
1.1 HeapCreate 要求操作系统分配一块内存
1.2 __crtheap 全局指针变量,指向分配的内存空间,专为 CRT(C Run Time) 使用
2. __sbh_heap_init,到 __ctrheap 指向的内存获取 16 * sizeof(Header) 空间
2.1 每个 SBH 中还有 16 个HEADER (当16 个HEADER用光后,会再进行分配)
header 结构分析
1. 在 _sbh_heap_init() 中含有对 Header 的配置,包括对所属的 region 的配置
2. pHeadData 指向实际所管理的可分配的内存区块
3. pRegin 指向管理 Region 区块,其中存储了所管理内存的控制信息
_heap_init() 和 __sbh_heap_init() 调用完成之后就可以进行下一步 _ioinit(), 使用 SBH 对小区块的内存请求进行响应和分配了
_ioinit()
_ioinit() 完成了应用程序启动后的“第一次”内存申请
_malloc_dbg()
示例中按照 dbg 模式进行
_heap_alloc_dbg(nSize)
首先了解 SBH 对所分配的内存块的结构设计,可分为三部分:
_CrtMemBlockHeader、data[nDateSize]、anotherGap[nNoMansLandSize]。
由此可以知道当应用程序申请 data[nSize] 实际消耗的内存空间大小为 blockSize = sizeof(_CrtMemBlickHeader) + nSize + nNoMansLandSize;
由此又可知 _heap_alloc_dbg(nSize) 的主要任务,调整申请的内存大小,增加DebugHeader,并进行值填充和初始化。
1. pBlockHeaderNext, 指向链表后内存块
2. pBlockHeaderPre, 指向链表前内存块
3. szFileName, 记录申请使用该内存空间所在的文件
4. nLine, 记录申请使用该内存空间所在文件的行号
5. nDataSize, 实际数据区的空间大小(应用程序实际使用到的内存块)
6. nBlockUse, 表示当前内存块的类型, 如 _CRT_BLOCK、_NORMAL_BLOCK 等...
7. IRequest, 操作流水号
8. gap[nNoMansLandSize], 上下两处“栏杆”,保护应用程序实际使用到的内存,到发生内存越界的情况,调试器可以检查到
1. 上图主要对分配的空间进行数据填充和值初始化,以后续对内存块管理
2. 在调试模式下,malloc 分配的内存都被管理在链表中(及时已被客户使用),因此调试器可以实现复杂的功能
3. _pFirstBlock、_pLastBlock 全局指针
_heap_alloc_base(size)
说明 :
完成对实际内存的分配和管理
1. 当应用程序进行内存申请时,内存相关底层实现会根据申请空间的大小选择不同的接口
1.1 当申请量小于等于 1016Bytes 时调用 __sbh_alloc_block
1.2 当申请量大于 1016Bytes 时调用操作系统提供的API HeapAlloc() 从 __crtheap(全局变量) 中取内存
2. __sbh_threshold = 1016 = 1024 - (2 * 4). 其中 2 * 4Bytes 对应两个 cookies 的开销
_sbh_alloc_block(...)
1. (应用程序实际申请量 + DebugHeader + cookie * 2) 向上调整 16 字节 【类似于 std::alooc 中的 ROUNDUP()】
2. 0x100 + 0x24 + 4 * 2 = 0x12C = 130; 因为是16的倍数,所以上下cookie最后四位都为0,借用最后 1 bit来标记当前内存是否空闲(1用户使用,0空闲)
__sbh_alloc_new_region()
之前的_ioinit()内部调用细节,主要完成实际分配内存大小的调整,自此函数开始真正的内存管理动作(可以看到真正的内存管理结构)
1. __sbh_heap_init 创建的 16 个header, 每个 header 负责管理 1M 字节内存(调用系统virtual alloc获得),未来应用程序的小区块内存申请都从这里切割(管理中心成本 16k 字节)。
2. bitvGroupHi, bitvGroupLow 对应 32 * (32 * 2) = 32 * 64bit 的空间,对应32个Group中的64个双向循环链表,记录其中每个链表是否已有空闲节点(1表示有,0表示无)
3. listHeader[64] 表示每个 Group 中的 64 个双向循环链表
__sbh_alloc_new_group()
1. 一个 Header 对应管理 1MB 内存,其被逻辑分为 32 块(1MB / 32 = 32kB) 对应到 32 个 Group
2. 每块内存又被分为 8 page(32KB / 8 = 4KB)
3. 每个 page 被组织到 Group 的最后一条链表中
4. 当有内存请求时,便从此链表中进行切割提供。当 Group0 再无内存可用,Group 1 便再去管理对应逻辑分块的第二块内存进行分配
Group中 listHead 管理规则:
listHead[0] 管理 16 字节内存块,依次叠加 16 字节增长至 listHead[63] (1024)。其中所有大于等于 1KB 的内存块都被 listHead[63] 管理
1. 上图黄色标注 0xffffffff (-1) 可理解为“阻隔器”,在内存回收合并时使用,保证相邻的page总是被分离的(不会交融合并)
2. 上图红色标注三个区块是 ENTRY 结构,4080 对应 sizeFont (表示空闲字节数), 是阻隔器之间空间大小
3. 已知 1page 是 4096 字节, 减去两个”阻隔器“ 4096 - (2 * 4) = 4088,预留(浪费)8字节以进行 16 对齐 4088 - 8 = 4080
4. 在上图 Group 结构中,只有 64 对指针,并没有看到 sizeFont。这因为 Group 只是自由链表的辅助节点,并未对应实际的page空间,因此为了节省内存 sizeFont 借用了上一个 Entry 的pEntryPrev
-----------------
EntryNext
EntryPrev | sizefont
-----
EntryNext
EntryPrev
-----------------
经过以上步骤,SBH 已经将一定数量的内存空间纳入自己的管理之下,并能够依照自己的规则向应用程序提供内存
函数调用栈返回,返回应用程序申请得到的内存地址
1. ioint 进行了第一次 0x100 Bytes 的内存申请
2. 经过调整为 0x130 Bytes [(应用程序实际申请量 + DebugHeader + cookie * 2) 向上调整 16 字节对齐]
3. 调整 sizeFont 为 0xEC0 (0xFF0 - 0x130 = 0xECO)
--
4. 切割 0x130 大小内存,填充上下 Cookie 空间为 0x131 (130 - > 131 前面已解释)
5. 返回申请得到的内存空间(不包含 Cookie 地址)
6. 填充构造 DebugHeader
7. 返回用用程序实际申请大小的内存空间 (不包含 DebugHeader)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。