跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C算法

理解 Linux 内存分配:malloc、brk、mmap 关系及 overcommit 参数

Linux 内存分配涉及 malloc、brk、sbrk 和 mmap 等机制。默认策略下,分配上限受物理内存限制。开启 overcommit 后,可分配虚拟内存空间显著增加。malloc 对小块内存使用堆扩展,大块则调用 mmap。直接 mmap 能突破 malloc 的内部开销限制,达到更高上限。碎片化是主要瓶颈之一。嵌入式开发需注意资源限制与碎片问题。

片刻发布于 2026/3/15更新于 2026/6/916 浏览
理解 Linux 内存分配:malloc、brk、mmap 关系及 overcommit 参数

理解 Linux 内存分配:malloc、brk、mmap 关系及 overcommit 参数

作为一名嵌入式系统或内核开发者,当我们调用 malloc() 时,内存究竟是如何运作的?我们通过实际示例来分析 Linux 中的内存分配过程,并理解其背后的底层机制。

1. 探索虚拟内存布局

对于任意一个进程,其内存布局可以通过查看 /proc/[pid]/maps 文件来了解。该文件清晰地展示了进程的虚拟内存映射结构。

$ cat /proc/14799/maps
00400000-00402000 r-xp 00000000 00:45 41291688 vaflmalloc
00402000-00403000 r--p 00001000 00:45 41291688 vaflmalloc
00403000-00404000 rw-p 00002000 00:45 41291688 vaflmalloc
00404000-00425000 rw-p 00000000 00:00 0 [heap]
...

解析上述内容:

  • 程序代码区域 (r-xp):存放可执行代码。
  • 数据区域 (r--p):包含已初始化的全局变量和静态变量。
  • 堆区域 (rw-p [heap]):动态内存分配的主要来源,例如通过 malloc() 分配的内存通常来自这里。
  • 内存映射区域 (r-xp):用于共享库以及通过 mmap 映射的文件。
  • 栈区域 (rw-p [stack]):存放局部变量及函数调用信息。
  • 特殊区域 ([vvar], [vdso], [vsyscall]):与内核相关的特殊内存区域,用于加速系统调用和时间读取。

值得注意的是,虚拟地址空间中存在大量'空洞'。这些是尚未使用的虚拟内存区域,在需要时,内核可以从这些区域中为进程分配新的内存。这些空隙的大小最终决定了单次能够分配的最大内存容量。

2. 内存分配是如何工作的

当你的程序调用 malloc() 时,通常会发生以下两种情况之一:

2.1 小块内存分配:堆(Heap)与 brk()

对于较小的内存分配请求,malloc() 会使用堆空间来完成分配。

// 分配一个小块内存
void *ptr = malloc(10 * 1024);

幕后发生了什么:

  1. malloc() 先检查当前堆中是否存在可用的空闲空间。
  2. 如果可用空间不足,它会通过 brk() 系统调用来扩展堆的边界。
  3. 接着,内存分配器为该内存块添加必要的内部管理信息(元数据)。
  4. 最后,malloc() 返回一个指向可用内存区域的指针。

我们可以通过 strace 来观察这一过程:

$ strace -e brk ./our_program
brk(NULL) = 0x403000
brk(0x424000) = 0x424000

第一次调用 brk(NULL) 用于获取当前的 program break(堆顶位置),第二次调用则在此基础上将堆空间向上扩展。

2.2 大块内存分配:直接使用 mmap()

对于较大的内存分配(通常阈值约为 128KB,但在本测试系统中显示为 16MB),malloc() 会完全绕过堆,转而直接使用 mmap() 来完成分配:

// 分配一块大内存
void *ptr = malloc(20 * 1024 * 1024);

strace 的输出显示:

$ strace -e mmap ./our_program
mmap(NULL, 20971520, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0)= 0x7f3700858000

这实际上是将内存直接映射到虚拟地址空间中的那些'空隙'位置。

3. 内存限制:物理内存(RAM)与虚拟内存

现在,让我们尝试分配一块远远超过物理内存容量的大型内存空间:

// 尝试分配 100GB
void* ptr = malloc(100ULL * 1024 * 1024 * 1024);
if(ptr)
    printf("Success!\n");
else
    printf("Failed!\n");

在我们拥有 45GB 内存的系统上,得到的结果是:Failed!。

为什么会这样呢?这是因为在默认情况下,Linux 采用了一种称为 'overcommit(内存超量分配)' 的策略,用来限制进程可分配的内存总量。我们可以查看配置项:

$ cat /proc/sys/vm/overcommit_memory
0

当 overcommit 设置为 0(默认值)时,Linux 基于启发式策略来决定是否允许内存超量分配。它并不严格把分配额度限制在物理内存大小之内,但会尽量确保应用程序不会使用超过物理内存可承受范围的内存。运行二分搜索测试时,最大分配量接近物理内存容量(约 45.33 GB)。

4. 通过 Overcommit 打破限制

修改 overcommit 的设置后,再次尝试:

$ sysctl -w vm.overcommit_memory=1
vm.overcommit_memory = 1

此时运行测试程序,结果为:Success!。

当 overcommit 设置为 1 时,Linux 的策略变得非常'乐观'。它会直接返回你所请求的虚拟内存地址,但在真正访问这些内存之前,并不会实际为其预留对应的物理内存。启用 overcommit 后,最大可分配内存可达 70 TB 左右,远超物理内存容量。

5. malloc() 与 mmap() 的极限对比

不再使用 malloc(),而是直接调用 mmap()。测试结果显示:

# 直接 mmap 分配 125TB
mmap(NULL, 137438953472000, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0)= 0x7f4db1b34000

# malloc 尝试分配 70TB
mmap(NULL, 76965814472704, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0)= 0x39835adaf000

使用直接的 mmap() 调用,最多可以分配 125 TB 的内存,而使用 malloc() 时却只能分配 70 TB。差异在于 malloc() 内部维护了额外的数据结构,而 mmap() 更直接地利用底层虚拟内存映射布局。

6. 实践中的内存分配

基于实验结果,总结内存分配在实际运行中的表现:

6.1 元数据开销

每一次通过 malloc() 进行的内存分配,都会包含一定的内部管理开销。小块内存分配(< 128KB)仅有少量元数据开销,而较大的内存分配则会产生更高的开销。

6.2 使用 mmap() 的分配阈值

在我们的系统上,当分配大小达到或超过 16MB 时,malloc() 就会改为使用 mmap() 来进行内存分配。

6.3 虚拟地址空间碎片化

限制我们最大可分配内存大小的最主要因素是虚拟地址空间的碎片化。直接 mmap() 调用可以更高效地利用底层的虚拟内存映射布局,受碎片化影响相对较小。

7. 理解内存分配的最大限制

综合以上分析,我们得出了不同场景下的内存分配上限:

方法Overcommit 禁用Overcommit 启用
malloc()~45 GB~70 TB
Direct mmap()~45 GB~125 TB

决定这些上限的关键因素包括:

  • 关闭 overcommit 时:内存分配上限主要受限于物理内存容量。
  • 启用 overcommit,使用 malloc() 时:上限受限于 malloc() 的实现机制。
  • 启用 overcommit,使用 mmap() 时:上限主要受限于虚拟地址空间的碎片化程度。

8. 对嵌入式与内核开发的启示

对于嵌入式系统开发者或内核开发者而言,这些发现具有重要的实际意义:

  • 资源受限环境:在 RAM 资源有限的嵌入式系统中,建议将 overcommit 设置为 2,以防止在内存请求无法由物理内存实际支撑时仍然'成功'分配。
  • 内存碎片化问题:在长时间运行的系统中,虚拟地址空间碎片化可能会逐渐成为一个严重问题,从而不断降低单次可分配内存的最大尺寸。
  • 自定义内存分配器:在某些场景下,可能需要实现定制的内存分配器,以更好地满足特定需求。
  • 直接硬件交互:在部分嵌入式系统中,可能需要使用 mmap() 并指定具体的物理地址,来直接映射物理内存区域以进行硬件交互。
  • 内存池机制:对于频繁分配固定大小内存的场景,可以考虑使用内存池(memory pool),以减少内存碎片并降低分配与释放的开销。

目录

  1. 理解 Linux 内存分配:malloc、brk、mmap 关系及 overcommit 参数
  2. 1. 探索虚拟内存布局
  3. 2. 内存分配是如何工作的
  4. 2.1 小块内存分配:堆(Heap)与 brk()
  5. 2.2 大块内存分配:直接使用 mmap()
  6. 3. 内存限制:物理内存(RAM)与虚拟内存
  7. 4. 通过 Overcommit 打破限制
  8. 5. malloc() 与 mmap() 的极限对比
  9. 直接 mmap 分配 125TB
  10. malloc 尝试分配 70TB
  11. 6. 实践中的内存分配
  12. 6.1 元数据开销
  13. 6.2 使用 mmap() 的分配阈值
  14. 6.3 虚拟地址空间碎片化
  15. 7. 理解内存分配的最大限制
  16. 8. 对嵌入式与内核开发的启示
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • Flutter for OpenHarmony 底部导航与 TabBar 多页切换
  • vi/vim 基础操作速查
  • ToDesk 顺网云海马云部署 DeepSeek 模型性能对比测试
  • Web 转移动端:网页打包成 APP 与 Uni-App 小程序低成本部署方案
  • Vheer:免费不限次的 AI 生图与视频生成工具评测
  • Python 字符串验证:从基础到进阶的字母检测方案
  • macOS 平台 notepad--文本编辑器高效配置指南
  • Llama Factory 微调中常见的 5 个配置错误
  • Qt Creator 配置 GitHub Copilot 插件指南
  • 2018 年编程语言趋势与开源生态报告解读
  • Python 转行自学指南:从零开始的全栈学习路径规划
  • Python 数据分析核心技术指南:流程、工具与实战
  • ES6 语法深入解析:进制、Symbol 与类继承
  • 2024 年大模型 LLM 学习路径与技术概览
  • AI 产品经理的十大核心技能
  • 苍穹外卖项目前端开发实战
  • 利用无监督学习为大语言模型实现信息记忆与微调
  • Python 树状结构可视化工具与实战技巧
  • AI 生成代码时代,人类开发者如何保持创意价值?
  • 数据结构:栈、队列、二叉树与哈希表

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online