【Linux指南】动静态库系列(九):动态库如何进入进程地址空间:从磁盘 .so 到共享内存映射

发布时间:2026/9/30 13:52:00
【Linux指南】动静态库系列(九):动态库如何进入进程地址空间:从磁盘 .so 到共享内存映射 文章目录一、动态库为什么比静态库更常用二、动态库也是文件三、动态库加载的整体流程四、从磁盘 .so 到物理内存五、从物理内存到进程虚拟地址空间六、多个进程如何共享同一个动态库七、共享的是代码不是什么都共享八、为什么动态库加载地址不固定九、使用 /proc 查看动态库映射十、使用 pmap 查看进程映射十一、动态链接和静态链接再对比一次十二、为什么动态库需要后续重定位十三、总结前面我们已经知道动态库.so不是在编译链接阶段完整拷贝进可执行程序而是在程序运行时被动态链接器加载。那么问题来了磁盘上的libxxx.so到底是怎么进入进程地址空间的多个程序都依赖同一个动态库时系统真的会给每个进程都复制一份库代码吗为什么说动态库可以节省内存这篇文章就围绕“动态库如何映射到进程地址空间”展开。一、动态库为什么比静态库更常用静态链接会把用到的库代码合并进可执行文件。这样程序可以独立运行但也带来两个问题可执行文件变大。多个程序使用同一个库时会重复保存同一份代码。假设系统里有 100 个程序都用到了 C 标准库。如果每个程序都把 libc 的代码静态链接进去磁盘和内存都会浪费大量空间。动态库的思路是把公共代码单独放在 .so 文件中 程序运行时再加载 多个进程尽量共享同一份库代码。这就是动态库广泛使用的重要原因。二、动态库也是文件先明确一点动态库不是内存里凭空出现的东西它首先是磁盘上的一个普通文件。例如ls-l/lib64/libc.so.6file/lib64/libc.so.6你会发现它是一个 ELF shared object。所以加载动态库的第一步仍然是文件操作找到 .so 文件 打开 .so 文件 读取 ELF 信息 把需要的 segment 映射进内存只不过这个过程通常由动态链接器和内核协作完成程序员平时不直接感知。三、动态库加载的整体流程一个动态链接程序启动时大致流程如下1. 内核加载主程序 ELF 2. 发现主程序需要动态链接器 3. 动态链接器读取主程序依赖的 .so 列表 4. 找到每个 .so 文件 5. 将 .so 的代码段、数据段等映射到进程地址空间 6. 完成必要的符号解析和重定位 7. 进入程序 main 函数如果第 4 步找不到库就会出现我们前一篇讲的libxxx.so not found如果能找到库下一步就是把库映射到进程地址空间中。四、从磁盘 .so 到物理内存当程序依赖libmyc.so时动态链接器会找到磁盘上的库文件。然后系统会把库中需要加载的 segment 放入物理内存尤其是代码段.text对应的内容。可以先粗略理解为磁盘 libmyc.so - 加载到物理内存中的若干页现代系统很多时候还会用文件映射、按需分页等机制并不是一开始就把整个库所有内容都读入内存。但从理解动态库共享的角度我们先抓住核心库文件的内容最终要对应到物理内存页。五、从物理内存到进程虚拟地址空间进程不能直接使用物理地址。每个进程看到的是自己的虚拟地址空间。所以动态库加载后还需要在当前进程的虚拟地址空间中划出一段区域用来映射这份库。可以理解为进程虚拟地址空间中的一段共享区 - 通过页表 - 映射到物理内存中的 libmyc.so 代码页例如进程 A 中libmyc.so可能映射到0x7f1000000000 ~ 0x7f1000010000这只是进程 A 自己看到的虚拟地址范围。真正的物理内存在哪里进程并不直接关心。CPU 会通过页表完成转换。六、多个进程如何共享同一个动态库假设进程 A 和进程 B 都依赖libmyc.so。进程 A 启动时磁盘 libmyc.so - 物理内存代码页 进程 A 的虚拟共享区 - 映射到这些物理页进程 B 启动时如果系统发现这份库的代码页已经在物理内存中就不必再加载一份完整代码。它只需要给进程 B 分配自己的虚拟共享区 让进程 B 的页表也映射到同一批物理代码页于是形成进程 A 虚拟地址 0x7f1000000000 - 同一份物理库代码 进程 B 虚拟地址 0x7f2000000000 - 同一份物理库代码注意两个进程的虚拟地址可以不同 但它们可以映射到同一份物理内存。这就是动态库节省内存的关键。七、共享的是代码不是什么都共享动态库中并不是所有内容都能随便共享。一般来说只读代码段可以共享 只读数据可以共享 可写数据通常不能直接在进程间共享。原因很简单如果动态库中的全局变量被多个进程共享那进程 A 修改变量会影响进程 B这显然不符合普通进程隔离原则。所以动态库的可写数据部分通常会为每个进程提供独立映射或者通过写时拷贝等机制保证进程隔离。动态库节省内存主要依赖代码段只读可以被多个进程共享。这也是为什么动态库代码要尽量做到位置无关不能随便修改代码段本身。八、为什么动态库加载地址不固定每个进程都有自己的虚拟地址空间。不同进程加载了不同的程序、不同的库地址空间中空闲区域也不同。所以动态链接器不能假设所有进程都把libmyc.so放到同一个虚拟地址。它通常会在当前进程地址空间中选择一段合适的空闲区域把动态库映射进去。这就带来一个关键问题动态库如果被加载到任意地址里面的函数调用和全局变量访问怎么保证正确这就是下一篇 GOT/PIC 要解决的问题。九、使用 /proc 查看动态库映射Linux 提供了/proc/[pid]/maps可以查看进程地址空间映射。先运行一个程序让它保持一段时间./main如果程序很快退出可以在代码中加sleep(100)。然后查看pidof maincat/proc/进程PID/maps你会看到类似7f... r-xp ... /lib64/libc.so.6 7f... r--p ... /lib64/libc.so.6 7f... rw-p ... /lib64/libc.so.6这些行说明libc.so.6的不同部分被映射到了进程虚拟地址空间中并且权限不同r-xp可读可执行通常对应代码段 r--p只读 rw-p可读可写通常对应数据段这能非常直观地看到动态库确实进入了进程地址空间。十、使用 pmap 查看进程映射也可以使用pmap 进程PID它会以更简洁的形式展示进程内存映射。例如pmap$(pidof main)可以看到主程序、堆、栈、动态库等区域。学习动态库时/proc/[pid]/maps是非常有价值的观察工具。十一、动态链接和静态链接再对比一次静态链接库代码在链接阶段进入可执行文件 程序运行时不再找库 多个程序各自包含一份库代码。动态链接可执行文件记录依赖 程序运行时加载 .so 多个进程可以共享同一份库代码物理页。动态链接本质上把一部分链接工作推迟到了程序加载和运行阶段。这带来灵活性也带来复杂性。十二、为什么动态库需要后续重定位动态库被映射到进程地址空间后它的加载地址才真正确定。但库代码中可能需要访问库内函数 库内全局变量 其他动态库函数 主程序中的符号有些地址必须在加载后才能知道。所以动态链接器还要做符号解析和重定位让这些引用指向正确位置。问题是代码段通常是只读且共享的不能每个进程都直接修改代码段里的地址。因此动态链接需要一种更巧妙的机制把可能变化的地址放到可写表中代码通过查表跳转。这张表就是 GOT全局偏移表。十三、总结动态库加载不是简单地“把.so拷贝进进程”。更准确的理解是动态链接器找到 .so 内核把 .so 的 segment 映射进进程虚拟地址空间 多个进程可以通过各自页表映射到同一份物理代码页 动态链接器再完成必要的符号解析和重定位动态库节省内存的关键在于只读代码段可以共享。但动态库加载地址不固定这就要求动态库不能把绝对地址写死在代码里。下一篇我们继续讲 GOT 与 PIC看动态库如何做到“加载到哪里都能运行”。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询