CH2-malloc
malloc
实际上,malloc并不是系统调用,而是C库里的函数,用于动态分配内存。malloc申请内存的时候,会有两种方式向操作系统申请堆内存。
**方式一:**通过==brk()系统调用==从堆分配内存
实现的方式很简单,就是通过brk()函数将「堆顶」指针向高地址移动,获得新的内存空间。如下图:
**方式二:**通过==mmap()系统调用==在文件映射区域分配内存
malloc()源码里默认定义了一个阈值:
- 如果用户分配的内存小于128KB,则通过brk()申请内存;
- 如果用户分配的内存大于128KB,则通过mmap()申请内存;
特点:
- 当malloc通过brk系统调用申请内存时,释放该内存后,操作系统不会立即回收这部分空间;相反,它会被保留在malloc管理的内存池中,供后续分配请求直接复用,从而减少系统调用开销。
- 当malloc通过mmap系统调用申请内存时,释放该内存后,系统会立即将其归还给操作系统,实现物理内存的即时释放,避免资源长期占用。
malloc()分配虚拟内存
malloc()分配的是虚拟内存。如果分配后的虚拟内存没有被访问的话,虚拟内存是不会映射到物理内存的,这样就不会占用物理内存了。只有在访问已分配的虚拟地址空间的时候,操作系统通过查找页表,发现虚拟内存对应的页没有在物理内存中,就会触发==缺页中断==,然后操作系统会建立虚拟内存和物理内存之间的映射关系。
brk()

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
35static 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
(当前堆顶)
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 Kevin's blogs!