首页 游戏 软件 资讯 排行榜 专题
首页
业界动态
图解 Linux 内存管理:虚拟内存、malloc、缺页中断,一次搞懂

图解 Linux 内存管理:虚拟内存、malloc、缺页中断,一次搞懂

热心网友
77
转载
2026-04-22

Linux 内存管理:一场由“懒惰”驱动的效率革命

Linux 内存管理的精妙之处,在于它巧妙地将几种“懒惰”哲学叠加在一起。正是这套组合拳,让系统能够在有限的内存资源上,高效地运行成百上千个进程,同时还能牢牢守住进程间的隔离墙。

上一期我们探讨文件系统时,提到了Page Cache如何占用内存,以及内核如何管理页(Page)。不少读者看完后留言追问:

“内存到底是怎么管理的?为什么32位系统每个进程有4GB地址空间,但机器只有2GB内存?malloc申请的内存什么时候才真正分配?”

今天,我们就来把这些疑问一次性彻底讲透。

一、先从一个“骗局”说起

想象这样一个场景:你写了一个C++程序,用malloc(1GB)申请1GB内存,而运行这台机器的物理内存只有512MB——结果,申请居然成功了。

void *p = malloc(1024 * 1024 * 1024); // 1GB
if (p) printf(“申请成功!\n”);        // 真的会打印这句

这可不是什么系统漏洞,而是Linux内存管理核心机制在起作用:虚拟内存。它从一开始,就给所有进程画了一张“大饼”。

二、虚拟内存:每个进程都有自己的“假地址空间”

Linux为每个进程都提供了一个独立的、连续的地址空间幻觉,这就是虚拟地址空间。在64位系统上,这个空间理论上有128TB之巨(实际可用的用户空间也接近这个数字)。但请注意,这仅仅是“地址”,并非真实的内存。虚拟地址必须经过MMU(内存管理单元)的翻译,才能对应到真实的物理内存页。

下面这张图清晰地展示了虚拟地址空间的完整布局:

进程的虚拟地址空间从低到高依次是:

  • 保留区(0x0附近):不可访问,空指针解引用崩溃就发生在这里。
  • 代码段(text):只读可执行,存放程序指令。
  • 数据段:已初始化的全局/静态变量。
  • BSS段:未初始化的全局/静态变量(运行时清零)。
  • 堆(heap):向上增长,malloc/new从这里分配内存。
  • 内存映射区(mmap):共享库、文件映射、大块malloc的归宿。
  • 栈(stack):向下增长,存放函数局部变量和调用链。
  • 内核空间:所有进程共享,映射内核代码和数据。

三、虚拟地址到物理地址:MMU和页表

虚拟地址空间是“假的”,CPU真正访问内存时,必须把虚拟地址翻译成物理地址。这个翻译工作由硬件MMU完成,而翻译的依据,则是内核维护的页表。

Linux以4KB为一页(Page)为单位管理内存。虚拟地址被分成几段索引,通过逐级查询页表,最终得到物理地址:

在Linux x86-64架构下,采用四级页表(PGD → PUD → PMD → PTE)结构。这意味着一次地址翻译最多可能需要四次内存访问。为了提速,CPU内部有一个TLB(翻译后备缓冲器)缓存最近的翻译结果。一旦命中,就能直接得到物理地址,完全跳过繁琐的四级查询。

四、缺页中断:内存是“用时才分配”的

现在回到文章开头那个问题——malloc(1GB)为什么能在512MB的机器上“成功”?

答案是:malloc的“成功”,仅仅是在虚拟地址空间里预留了一段地址范围,并没有立刻分配物理内存。物理内存的分配,要等到你真正去访问那个地址时才会发生。

这套机制叫做缺页中断,堪称Linux内存管理最精妙的设计之一。

char *p = malloc(1024 * 1024 * 1024); // 只分配虚拟地址,物理页未分配
p[0] = ‘A’;  // 第一次写入:触发缺页中断!内核此时才真正分配物理页
p[1] = ‘B’;  // 这个物理页已存在,直接写入即可

缺页中断的完整流程如下:

缺页中断主要有三种典型场景:

  • 匿名页缺页malloc后第一次访问,内核分配新的物理页,清零后映射。
  • 文件页缺页mmap映射文件后第一次访问,内核从磁盘读取数据到Page Cache。
  • 写时复制(COW)缺页fork()后子进程尝试写入父进程的页,内核会复制一份新的物理页给子进程。

五、fork()与写时复制:最优雅的懒惰

fork()创建子进程时,子进程理论上需要复制父进程的全部内存。如果父进程有1GB数据,难道每次fork()都要拷贝1GB吗?

Linux用写时复制(Copy-On-Write,COW)完美解决了这个问题。fork()之后,父子进程共享同一批物理页,页表项都被标记为只读。谁第一个尝试写入,才会触发缺页中断,内核此时才会复制一份新的物理页给写入方。如果没有写操作,物理页就永远不需要复制。

int main() {
    char *buf = malloc(4096);
    strcpy(buf, “hello”);
    pid_t pid = fork();  // fork后父子共享物理页,页被标记为只读
    if (pid == 0) {
        buf[0] = ‘H’;    // 子进程写入 → 触发COW缺页 → 子进程获得独立副本
    }
    // 父进程的buf依然是“hello”,不受影响
}

这就是为什么fork()操作极其快速——无论父进程占用了多少内存,fork()本身的耗时几乎是恒定的。

六、malloc的真相:不只是向内核要内存

很多人以为malloc就是直接调用系统调用向内核要内存。其实不然。

malloc是C标准库(通常是glibc)提供的函数,它在内核和用户程序之间做了一层高效的内存池管理:

  • 首次malloc:调用brk()mmap()向内核申请一大块内存。
  • 后续malloc:从glibc已申请的内存池里切割,无需再进入内核。
  • free:把内存块还给glibc的内存池,但不一定立刻还给内核。
  • 内存池空闲块过多时:glibc才会调用brk()munmap()将部分内存归还给内核。
用户程序                    glibc (ptmalloc)              内核
   │                             │                          │
   ├── malloc(64B)  ────────►    │ 从内存池切一块           │
   │                             │ 池空了? ──brk(+128KB)──► │
   │                             │ ◄── 映射新虚拟内存 ──────┤
   ├── free(p)      ────────►    │ 放回内存池               │
   │                             │ 池太大? ──brk(-64KB)───► │ 还给内核

这也解释了为什么内存泄漏有时难以从系统层面立刻察觉:free之后,内存可能仍然留在glibc的池里,进程占用的RSS(常驻内存)不会立刻下降,但从业务逻辑上看,内存已经“泄漏”了。

七、内存大小那些概念:VSZ、RSS、PSS傻傻分不清?

top命令里看到的各种内存数字,常常让人困惑:

VIRT    RES    SHR
2.1g   156m    42m
  • VIRT(VSZ):虚拟内存大小,包含了进程所有映射的虚拟地址空间,通常远大于实际使用的物理内存。
  • RES(RSS):常驻内存大小,进程实际占用的物理内存(包含共享库占用的部分)。
  • SHR:与其他进程共享的物理页(主要是共享库.so文件)。

所以,一个进程实际独占的物理内存,大致可以估算为:RSS - SHR。看到VIRT很大不必惊慌,关键要看RSS和SHR。

八、内存回收:内核什么时候把内存要回去?

当物理内存不够用时,内核的kswapd进程就开始扮演“清道夫”的角色,回收内存:

  • 干净的文件页(clean page):直接丢弃(因为磁盘上有原始数据,需要时重新读取即可)。
  • 脏的文件页(dirty page):先写回磁盘,再丢弃。
  • 匿名页(anonymous page):写入swap分区,腾出物理内存。

当内存压力极大,上述回收机制仍无法满足需求时,就会触发最后的“杀手锏”——OOM Killer。内核会强行选择一个进程杀掉以释放内存。

# 系统日志里看到这个就是 OOM 了
kernel: Out of memory: Kill process 1234 (myapp) score 900 or sacrifice child

OOM Killer根据“最该死”的分数(oom_score)来选择目标。通常,内存占用大、运行时间短的进程得分最高,最容易被终结。可以通过调整进程的oom_score_adj值来保护重要进程。

九、高频面试题精析

Q:32位系统每个进程有4GB虚拟空间,但物理内存只有2GB,能运行多少个进程?

理论上可以运行很多个。因为每个进程的虚拟地址空间是独立的,物理页是按需分配的(触发缺页中断时才分配)。更重要的是,多个进程的代码段、共享库可以映射到同一物理页,这能大量节省内存。

Q:malloc(0)返回什么?

C语言标准规定,malloc(0)应返回一个非NULL的唯一指针(该指针可以被free),但指向的内存大小为0,不可解引用。glibc的实现确实会返回一个合法的指针。

Q:free之后内存会立刻归还给操作系统吗?

通常不会。glibc的free只是把内存块放回自己的内存池。只有当内存池中的空闲块超过一定阈值时,glibc才会调用sbrk()munmap()将内存归还给内核。这也是为什么使用Valgrind检测内存泄漏时,要求程序退出前必须free所有内存,否则工具无法区分是“真泄漏”还是“内存仍在glibc池中”。

Q:为什么栈溢出(stack overflow)会直接崩溃,而不是触发缺页中断分配新内存?

内核在栈的底部预留了一个不可访问的保护页(guard page)。当栈溢出时,访问会触及这个保护页,从而触发缺页中断。内核检查对应的VMA(虚拟内存区域)后,发现这是非法访问,于是直接向进程发送SIGSEGV信号,导致其崩溃。

结语

说到底,Linux内存管理的核心思路,是几种“懒惰”哲学的叠加与协同:

  • 虚拟内存:先分地址,物理内存用时再说。
  • 缺页中断:访问才分配,按需加载。
  • 写时复制:fork时不复制,写时才复制。
  • glibc内存池:批量向内核要,批量给用户用。

正是这几大机制的精妙组合,让Linux得以在有限的内存资源上,高效、稳定地运行海量进程。

透彻理解这些机制,你才能真正读懂内存泄漏检测工具的原理,明白为什么高性能程序需要自定义内存池,也才能在面试中,将“内存管理”这道题讲得深入&浅出,令人信服。

来源:https://www.51cto.com/article/838675.html
免责声明: 游乐网为非赢利性网站,所展示的游戏/软件/文章内容均来自于互联网或第三方用户上传分享,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系youleyoucom@outlook.com。

相关攻略

Linus Torvalds 提醒开发者 AI 再强也需独立思考
业界动态
Linus Torvalds 提醒开发者 AI 再强也需独立思考

在近日举行的北美开源峰会上,Linux创始人林纳斯·托瓦兹分享了一个深刻洞察:人工智能技术正悄然重塑Linux内核开发的节奏与生态。 托瓦兹指出,自Git版本控制系统确立稳定的发布流程以来,Linux内核的迭代周期已平稳运行近二十年。然而,过去半年间,这一长期形成的稳定节奏出现了显著波动。 代码提交

热心网友
05.23
Ubuntu系统安装OpenClaw详细步骤教程
AI资讯
Ubuntu系统安装OpenClaw详细步骤教程

第一步:彻底卸载旧版 Node js 为确保安装过程顺利,避免版本冲突,我们首先需要完全移除系统中可能存在的旧版本 Node js 及其关联组件。 请打开终端,依次执行以下命令: apt remove --purge -y nodejs libnode-dev npm 该命令将彻底卸载 Node j

热心网友
05.20
Linux系统Nginx服务器HTTPS证书安装配置教程
系统平台
Linux系统Nginx服务器HTTPS证书安装配置教程

为Nginx启用HTTPS加密,看似复杂实则核心步骤清晰。关键在于确保Nginx编译时已包含--with-http_ssl_module模块,并正确配置证书与私钥的绝对路径及严格权限(私钥文件权限应为600)。实现HTTPS服务的最小化配置仅需三行指令:listen 443 ssl、ssl_cert

热心网友
05.20
Linux批量重命名文件教程:rename与mv命令详解
系统平台
Linux批量重命名文件教程:rename与mv命令详解

Linux系统批量重命名文件有多种方法。基础方法是使用mv命令配合for循环,适合简单的前缀、后缀修改。C语言版rename命令可进行直接字符串替换。功能更强的Perl版rename支持正则表达式,能实现复杂模式匹配。mmv工具通过通配符映射,适合结构化重命名。无论使用哪种方法,都建议先通过预览模式确认操作,避免误改。

热心网友
05.20
Kubernetes Dashboard安装与配置详细图文教程
系统平台
Kubernetes Dashboard安装与配置详细图文教程

默认部署KubernetesDashboard后服务类型为ClusterIP,无法从外部访问。需将Service类型改为NodePort并指定30000-32767范围内的端口,才能通过浏览器直接访问。登录失败常因缺少权限绑定、token过期或命名空间错误。临时调试可使用port-forward,但生产环境不推荐。部署前需确保集群基础配置正确,避免后续问题。

热心网友
05.20

最新APP

宝宝过生日
宝宝过生日
应用辅助 04-07
台球世界
台球世界
体育竞技 04-07
解绳子
解绳子
休闲益智 04-07
骑兵冲突
骑兵冲突
棋牌策略 04-07
三国真龙传
三国真龙传
角色扮演 04-07

热门推荐

面壁智能开源全双工全模态模型MiniCPM-o 4.5详解
AI资讯
面壁智能开源全双工全模态模型MiniCPM-o 4.5详解

MiniCPM-o 4 5是什么 在探索更自然、更智能的人机交互道路上,我们始终在期待一个“全能型选手”的到来。如今,这个角色或许已经登场。面壁智能最新开源的MiniCPM-o 4 5,一个仅拥有90亿参数的全模态大模型,正致力于重新划定“智能对话”的边界。 它彻底颠覆了传统一问一答的“对讲机”式交

热心网友
05.23
2025欧易OKX官网正版APP下载入口及安全获取教程
web3.0
2025欧易OKX官网正版APP下载入口及安全获取教程

Binance币安 欧易OKX ️ Huobi火币️ 想在2025年安全获取欧易OKX的正版APP?其实秘诀就一个:认准官方网站,避开所有仿冒和可疑的下载渠道。要知道,欧易现已统一更名为欧易OKX,其核心业务始终围绕数字资产交易及相关服务展开。 确认官方网站地址 第一步,打开浏览器,手动输入欧易OK

热心网友
05.23
国产AI社交平台SecondMe:真人发帖与智能互动体验
AI资讯
国产AI社交平台SecondMe:真人发帖与智能互动体验

SecondMe Book是什么 在AI社交这一前沿赛道,一款国产平台正带来独特的解决方案。SecondMe Book,本质上是一个能够让你构建个人AI数字分身的创新平台。它允许用户创建一个能够代表真实自我风格与思维的AI数字身份,并让这个“第二自我”在一个专属的AI社交网络中自主运行——包括主动发

热心网友
05.23
阶跃星辰开源Step 3.5 Flash基座模型详解
AI资讯
阶跃星辰开源Step 3.5 Flash基座模型详解

在AI大模型技术快速发展的今天,如何在卓越性能与高效推理成本之间取得最佳平衡,已成为行业关注的核心焦点。近期,由阶跃星辰推出的开源模型Step 3 5 Flash引发了广泛热议。该模型专为智能体(AI Agent)应用场景深度优化,旨在顶尖能力与亲民部署成本之间,构建一个极具竞争力的技术支点。 简而

热心网友
05.23
美团开源LongCat大语言模型Flash Lite版本详解
AI资讯
美团开源LongCat大语言模型Flash Lite版本详解

LongCat-Flash-Lite是什么 在探索大语言模型性能与效率的最佳平衡点时,美团近期推出的LongCat-Flash-Lite提供了一个极具创新性的解决方案。作为新一代高效大语言模型,它凭借其突破性的架构设计,在人工智能领域获得了广泛关注。 简而言之,该模型创新性地融合了“混合专家系统(M

热心网友
05.23