第一次,分配

image.png

知识点补充
LPVOID VirtualAlloc{
    LPVOID lpAddress,       // 要分配的内存区域的地址 (当实参为0时,由操作系统指定地址)
    DWORD dwSize,           // 分配的大小
    DWORD flAllocationType, // 分配的类型
    DWORD flProtect         // 该内存的初始保护属性
};

1. virtualAlloc 是一个 Window API 函数,该函数的功能是在调用进程的虚拟地址空间预定或者提交一部分页
2. flAllocationType :
    MEM_RESERVE 保留分配地址,不分配物理内存。这样可以阻止其它分配函数 malloc 和 LocalAlloc 等再使用已保留的内存范围,直到它被释放
    MEM_COMMIT 为指定地址空间提交物理内存。这个函数初始化内在为零
typedef struct tagGroup {
    int cntEntries;
    struct tagListHead listHead[64];
}GROUP, *PGROUP;

1. int cntEntries, 累计量,内存分配时+1,内存释放时-1。当 cntEntries 为 0 ,表示 8 page 全部收回,就可以将此内存块归还给操作系统
typedef unsigned int BITVEC;

typedef struct tagRegion {
    int indGroupUes;
    ......
    BITVEC bitvGroupHi[32];
    BITVEC bitvGroupLo[32];
    struct tagGroup grtHeadList[32];
}

1. indGroupUes, 定位当前正在使用的 Group 索引
2. 32 组 GROUP 对应 32 * 64 bit,当 bitvGroup[i][j] (第 i 个 GROUP 中的第 j 条双向链表)的 bit 位为 1 表示链表有可用内存块, 为 0 表示链![image.png](/img/bVcSzQZ)
typedef struct tagEntry
{
    int sizeFront;
    struct tagEntry *pEntryNext;
    struct tagEntry *pEntryPrev;
}ENTRY, *PENTRY;

1. pEntryNext、pEntryPrev 为嵌入式指针,在内存块分配前后分配后有不同的解释
流程描述
1. 由 ioinit.c, line#18 申请 100h,区块大小 130h (debugheader、cookie、16字节对齐调整)
2. 使用 Group[indGroupUes] => Group[0] ; 
        bitvGroup[indGroupUes] [130h = 304 * 16 - 1] => bitvGroup[0] [18] bit 为 0,表明对应 Group[0][18] (listHead[18])链表中无可用区块,于是继续向右查找,Group[0][63] 最后一条链表有可用区块
3. 进行内存切割(参见上节文章流程)

第二次,分配

__crtGetEnvironmentStringsA() 发起

image.png

第三次,分配

image.png

第十五次,释放

环境变量处理完成后的内存归还(main之气) 240H

image.png

流程描述
1. 当前操作仍为 Group[0]
2. 修改 cntEnteries 由 14 为 13
3. 240h / 16 - 1 = 576 / 16 - 1 = 35 => listHead[35], 归还的内存块挂接(嵌入式指针完成)到 35 号链表
4. 修改  bitvGroup[0] [35] 为 1
5. 修改 cookie 最后一位为 0

第十六次,分配

image.png

流程描述 【申请 b0h】
1. 修改 cntEnteries 由 13 为 14
2. b0h / 16 - 1 = 10 => bitvGroup[0][0] 为 0, 表示无可用区块
3. 查找临近可用区块, bitvGroup[0][35] 为 1, 在 listHead[35] 链表管理的内存块中进行切割
4. 240h - b0h = 190h, 190h / 16 - 1 = 24 切割后的内存块重新调整挂在到 listHead[24]
5. 修改 bitvGroup[0][35] 为 0,bitvGroup[0][24] 为 1

第 n 次, 分配

image.png

流程描述【申请 230h】
1. 经过不断的内存分配,Group[0] 管理的 page[1-8] (32KB) 已无法满足 230h
2. Group[indGroupUes] => indGroupUes[1] 开始新的故事

区块合并

free-list 内的合并

为了尽量减少内存空间的碎片化以满足后期较大内存块的申请需求,将相邻空闲的区块进行合并时 SBH 进行碎片整理的思路。(分为向下合并、向上合并)

image.png

[最左侧图]
1. 应用程序归还上图蓝色箭头所指向内存地址[0x00000304](修改上下 cookie 最后 1 bit 为 0)
2. 0x00000304 向上加 4bytes, 定位到上 cookie 地址 0x00000300,获取到当前内存块长度 0x300
3. 上 cookie 地址加 0x300 定位到下 cookie
 
[最中间图]
4. 下 cookie 加 4 bytes, 定位到下块内存块的上 cookie,检查最后bit为0,表明此块内存空闲,两内存块进行合并【下内存块需要调整对应的 listHead 和 bitvGroup】
 
[最右侧图]
5. 上 cookie 减 4 bytes, 定位到上内存块的下 cookie, 检查最后bit为0, 表明此内存块空间,接上步骤内存块进行合并【上内存块需要调整对应的 listHead 和 bitvGroup】

6. 0x300 + 0x300 + 0x300 = 0x900 大于 1 KB, 调整挂载到最后一条链表 list[63],设置对应的 bitvGroup 位

free(p) 索引定位

image.png

p 落在哪个 Header 内
1. 已知 Header 首地址、 Header 数量、Header 大小
2. Header 内成员 pHeapData 指向 1MB 内存空间
3. 遍历所有 Header, 查看 p 落在哪个 Header 的 [pHeapData, pHeapData+1MB] 内
p 落在哪个 Group 内
(p - pHeapData) / 32 - 1
p 落在哪个 free-list 内
1. 上 cookie 地址 = p - 4
2. 从上 cookie 获得要释放的内存块大小 size
3. index = (size / 16) - 1

内存分段管理的妙处

分段管理之妙,利于归还 O.S(当 cntEntries 为 0 时进行“全回收”)

SBH 做分段的管理(每次申请1MB虚拟空间 -> 每次申请32KB物理内存 -> 分为8Page -> 64条链表)

image.png

内存的延迟归还

为了内存分配的高效性,对内存的回收使用了 Defering 机制

image.png

当归还完所有的内存块

当归还完所有内存块, SBH 将恢复到初始状态

image.png

VC malloc + GCC allocator

  • malloc 和 allocator 的访问速度都很快,绝大多数情况下不需要我们重新实现
  • allocator 目的是为了减少 cookie, 缺点是不会向操作系统归还内存

image.png

叠屋架床

image.png

叠屋架床,层层封装是一种浪费吗?
是,但有必要。因为每一个上层调用都无法保证下层是否有合适的内存管理

TianSong
737 声望139 粉丝

阿里山神木的种子在3000年前已经埋下,今天不过是看到当年注定的结果,为了未来的自己,今天就埋下一颗好种子吧