malloc

实际上,malloc并不是系统调用,而是C库里的函数,用于动态分配内存。malloc申请内存的时候,会有两种方式向操作系统申请堆内存。

  • **方式一:**通过==brk()系统调用==从堆分配内存

    实现的方式很简单,就是通过brk()函数将「堆顶」指针向高地址移动,获得新的内存空间。如下图:

    img
  • **方式二:**通过==mmap()系统调用==在文件映射区域分配内存

    img

malloc()源码里默认定义了一个阈值:

  • 如果用户分配的内存小于128KB,则通过brk()申请内存;
  • 如果用户分配的内存大于128KB,则通过mmap()申请内存;

特点:

  • 当malloc通过brk系统调用申请内存时释放该内存后,操作系统不会立即回收这部分空间;相反,它会被保留在malloc管理的内存池中,供后续分配请求直接复用,从而减少系统调用开销。
  • 当malloc通过mmap系统调用申请内存时,释放该内存后,系统会立即将其归还给操作系统,实现物理内存的即时释放,避免资源长期占用。

malloc()分配虚拟内存

malloc()分配的是虚拟内存。如果分配后的虚拟内存没有被访问的话,虚拟内存是不会映射到物理内存的,这样就不会占用物理内存了。只有在访问已分配的虚拟地址空间的时候,操作系统通过查找页表,发现虚拟内存对应的页没有在物理内存中,就会触发==缺页中断==,然后操作系统会建立虚拟内存和物理内存之间的映射关系。

brk()

image-20250709150943932

brk类 UNIX 操作系统(如 Linux、BSD 等)提供的系统调用,用于 进程地址空间的堆内存管理。堆(Heap)位于数据段(.data) 之后、栈(Stack) 之前,向上(高地址)生长。brk 通过调整 堆的顶部边界(break 指针) 来控制堆的大小。

  • brk的作用

    核心是 修改进程堆的顶部地址(break 值),实现堆内存的 扩展或收缩

    • 扩展堆:若新的 break 值比当前大,内核会将堆向高地址扩展,分配新的内存区域(进程可直接使用,无需额外映射)。
    • 收缩堆:若新的 break 值比当前小,尝试回收堆的高地址部分内存(实际中因内存分配器的管理逻辑,收缩可能受限,比如已分配的内存块未释放时无法回收)。
  • brk对应的映射类型

    • 匿名映射:内存无对应的文件作为 “后盾”(即不关联磁盘文件)。brk 管理的堆内存 没有文件支持,属于 匿名映射
    • 私有映射:对内存的修改仅作用于当前进程(不会同步到其他进程或文件)。brk 的堆是 进程私有 的(每个进程的堆独立,修改不影响其他进程),因此属于 私有映射
  • 运行流程

    我们需要分析brk系统调用,我们要从源码中进行分析:

    mmap.c文件中:sys_brk 函数(处理 brk 请求),负责接收用户空间的 brk 请求,初步校验后调用 do_brk

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    static int do_brk_flags(unsigned long addr, unsigned long request, unsigned long flags,
    struct list_head *uf);
    SYSCALL_DEFINE1(brk, unsigned long, brk) {
    struct mm_struct *mm = current->mm; // 当前进程的内存描述符
    unsigned long old_brk = mm->brk; // 旧堆顶地址

    // ---------- 关键判断 1:堆顶不能小于起始地址 ----------
    if (new_brk < mm->start_brk) {
    return -EINVAL; // 非法:堆不能收缩到起始地址之前
    }
    // ---------- 堆收缩处理 ----------
    if (brk <= mm->brk) { // 请求收缩堆
    mm->brk = brk; // 立即更新堆顶指针
    if (do_vmi_align_munmap(..., newbrk, oldbrk, ...))
    goto out; // 释放物理内存并解除映射
    }
    // ---------- 堆扩展处理 ----------
    // 检查新堆顶是否与栈或其他 VMA 冲突
    next = vma_find(&vmi, newbrk + PAGE_SIZE + stack_guard_gap);
    if (next && newbrk + PAGE_SIZE > vm_start_gap(next))
    goto out; // 与现有 VMA 冲突或过于接近栈

    // 创建新的 VMA 区域
    if (do_brk_flags(&vmi, brkvma, oldbrk, newbrk - oldbrk, 0) < 0)
    goto out;

    // ---------- 调用 do_brk 处理实际调整 ----------
    unsigned long ret = do_brk(new_brk);
    if (ret < 0) {
    return ret; // 调整失败(如地址冲突、内存不足)
    }

    mm->brk = ret; // 更新堆顶地址
    return ret;
    }
  • 堆边界定义

    struct mm_struct 中的 start_brk(堆起始)和 brk(当前堆顶)