Malloc即服务

2020-10-14 15:17:53

如果工作正常,上面的输入框将加载一个简单的框架malloc实现,并公开";malloc";和";free&34;绑定。但巧妙的是,它是在没有Emscripten的情况下构建的:它是一个独立的C文件,直接编译成WebAssembly,根本没有JavaScript运行时。我在这里分享它,因为它可能对从事WebAssembly工具链工作的人很有用,也因为构建它是一种有趣的体验。

单机版。不需要stdlib;不需要Emscripten。可以包含在项目中,而不需要引入任何其他内容。

Emscripten包含两个很好的malloc实现(dlmalloc和emmalloc),您可能应该改用它们。但是,如果你真的在寻找一种简单的麦乐克,沃尔洛克是不错的选择。

您可以在walloc项目页面查看所有细节;下面精选了一些重要内容。

产生的walloc.o本身就是一个符合标准的WebAssembly文件,但是它还包含额外的符号表和重定位部分,这些部分允许wasm-ld将单独的编译单元组合到单个最终的WebAssembly文件中。Walloc.c本身并不导入或导出任何内容,在WebAssembly意义上;要使绑定对JS可见,您需要添加一个小包装器:

Tyfinf__size_type__size_t;#定义WASM_EXPORT(名称)\__ATTRIBUTE__((EXPORT_NAME(#name)\name//声明它们来自walloc.c.void*malloc(Size_T Size);void free(void*p);void*WASM_export(Walloc)(Size_T Size){return malloc(Size);}void WASM_export(Wfree)(void*ptr){free(Ptr);}。

如果将其编译为exports.o并通过wasm-ld--no-entry--import-memory-o walloc.wasm exports.o walloc.o链接,则最终得到上面演示中使用的walloc.wasm。有关URL,请咨询您的检查员。

Walloc并不是最小的分配器。一个简单的永不释放的凹凸指针分配器是你能拥有的最快的东西。还有一个用于Rust的备用分配器wee_alloc,据说它比walloc小,尽管我认为它对小对象的空间效率较低。不过,沃尔克还是相当小的。

当C程序编译到WebAssembly时,生成的wasm模块(通常)具有相关的线性内存。它可以以这样的方式链接,即当它被实例化时由模块创建内存,或者使得模块由其主机给予内存。上面的示例将--import-memory传递给链接器,允许主机绑定模块实例的内存使用。

线性存储器具有常见的数据、堆栈和堆段。首先放置数据和堆栈。堆从&;__heap_base符号开始。(此符号由链接器计算和定义。)。Wasm程序可以随意使用&;__heap_base上面的所有字节。因此&;__heap_base是walloc管理的内存下限。

内存增长->;+----|数据和堆栈|对齐|Walloc页面|...+。-+-^0^&;__HEAP_BASE^64 kB对齐。

有趣的是,不同的工具链使用了几种不同的数据和堆栈顺序。甚至在过去,堆栈也是这样发展起来的。来自Lehmann等人最近发表的“一切旧都是新的:WebAssembly的二进制安全性”一文中的图表是一个很好的总结:

防止意外溢出(实际上是下溢)的明智做法是让堆栈向下增长到0,数据位于更高的地址。但这可能会导致引用数据的WebAssembly代码占用更多字节,因为地址是使用支持短偏移量的可变长度编码写入的,因此它不是默认的,至少目前是这样。

无论如何!Walloc管理的内存上限是内存的总大小,它在64KB边界上对齐。(WebAssembly可确保此对齐。)。Walloc还管理64kb页面中的内存。它从最初分配给模块的任何内存开始,如果内存用完,它将扩展内存。宿主可以指定最大内存大小(以页为单位);如果没有更多的页可用,walloc的malloc将简单地返回NULL;内存不足的处理由调用方决定。

有一个可用大对象的全局自由列表,每个大对象都有一个标头指示其大小。分配时,walloc会对该列表进行最合适的搜索。

如果空闲列表上没有能够满足分配的对象,walloc将按分配大小或当前walloc堆大小的一半(以较大者为准)扩展堆。生成的一个或多个页面形成可以满足分配的大对象。

如果自由列表中最好的对象末尾有超过一大块空间,则将其拆分,并将尾巴放回自由列表中。块是256字节。

+-+|页眉|块1|块2|...|块255|+-+^+0^+256^+512^+64kB。

由于每页为65536字节,每个块为256字节,因此一页中有256个块。页中开始分配的对象(无论大小)的第一个块包含标题块。页头对于页中的256个块中的每个块都有一个字节。如果相应的块开始一个大对象,则该字节为255;否则,该字节指示打包的小对象分配的大小类(见下文)。

+-+|页眉|大对象1|大对象2...|+-+。-+^+0^+256^+512^+64kB。

拆分大对象时,我们避免在页眉块上启动新的大对象。如果大对象包含整个页面,则它只能跨越页眉块所在的位置。

释放一个大对象会将其推送到全局自由列表中。通过查看页眉,我们知道指针是一个大对象。我们知道分配的大小,因为大对象标头在分配之前。当在释放之后发生下一次大对象分配时,将通过合并相邻的大对象来压缩自由列表。

小对象是从隔离的自由列表中分配的。粒大小为8字节。小对象分配被打包在统一分配大小的块中。每个大小的分配都有大小类别,从1到6个颗粒,然后是8、10、16和32个颗粒;总共有10个大小。例如,将从16粒块中满足例如12粒的分配。每个大小类别都有自己的空闲列表。

在分配时,如果相应的freelist上没有任何内容,walloc将分配一个新的大对象,然后将其页头中的块类型更改为Size类。然后,它遍历新的块,通过线程将对象彼此连接到一个空闲列表中。

+-+|页眉|大对象1|Granules=4|大对象2';|+-+^+0^+256^+512^+768+1024^+64kB.。

在本例中,我们假设4粒自由列表为空,而大对象自由列表只包含大对象2,一直运行到页面末尾。我们分配了一个新的4粒块,将第一个块从大对象中拆分出来,并将新修剪的大对象推回到大对象自由列表中,相应地更新了页眉。然后,我们将新块中的4-Granules(32字节)分配线程在一起(该块有容纳其中8个的空间),将它们视为struct freelist的实例,将它们推送到4-Granules分配的全局空闲列表上。

在刷新块中,对象N下一个链接指向对象N+1/-\||+-+-^-v-+-+granules=4:|(填充,可能)|Object 0|...|Object 7|+-+^4-Granule Freelist现在指向此处。

选择SIZE类时,任何浪费的空间(填充)都小于SIZE类。

释放一个小对象会将其推回到其Size类的空闲列表中。给定一个指针,我们通过查看页眉中的块类型来知道它的大小类。

嘿,祝你玩得开心!如果你觉得有用,请告诉我。祝您黑客活动愉快,下次再见!