You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

无C标准库依赖时ARM64汇编如何实现堆内存分配

ARMv8-A 汇编无C标准库依赖的堆内存分配实现方法

首先纠正对brk()系统调用的认知偏差

brk()本身完全支持内存释放,你之前的判断有误:

  • brk()的核心作用是修改进程堆段的结束边界(program break),只要你把这个边界向低地址移动,内核会自动回收边界以上的物理内存,这就是标准的内存释放操作。
  • 举个实际场景:初始堆边界为0x0000aaaabbbbb000,调用brk()将边界设为0x0000aaaabbbbb000 + 16384,即申请了16KB堆内存;后续再调用brk()将边界改回初始值,这16KB内存就会被完整交还给内核。
  • 常提到的sbrk()是C库对brk()的封装,传入偏移量返回旧边界值,传入负偏移即可收缩堆,本质和直接调用brk()没有区别。

AArch64(ARMv8-A)下直接调用brk()的方法

AArch64架构Linux系统调用遵循如下约定:系统调用号存入x8寄存器,参数按顺序存入x0-x5寄存器,执行svc #0指令触发调用,返回值存在x0寄存器。

  • brk()的系统调用号为214:x0传入目标堆边界地址,调用成功后x0返回实际生效的堆边界,若返回值小于传入的目标地址则代表调用失败。
  • 首次调用brk()时可给x0传0,即可获取进程初始的堆边界地址,你可以自行维护全局变量存储堆基址、当前堆边界、空闲块链表等管理数据。
    基础调用示例:
// 获取初始堆边界
mov x8, #214        // 加载brk系统调用号
mov x0, #0          // 传0查询当前堆边界
svc #0
mov x19, x0         // x19存堆基址(初始边界)
mov x20, x0         // x20存当前堆边界

// 申请4KB内存
add x0, x20, #4096  // 计算新堆边界 = 当前边界 + 4KB
mov x8, #214
svc #0
cmp x0, x20         // 校验返回值是否符合预期
blt alloc_fail      // 返回值小于当前边界则分配失败
mov x0, x20         // 分配到的内存起始地址为旧堆边界
add x20, x20, #4096 // 更新当前堆边界

// 释放堆顶的4KB内存
mov x0, x19         // 将堆边界改回初始位置
mov x8, #214
svc #0
mov x20, x19        // 更新当前堆边界,释放完成

实现通用分配/释放逻辑需要补充用户态堆管理

你觉得brk()没法自由释放内存,本质是因为brk()只能直接收缩堆顶的内存,无法直接释放堆中间位置的内存——这不是brk()的缺陷,而是堆内存分配器的通用设计:所有内存的申请释放逻辑都需要在用户态实现,C库的malloc/free也只是在brk()、mmap()这些系统调用之上加了一层管理逻辑而已,你完全可以自己用汇编写这部分,不需要依赖C库:

  • 给每个分配的内存块加固定长度的头部元数据,记录块大小、是否空闲、相邻块地址等信息
  • 分配内存时优先遍历已有的空闲块,匹配大小合适的块拆分使用,没有合适的空闲块时再调用brk()扩容堆段
  • 释放内存时将对应块标记为空闲,立刻合并前后相邻的空闲块;如果合并后的空闲块位于堆的最顶端,就调用brk()降低堆边界,把内存正式还给内核

大内存分配的补充方案

如果要分配大于128KB的内存块,不需要走brk()堆管理逻辑,可以直接调用mmap()申请匿名私有内存映射:

  • AArch64下mmap()系统调用号为222,munmap()系统调用号为215,参数语义和C库封装的同名函数完全一致
  • 这类内存释放时直接调用munmap()即可,不需要等待堆边界收缩,释放后立刻归还内核

提示:所有Linux系统调用都不需要依赖C库,只要按照架构约定的传参规则触发软中断即可直接使用,C库本身也是用相同方式调用这些系统调用实现上层功能的。

内容的提问来源于stack exchange,提问作者EclecticPasta

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 18:24:39