Linux 进程内存布局怎么看:代码段、堆、栈与内存映射
梳理 Linux 进程地址空间中的代码段、已初始化数据段、BSS、堆、共享库映射区和栈,并解释虚拟地址与物理地址如何衔接。
相关工具
一个进程看到的是自己的地址空间
在 Linux 中,每个进程通常都以为自己拥有一片连续而宽广的虚拟地址空间。程序使用的是虚拟地址,处理器和内核再把这些地址转换到实际的物理内存。不同进程可以使用相同的虚拟地址数值,却映射到不同的物理页面。
这种安排让进程不必直接关心物理内存当前放在哪里,也让操作系统能够按需装入页面、回收暂时不用的页面,并限制进程之间的访问范围。理解进程内存布局,第一步就是把“程序使用的地址”与“内存条上的位置”分开。
代码、数据、堆、映射区和栈位于进程自己的虚拟地址空间中,再由页表映射到物理页面。
代码段保存什么
代码段通常保存程序的机器指令,也就是处理器即将执行的内容。为了避免运行中的程序把自己的指令当成普通数据覆盖,代码段一般具有可读、可执行但不可随意写入的访问属性。只读常量也可能放在与代码相关的只读区域中。
程序启动时,操作系统并不一定把整个可执行文件一次性复制进物理内存。通过虚拟内存和按需调页,只有真正访问到的代码页面才可能被装入。多个进程使用同一个只读代码页面时,系统还可以让它们共享物理页面,减少重复占用。
DATA 和 BSS 有什么区别
已初始化数据段保存已经写入初始值的全局变量和静态变量,例如程序启动前就确定为某个数值的变量。它们的初始内容通常来自可执行文件,进程装载时需要建立对应的数据页面。
BSS 段保存未显式初始化或初始化为零的全局变量和静态变量。可执行文件不必为大量连续的零值保存完整内容,只要记录这片区域的大小,装载时由系统建立并清零即可。两者都属于程序的静态数据区域,但来源和占用文件空间的方式不同。
堆是怎样增长的
堆用于支持运行期间的动态内存分配。程序申请内存时,运行库和内核共同决定是否从已有空闲区域切出一块,或者向系统请求更多虚拟地址空间。堆通常从较低地址方向向更高地址扩展,但具体边界会受到实现和地址布局随机化影响。
申请内存成功不等于所有物理页面已经准备好。操作系统可以先建立虚拟地址范围,等程序真正写入某个页面时再分配物理页面。释放内存后,运行库可能先把空间留在进程内部复用,也可能把部分区域交还给系统。于是,程序申请量、堆边界和实际物理占用并不是同一个数字。
堆可以先扩展虚拟地址范围,程序真正访问页面时才触发物理内存分配。
内存映射区为什么重要
内存映射区用于把文件内容、共享库和共享内存等对象映射到进程地址空间。程序访问映射区域时,看起来像是在读写内存;系统则可以根据映射关系从文件或其他共享对象中装入页面。这样,文件 I/O 和内存访问之间建立了直接联系。
动态链接库通常也通过内存映射区进入进程。多个进程可以共享库中只读的代码页面,同时为各自的可写数据建立独立映射。共享内存则允许不同进程明确指向同一组物理页面,但共享关系仍需要同步机制配合。
栈保存函数调用的现场
栈用于保存函数调用过程中产生的局部变量、参数、返回地址以及部分寄存器现场。函数调用时,栈上会建立新的调用帧;函数返回后,对应的调用帧被撤销。栈的管理方式与函数调用结构天然匹配,因此分配和释放通常很快。
栈空间通常从较高地址向较低地址增长,和堆保留出相对方向相反的扩展空间。递归过深、局部数组过大或不断创建过大的调用帧,都可能让栈超出允许范围。栈溢出本质上是一次越过地址边界的内存访问问题。
虚拟地址怎样变成物理地址
程序发出的虚拟地址通常可以拆成虚拟页号和页内偏移。页表根据虚拟页号找到对应的物理页框,页内偏移保持不变,二者组合成最终的物理地址。若页表中没有有效映射,处理器会触发缺页异常,由内核判断是否需要分配页面、从存储设备装入,或认定访问非法。
地址转换过程中还会检查页面权限,例如是否允许用户态访问、是否可以写入或执行。TLB 用来缓存近期的虚拟页到物理页映射,减少每次访问都完整查询页表的成本。虚拟内存的便利和访问保护,正是在这套转换机制上同时完成的。
虚拟页号经过 TLB 或页表查找得到物理页框,页内偏移保持不变并参与权限检查。
为什么内存布局还要做随机化
如果每个进程的代码、堆、库和栈总是位于固定地址,攻击者更容易猜测目标数据或指令的位置。地址空间布局随机化会在进程启动时改变部分区域的位置,让同一种程序在不同运行中拥有不同的地址布局。
随机化不是单独的保护边界,也不能替代权限位、栈保护和输入校验。它的作用是增加猜测地址的难度,让某些依赖固定地址的攻击更难稳定复现。把它放进整体内存保护模型中,才能准确理解它的作用范围。
用一条主线理解进程内存布局
代码段放指令,DATA 和 BSS 放静态数据,堆服务于动态分配,映射区连接文件与共享对象,栈保存函数调用现场。它们共同构成进程看到的虚拟地址空间,再通过页表、TLB 和缺页处理与物理内存衔接。
排查内存问题时,可以先问问题发生在哪个区域:是栈空间耗尽,堆分配失败,映射文件访问异常,代码段被错误写入,还是页表权限不允许当前操作。区域不同,触发机制和处理方向也不同。
常见问题
堆和栈有什么根本区别?
堆主要服务于运行期间的动态分配,生命周期由程序和运行库管理;栈主要保存函数调用现场,随调用进入和返回自动建立与撤销。
申请了很多内存就一定占用了同样多的物理内存吗?
不一定。系统可以先建立虚拟地址范围,等页面真正被访问时再分配物理内存,运行库也可能保留已申请区域以便后续复用。
内存映射区只能映射文件吗?
不只能。它还可以承载共享库、共享内存和匿名映射等对象,具体用途由映射方式和访问权限决定。