BGET内存分配器
BGET是一个全面的内存分配包,可以根据应用程序的需要轻松配置。BGET在分配和释放缓冲区所需的时间以及缓冲池管理所需的内存开销方面都很有效。它会自动整合连续的空间,以最大限度地减少碎片。BGET由编译时定义配置,主要选项包括:
- 内置的测试程序,用于练习BGET并演示如何使用各种功能。
- 通过“第一次适合”或“最佳适合”方法进行分配。
- 在发布时擦除缓冲区以捕获引用先前释放的存储的代码。
- 用于转储单个缓冲区或整个缓冲池的内置例程。
- 检索分配和池大小统计信息。
- 将缓冲区大小量化为2的幂以满足硬件对齐约束。
- 通过对用户定义函数的回调自动池压缩,增长和收缩。
BGET的应用范围可以从基于ROM的嵌入式程序中的存储管理到提供构建包含垃圾收集的多任务系统的框架。BGET使用该<assert.h>
机制进行广泛的内部一致性检查 ; 所有这些检查都可以通过使用已NDEBUG
定义的编译来关闭,从而产生具有最小尺寸和最大速度的BGET版本。
BGET的基本算法经受住了时间的考验; 自第一次实施此代码以来已超过25年。然而,它比许多操作系统的原生分配方案效率要高得多:Macintosh和Microsoft Windows,其中两个程序通过将BGET分层作为底层系统顶层的应用程序级内存管理器而获得了大幅提升。
BGET已 和最低的微处理器上实现。它已成为多任务操作系统,多线程应用程序,数据网络交换处理器中的嵌入式软件以及大量C程序的核心。虽然它多年来已经增加了灵活性和附加选项,但它仍然快速,高效,便携,并且易于集成到您的程序中。
BGET实施假设
BGET以尽可能便携的方言编写。关于底层硬件架构的唯一基本假设是分配的存储器是线性阵列,其可以作为C的向量来寻址char
对象。在分段地址空间体系结构上,这通常意味着BGET应该用于在单个段内分配存储(尽管一些编译器模拟分段体系结构上的线性地址空间)。在分段体系结构上,BGET缓冲池可能不会大于一个段,但由于BGET允许任意数量的单独缓冲池,因此对可管理的总存储没有限制,只能在最大的单个对象上进行管理。分配。具有线性地址架构的机器,例如VAX,680x0,SPARC,MIPS或本机模式下的Intel 80386及更高版本,可以无限制地使用BGET。
BGET入门
尽管BGET可以采用多种方式进行配置,但使用BGET有三种基本方法。下面介绍的功能将在下一节中介绍。请原谅为了提供路线图而提出的前瞻性参考资料,以指导您可能需要的BGET功能。
嵌入式应用
嵌入式应用程序通常具有专用于缓冲区分配的固定存储区域(通常位于与包含可执行代码的ROM不同的单独RAM地址空间中)。要在这样的环境中使用BGET,只需bpool()
使用RAM中的缓冲池区域的起始地址和长度进行调用,然后分配缓冲区bget()
并将其释放 brel()
。内存非常有限但CPU速度充足的嵌入式应用程序可以通过配置BGET进行 BestFit
分配(在其他环境中通常不值得)。
malloc()
仿真
如果C库malloc()
函数太慢,在开发环境中不存在(例如,本机Windows或Macintosh程序),或者不适合,则可以将其替换为BGET。最初bpool()
通过调用操作系统的低级内存分配器来定义一个适当大小的缓冲池。然后使用bget()
,bgetz()
和 分配缓冲区bgetr()
(最后两个允许将初始化的缓冲区分配为零,并且[低效]重新分配现有缓冲区以与C库函数兼容)。通过调用释放缓冲区brel()
。如果缓冲区分配请求失败,请从底层操作系统获取更多存储,通过另一次调用将其添加到缓冲池bpool()
,然后继续执行。
自动存储管理
您可以将BGET用作应用程序的本机内存管理器,并通过使用BECtl
定义的变量编译BGET ,然后调用 bectl()
和提供存储压缩,获取和释放的功能,实现自动存储池扩展,收缩和可选的特定于应用程序的内存压缩作为标准池扩展增量。所有这些功能都是可选的(尽管提供没有采集功能的释放功能没有多大意义,是吗?)。一旦定义了回调函数bectl()
,您就可以像以前一样使用bget()
和 brel()
分配和释放存储。您可以提供初始缓冲池,bpool()
或者依靠自动分配来获取整个池。打电话时 bget()
不能满足,BGET首先检查是否提供了压缩功能。如果是这样,则调用它(具有满足分配请求所需的空间和序列号,以允许连续调用压缩例程而不循环)。如果压缩函数能够释放任何存储(它不需要知道它释放的存储是否足够),它应该返回非零值,因此BGET将重试分配请求,如果再次失败,则再次调用压缩函数具有次高的序列号。
如果压缩函数返回零,指示无法释放空间,或者没有定义压缩函数,则BGET接下来测试是否提供了非NULL
分配函数 bectl()
。如果是,则使用参数调用该函数,该参数指示需要多少字节的额外空间。这将是调用中提供的标准池扩展增量, bectl()
除非原始bget()
调用请求大于此的缓冲区; 大于标准池块的缓冲区可以在此模式下由BGET“书籍”管理。如果分配函数成功获得存储,则返回指向新块的指针,BGET扩展缓冲池; 如果失败,则分配请求失败并向调用者返回NULL。如果是非NULL
提供了释放功能,通过将其地址传递给释放功能,将变为完全空的扩展块释放到全局空闲池。
BGET具有适当的分配,释放和压缩功能,可用作非常复杂的内存管理策略的一部分,包括垃圾收集。(但请注意,BGET本身并不是垃圾收集器,开发这样的系统需要更多的逻辑和仔细设计应用程序的内存分配策略。)
BGET功能描述
BGET实现的功能(某些功能由以下某些可选设置启用):
void bpool(void *
缓冲, bufsize
LEN);
使用从缓冲区开始的存储 创建len个字节的缓冲池。您可以随后调用以向整个缓冲池贡献额外的存储空间。 bpool()
void *bget(bufsize
尺寸);
分配大小字节的缓冲区。返回缓冲区的地址,或者NULL
如果没有足够的内存可用于分配缓冲区。
void *bgetz(bufsize
尺寸);
分配大小字节的缓冲区并将其清除为全零。返回缓冲区的地址,或者NULL
如果没有足够的内存可用于分配缓冲区。
void *bgetr(void *
缓冲区, bufsize
新闻);
重新分配先前分配的缓冲区bget()
,将其大小更改为新闻化并保留所有现有数据。 NULL
如果没有足够的内存可用于重新分配缓冲区,则返回,在这种情况下,原始缓冲区保持不变。
void brel(void *
BUF);
将之前分配 的缓冲区buf返回bget()
到空闲空间池。
void bectl(int (*
compact )(bufsize
sizereq , int
序列), void *(*
获取)(bufsize
大小), void (*
释放)(void *
buf ), bufsize
pool_incr);
扩展控制:指定在分配请求失败时包可以通过其压缩存储(或采取其他适当的操作)的功能,并且可选地在必要时自动获取扩展块的存储,并在它们变空时释放这些块。如果compact是非NULL
,则只要缓冲区分配请求失败,就会调用compact函数,其参数指定满足分配请求所需的字节数(总缓冲区大小,包括头开销),以及指示数量的序列号。紧凑的连续调用尝试满足此分配请求。对于Compact上的第一次调用,序列号为1 对于给定的分配请求,以及后续呼叫的增量,允许紧凑功能采取越来越可怕的措施以试图释放存储。如果 compact函数返回非零值,则重新尝试分配尝试。如果compact返回0(如果它无法释放任何空间或向缓冲池添加存储,则必须返回),分配请求将失败,如果获取参数为非,则可以触发自动池扩展NULL
。在调用compact函数时,缓冲区分配器的状态与分配请求时的状态相同; 因此,紧凑的功能可以调用 brel()
,bpool()
,bstats()
和/或以任何有效的方式直接操作缓冲池是控制中的应用程序。但是,这并没有减轻紧凑功能的需要,以确保它所采取的任何操作都不会改变发出分配请求的应用程序下面的内容。例如,在重新分配过程中释放缓冲区的 紧凑功能bgetr()
会导致灾难。实现安全有效的紧凑机制需要仔细设计应用程序的内存架构,并且通常不能轻易地改装到现有代码中。
如果acquire为non NULL
,则只要分配请求失败,就会调用该函数。如果获取 功能成功分配请求的空间并返回指向新区域的指针,则将使用扩展缓冲池继续分配。如果获取无法获取请求的空间,则应返回NULL
并且整个分配过程将失败。 pool_incr指定正常的扩展块大小。提供获取功能将导致后续 bget()
请求缓冲区太大而无法在链接块方案中管理(换句话说,大于 pool_incr减去缓冲区开销)直接通过调用来满足获得功能。仅当系统中的所有池块都是pool_incr给定的大小时,才会自动释放空池块。
void bstats(bufsize *
curalloc , bufsize *
totfree , bufsize *
maxfree , long *
nget , long *
nrel);
当前分配的空间量存储在curalloc指向的变量中。总可用空间(池中所有空闲块的总和)存储在totfree指向的变量中 ,并且空闲空间池中最大单个块的大小存储在maxfree指向的变量中。nget和nrel指向的变量分别用成功(非NULL
返回)bget()
调用的数量和brel()
调用的数量填充。
void bstatse(bufsize *
pool_incr , long *
npool , long *
npget , long *
nprel , long *
ndget , long *
ndrel);
扩展统计信息:扩展块大小将存储在pool_incr指向的变量中,如果禁用自动扩展块释放,则为负数。当前活动池块的数量将存储到npool指向的变量中。npget和 nprel指向的变量将分别填充已发生的扩展块获取和释放的数量。ndget和ndrel指向的变量将 分别由通过获取和释放函数直接分配的块管理的数量bget()
和brel()
调用填充。
void bufdump(void *
BUF);
缓冲区指向BUF是在标准输出倾倒。
void bpoold(void *
pool , int
dumpalloc , int
dumpfree);
缓冲池池中的所有缓冲区(先前由bpool()上的调用初始化)按升序内存地址顺序列出。如果dumpalloc非零,则转储已分配缓冲区的内容; 如果dumpfree非零,则转储空闲块的内容。
int bpoolv(void *
池);
先前通过调用初始化的命名缓冲池将 bpool()
针对错误指针,覆盖数据等进行验证。如果NDEBUG
未定义编译,则任何错误都会生成断言失败。否则,如果池有效则返回1,如果找到错误则返回0。
BGET配置
以下定义的变量bget.c
允许您配置BGET的各种功能和操作模式。
#define TestProg 20000 / *生成内置测试程序 如果定义。值指定 缓冲区分配尝试次数 测试程序应该做。* /#define SizeQuant 4 / *缓冲区分配大小量程: 分配的所有缓冲区都是 这个大小的倍数。这个 必须是两个人的力量。* /#define BufDump 1 / *定义此符号以启用 转储的bpoold()函数 缓冲池中的缓冲区。* /#define BufValid 1 / *定义此符号以启用 bpoolv()函数用于验证 一个缓冲池。* /#define DumpData 1 / *定义此符号以启用 允许的bufdump()函数 转储已分配的内容 或免费缓冲。* /#define BufStats 1 / *定义此符号以启用 计算的bstats()函数 缓冲区中的总可用空间 游泳池,最大的可用 缓冲区和总空间 目前已分配。* /#define FreeWipe 1 / *擦除保证的免费缓冲区 绊倒的垃圾模式 试图使用的不法分子 指向已释放缓冲区的指针。* /#define BestFit 1 / *何时使用最佳拟合算法 寻找空间 分配请求。这用 记忆更有效率,但是 分配会慢得多。* /#define BECtl 1 / *定义此符号以启用 bectl()函数用于自动 泳池空间控制。* /