第一次,调配
知识点补充
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()
发动
第三次,调配
第十五次,开释
环境变量解决实现后的内存偿还(main 之气)240H
流程形容
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
第十六次,调配
流程形容【申请 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 次,调配
流程形容【申请 230h】
1. 通过一直的内存调配,Group[0] 治理的 page[1-8](32KB)已无奈满足 230h
2. Group[indGroupUes] => indGroupUes[1] 开始新的故事
区块合并
free-list 内的合并
为了尽量减少内存空间的碎片化以满足前期较大内存块的申请需要,将相邻闲暇的区块进行合并时 SBH 进行碎片整顿的思路。(分为向下合并、向上合并)
[最左侧图]
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) 索引定位
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 条链表)
内存的提早偿还
为了内存调配的高效性,对内存的回收应用了 Defering 机制
当偿还完所有的内存块
当偿还完所有内存块,SBH 将复原到初始状态
VC malloc + GCC allocator
- malloc 和 allocator 的访问速度都很快,绝大多数状况下不须要咱们从新实现
- allocator 目标是为了缩小 cookie,毛病是不会向操作系统偿还内存
叠屋架床
叠屋架床,层层封装是一种节约吗?是,但有必要。因为每一个下层调用都无奈保障上层是否有适合的内存治理