重建和安装共享库不会影响已加载该库的进程



我有一个关于多个进程使用的共享库的问题。

我有一个共享库libfoo.so它由两个不同的进程使用,process1process2 .

第一个进程(process1)处于运行状态,libfoo.so加载到内存中。我对libfoo.so代码进行了一些修改,重建并安装,然后开始process2。新process2已加载新安装的库libfoo.so

但是process1仍在使用较旧的libfoo.so运行。如果我重新启动process1,那么它会按预期加载新安装的libfoo.so

如果操作系统具有共享库的单个副本,那么为什么安装新的共享库不会影响当前正在运行的进程?

如果操作系统具有共享库的单个副本,那么为什么安装新的共享库不会影响当前正在运行的进程?

首先,你对单个副本的整个概念有些缺陷。

让我们谈谈ELF共享库(这些概念也适用于其他类型的库,尽管细节不同)。

ELF 共享库通常至少有两个可加载段:只读段和可写段。第一个包含只读数据、程序代码(通常称为.text)、重新定位部分等。第二段包含已初始化但可写的数据(通常称为.data)。

当两个进程运行并使用相同的libfoo.so时,libfoo.so将使用至少页内存:至少一页用于"覆盖"只读段(该页面将在两个运行进程之间共享),每个进程中至少有一个单独的页面来"覆盖"可写段。

从中可以看出,当库由正在运行的程序使用时,磁盘上共享库的单个副本也会复制到 RAM 中的多个副本中。

其次,我们需要谈谈你如何更新libfoo.so。您可以通过以下两种方式之一执行此操作:

  1. 你可以这样做: rm -f libfoo.so; gcc -shared -o libfoo.so foo.o
  2. 你可以这样做:gcc -shared -o libfoo.so foo.o.

在第一种情况下,您根本不影响任何mmap ed libfoo.so的进程:libfoo.so 的旧数据将保留在磁盘上,但对任何尚未open ed 或 mmap ed 的进程不可见。 另请注意,如果libfoo.so的大小为 1GB, 您的磁盘使用量将增加 1GB(旧副本和新副本仍在占用磁盘空间)。

在第二种情况下,您正在就地更新libfoo.so(不建议这样做,原因很快就会变得明显)。libfoo.so的索引节点编号将保持不变,旧数据将消失。您的磁盘使用量将保持不变(假设新libfoo.so的大小与旧磁盘的大小大致相同)。

这将影响任何正在运行的进程,但可能不是以您期望的方式。最有可能的结果是正在运行的进程将崩溃。

为什么会这样?把图书馆想象成一本书,里面有目录。在初始库加载期间,目录将被加载到 RAM 中并修改(因为共享库可以在内存中的任意位置加载)。如果您现在更新书籍(磁盘上的图书馆),例如第 3 章变长 3 页,那么目录将不再有效(至少对于第 4 章到结尾)。任何遵循目录中指针的尝试都不会让你在你正在寻找的章节的开头,而是在一章的中间。因此,您将调用一个函数,并降落在不同的函数中间。最可能的结果是崩溃。

需求分页使情况更加复杂。您可能已经分页了某些章节,但没有其他章节。因此,您可能不会发现您的流程实际上是在更新后立即进行的。如果您的库很小,您可能根本无法发现这一点。

附言某些操作系统禁止第二种形式的更新:如果某个进程当前正在使用库,则打开库进行写入失败并显示ETXTBSY。Linux 对某些文件系统执行此操作,但不是全部。

最新更新