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

如何用NASM汇编实现自定义malloc?对brk与mmap存疑

实现x64 Linux下的自定义malloc(NASM汇编)

核心原理澄清

Linux下动态内存分配依赖两个核心系统调用:

  • brk/sbrk:调整进程堆空间的末端地址(program break),堆是低地址向高地址增长的连续区域
  • mmap:不仅能关联文件,更常用的是匿名映射(通过MAP_ANONYMOUS标志),用于分配大块内存,避免堆碎片化——这是你之前对mmap的误解点

初始堆指针的获取

不存在“一开始没有可用指针”的问题:调用brk(0)就能获取当前堆的末端地址(program break),这就是初始堆指针。后续扩展堆时,只需调用brk(new_break)将末端地址设置到更高位置,中间的空间即可用于分配。

分步实现代码

1. x64 Linux系统调用号

系统调用前需将对应编号存入rax寄存器:

  • brk:12
  • mmap:9

2. 基础brk版malloc(无free)

先实现一个仅支持内存分配的简单版本,处理小内存请求:

; 全局变量:保存当前堆末端地址
section .bss
    current_break resq 1   ; x64为8字节,用resq定义

section .text
global my_malloc

my_malloc:
    push rbp
    mov rbp, rsp

    ; 将请求大小对齐到8字节(x64内存对齐要求)
    add rdi, 7
    and rdi, ~7

    ; 初始化current_break(首次调用时)
    cmp qword [current_break], 0
    jne .heap_init_done
    ; 调用brk(0)获取初始堆末端地址
    mov rax, 12
    mov rdi, 0
    syscall
    mov qword [current_break], rax

.heap_init_done:
    ; 计算新堆末端地址:current_break + 分配大小
    mov rsi, rax         ; 保存原堆末端(即本次分配的起始地址)
    add rax, rdi         ; new_break = current_break + size
    ; 调用brk扩展堆
    mov rdi, rax
    mov rax, 12
    syscall
    ; 检查扩展是否成功(返回值等于new_break则成功)
    cmp rax, rdi
    jne .alloc_fail

    ; 返回分配的内存起始地址
    mov rax, rsi
    ; 更新全局堆末端地址
    mov qword [current_break], rdi
    leave
    ret

.alloc_fail:
    ; 分配失败返回0
    mov rax, 0
    leave
    ret

3. 加入mmap处理大块内存

当请求内存超过阈值(比如128KB,参考glibc的策略),改用mmap匿名映射更高效:

; 定义大块内存阈值:128KB
section .data
    MMAP_THRESHOLD equ 131072

section .bss
    current_break resq 1

section .text
global my_malloc

my_malloc:
    push rbp
    mov rbp, rsp

    ; 对齐请求大小到8字节
    add rdi, 7
    and rdi, ~7

    ; 判断是否使用mmap分配大块内存
    cmp rdi, MMAP_THRESHOLD
    jge .use_mmap

    ; brk分配分支(同基础版逻辑)
    cmp qword [current_break], 0
    jne .heap_init_done
    mov rax, 12
    mov rdi, 0
    syscall
    mov qword [current_break], rax

.heap_init_done:
    mov rsi, rax
    add rax, rdi
    mov rdi, rax
    mov rax, 12
    syscall
    cmp rax, rdi
    jne .alloc_fail

    mov rax, rsi
    mov qword [current_break], rdi
    leave
    ret

.use_mmap:
    ; mmap匿名映射参数:
    ; rdi=0(让内核选择映射地址)
    ; rsi=分配大小
    ; rdx=PROT_READ|PROT_WRITE (0x3)
    ; r10=MAP_PRIVATE|MAP_ANONYMOUS (0x22)
    ; r8=-1(匿名映射无需文件描述符)
    ; r9=0(文件偏移量,无效)
    mov rax, 9
    mov rsi, rdi
    mov rdi, 0
    mov rdx, 0x3
    mov r10, 0x22
    mov r8, -1
    mov r9, 0
    syscall
    ; 检查是否失败(返回值为-1则失败)
    cmp rax, -1
    je .alloc_fail
    ; 成功则返回映射地址
    leave
    ret

.alloc_fail:
    mov rax, 0
    leave
    ret

关键注意事项

  • 内存对齐:x64环境下所有指针和数据结构必须对齐到8字节,否则会引发性能问题或错误
  • 系统调用校验:brk成功返回新的program break,失败返回-1;mmap成功返回映射地址,失败返回MAP_FAILED(即-1)
  • 后续扩展:若要实现free,需在每个分配块的头部记录块大小和类型(brk分配/mmap分配),这样free时才能判断是调用brk收缩堆还是munmap释放映射

内容的提问来源于stack exchange,提问作者R-Rothrock

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 01:57:58