注意:我将假设您的计算机具有内存映射单元(MMU)。有一个不需要MMU的Linux版本(µClinux),此答案不适用于该版本。
什么是MMU?它是硬件 -处理器和/或内存控制器的一部分。了解共享库链接并不需要您确切了解MMU的工作原理,只是MMU允许逻辑内存地址(程序使用的地址)和物理内存地址之间存在差异。内存地址(实际存在于内存总线上的地址)。内存分为几页,在Linux上通常为4K。对于4k页,逻辑地址0–4095是页0,逻辑地址4096–8191是页1,等等。MMU将那些地址映射到RAM的物理页,每个逻辑页通常可以映射到0或1个物理页。给定的物理页面可以对应于多个逻辑页面(这是共享内存的方式:多个逻辑页面对应于同一物理页面)。请注意,这适用于任何操作系统;这是对硬件的描述。
在进程切换时,内核会更改MMU页面映射,以便每个进程都有自己的空间。进程1000中的地址4096可以(通常是)与进程1001中的地址4096完全不同。
几乎每当您看到一个地址时,它就是一个逻辑地址。用户空间程序几乎不会处理物理地址。
现在,还有多种构建库的方法。假设某个程序调用foo()
了库中的函数。CPU对符号或函数调用一无所知,它只知道如何跳转到逻辑地址并执行在其中找到的任何代码。它可以通过两种方法来执行此操作(当库访问其自己的全局数据时,类似的情况也适用):
- 它可以对一些逻辑地址进行硬编码以对其进行调用。这要求将库始终加载到完全相同的逻辑地址。如果两个库需要相同的地址,则动态链接将失败,并且您将无法启动该程序。库可能需要其他库,因此,这基本上要求系统上的每个库都具有唯一的逻辑地址。但是,如果可以的话,速度非常快。(这是a.out的工作方式,以及预链接的设置方式)。
- 它可以对伪造的逻辑地址进行硬编码,并告诉动态链接器在加载库时以正确的地址进行编辑。加载库时,这花费了相当多的时间,但是之后非常快。
- 它可能会增加一个间接层:使用CPU寄存器保存加载库的逻辑地址,然后访问所有内容作为该寄存器的偏移量。这会给每次访问带来性能成本。
几乎没有人使用#1,至少在通用系统上没有。在32位系统(没有足够的存储空间)上,保持唯一的逻辑地址列表是不可能的,而在64位系统上,这是管理上的噩梦。不过,预链接是在每个系统的基础上进行的。
使用#2还是#3取决于库是否使用GCC -fPIC
(与位置无关的代码)选项构建。#2没有,#3是。通常,库是使用构建的-fPIC
,因此会发生#3。
有关更多详细信息,请参见Ulrich Drepper的如何编写共享库(PDF)。
因此,最后,您的问题可以得到回答:
- 如果该库是使用构建的
-fPIC
(几乎可以肯定应该如此),则每个加载该库的进程的绝大多数页面都是完全相同的。您的流程a
和b
可能装载在不同的逻辑地址库中,但那些将指向同一个物理页:内存将被共享。此外,RAM中的数据与磁盘上的数据完全匹配,因此只有页面错误处理程序需要时才可以加载它。
- 如果构建的库没有
-fPIC
,那么事实证明该库的大多数页面都需要链接编辑,并且将有所不同。因此,它们必须是单独的物理页面(因为它们包含不同的数据)。这意味着他们没有共享。这些页面与磁盘上的内容不匹配,因此如果加载了整个库,我不会感到惊讶。当然,随后可以将其换出到磁盘(在swapfile中)。
您可以使用该pmap
工具或直接通过检查中的各种文件来进行检查/proc
。例如,这是pmap -x
两个不同的新生成的bc
s 的(部分)输出。请注意,pmap显示的地址通常是逻辑地址:
pmap -x 14739
Address Kbytes RSS Dirty Mode Mapping
00007f81803ac000 244 176 0 r-x-- libreadline.so.6.2
00007f81803e9000 2048 0 0 ----- libreadline.so.6.2
00007f81805e9000 8 8 8 r---- libreadline.so.6.2
00007f81805eb000 24 24 24 rw--- libreadline.so.6.2
pmap -x 17739
Address Kbytes RSS Dirty Mode Mapping
00007f784dc77000 244 176 0 r-x-- libreadline.so.6.2
00007f784dcb4000 2048 0 0 ----- libreadline.so.6.2
00007f784deb4000 8 8 8 r---- libreadline.so.6.2
00007f784deb6000 24 24 24 rw--- libreadline.so.6.2
您可以看到该库分为多个部分加载,并分别pmap -x
提供了每个部分的详细信息。您会注意到两个进程之间的逻辑地址是不同的。您可以合理地期望它们是相同的(因为运行相同的程序,并且计算机通常是可以预测的),但是有一种称为地址空间布局随机化的安全功能可以有意地将它们随机化。
从大小(千字节)和驻留大小(RSS)的差异中可以看出,整个库段尚未加载。最后,您可以看到对于较大的映射,dirty为0,这意味着它与磁盘上的内容完全对应。
您可以重新运行pmap -XX
,它会显示-取决于您运行的内核版本,因为-XX输出随内核版本的不同而有所不同-第一个映射Shared_Clean
的176与完全匹配RSS
。Shared
内存意味着物理页面在多个进程之间共享,并且由于它与RSS匹配,因此意味着内存中的所有库都是共享的(请参阅下面的另请参见以了解共享与私有的进一步说明):
pmap -XX 17739
Address Perm Offset Device Inode Size Rss Pss Shared_Clean Shared_Dirty Private_Clean Private_Dirty Referenced Anonymous AnonHugePages Swap KernelPageSize MMUPageSize Locked VmFlagsMapping
7f784dc77000 r-xp 00000000 fd:00 1837043 244 176 19 176 0 0 0 176 0 0 0 4 4 0 rd ex mr mw me sd libreadline.so.6.2
7f784dcb4000 ---p 0003d000 fd:00 1837043 2048 0 0 0 0 0 0 0 0 0 0 4 4 0 mr mw me sd libreadline.so.6.2
7f784deb4000 r--p 0003d000 fd:00 1837043 8 8 8 0 0 0 8 8 8 0 0 4 4 0 rd mr mw me ac sd libreadline.so.6.2
7f784deb6000 rw-p 0003f000 fd:00 1837043 24 24 24 0 0 0 24 24 24 0 0 4 4 0 rd wr mr mw me ac sd libreadline.so.6.2
也可以看看
-fPIC
用法在一段时间前已完全改变)?