共享内存中的不可重入库?

共享内存中的不可重入库?

我发现本次问答说共享库可以在使用共享内存的进程之间共享。然而,如果不对可共享的代码类型进行相当严格的限制,似乎不可能在进程之间共享代码。我正在考虑具有不可重入 C 函数的库,其输出取决于其定义主体内的全局变量或静态变量的值。像这个。

int really_really_nonreentrant(void x)
{
    static int i = 0;
    i++;
    return i;
}

具有这样的函数的库将为使用它的每个进程返回单独的递增序列,因此它看起来绝对像代码不是在进程之间共享。 real_really_nonreentrant() 是否与可重入函数分离,或者它主要与其他函数一起保留,仅将 static int i 分离出来?或者整个库被排除在共享内存之外,因为这个函数是不可重入的?

最终,需要做多少工作才能确保将库分配到共享内存?

答案1

我相信,真正简短的答案是,Linux 编译器将代码分成几部分,至少其中之一只是纯代码,因此可以将内存映射到多个进程的地址空间。任何全局变量都会被映射,以便每个进程都有自己的副本。

您可以使用readelf、 或来查看这一点objdump,但readelf我认为可以提供更清晰的图片。

这是 的一段输出readelf -e /usr/lib/libc.so.6。那是 C 库,可能映射到几乎每个进程。输出的相关部分readelf(尽管所有这些都很有趣)是程序头:

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  PHDR           0x000034 0x00000034 0x00000034 0x00140 0x00140 R E 0x4
  INTERP         0x164668 0x00164668 0x00164668 0x00017 0x00017 R   0x4
      [Requesting program interpreter: /usr/lib/ld-linux.so.2]
  LOAD           0x000000 0x00000000 0x00000000 0x1adfc4 0x1adfc4 R E 0x1000
  LOAD           0x1ae220 0x001af220 0x001af220 0x02c94 0x057c4 RW  0x1000
  DYNAMIC        0x1afd90 0x001b0d90 0x001b0d90 0x000f8 0x000f8 RW  0x4
  NOTE           0x000174 0x00000174 0x00000174 0x00044 0x00044 R   0x4
  TLS            0x1ae220 0x001af220 0x001af220 0x00008 0x00048 R   0x4
  GNU_EH_FRAME   0x164680 0x00164680 0x00164680 0x06124 0x06124 R   0x4
  GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x10
  GNU_RELRO      0x1ae220 0x001af220 0x001af220 0x01de0 0x01de0 R   0x1

两条 LOAD 行是文件中唯一直接映射到内存的部分。第一个 LOAD 头将一块映射/usr/lib/libc.so.6到具有 R 和 E 权限的内存中:读取和执行。这就是代码。硬件功能阻止程序写入该内存,因此所有程序都可以共享真实物理内存的相同页面。内核可以设置硬件将相同的物理内存映射到所有进程。

第二个 LOAD 标头标记为 RW - 读和写。这是 C 库使用的全局变量部分。每个进程在物理内存中都有自己的副本,映射到该进程的地址空间,并设置硬件权限以允许读取和写入。该部分不共享。

您可以使用文件系统在正在运行的进程中查看这些内存映射/proc。一个很好的命令来说明:cat /proc/self/maps.它列出了进程拥有的所有内存映射cat,以及内核从哪些文件中获取它们。

至于你需要做多少事情来确保你的函数被分配到映射到不同进程的内存,这几乎完全取决于你给编译器的标志。用于“.so”共享库的代码是“位置无关”编译的。位置无关代码执行的操作包括引用相对于当前指令具有偏移量的变量的内存位置,以及跳转或分支到相对于当前指令的位置,而不是从绝对地址加载或写入绝对地址,然后跳转到绝对地址。这意味着“RE”LOAD 块/usr/lib/libc.so和“RW”块只需加载到每个进程中相距相同距离的地址处。在您的示例代码中,static除了引用它的代码之外,该变量始终至少是页面大小的倍数,并且由于 LOAD ELF 标头的方式,它始终会在进程的地址空间中加载该距离给予。

关于“共享内存”一词,请注意:有一个用户级共享内存系统,与“System V 进程间通信系统”相关。这是多个进程非常明确地共享一块内存的一种方式。设置和正确设置是相当复杂和晦涩的。我们在这里讨论的共享内存对于任何用户进程来说或多或少都是完全不可见的。如果您的示例代码作为在多个进程之间共享的位置无关代码运行,或者它是唯一的副本,则您的示例代码不会知道其中的区别。

相关内容