compilation - 将 PLT 和 GOT 表用于共享库的主要原因是什么?
问题描述
我正在阅读 Ian Lance Taylor 关于链接器的文章:http: //inai.de/documents/Linkers.pdf
在第 9 页讨论共享对象时,他提到由于共享库可以加载到无法预测的虚拟地址的进程中,因此一旦知道地址,动态链接器将需要处理大量重定位。这会减慢加载速度。为了避免动态链接器完成大量重定位,程序链接器将函数引用更改为对 PLT 表的 PC 相关调用,并将全局/静态变量引用转换为对 GOT 表的引用。然后动态链接器只需要在加载时重定位 PLT/GOT 中的条目,而不是处理整个二进制文件中的重定位。
然而,这种对加载时间优化的关注让我感到困惑,因为这里似乎有一个更明显的问题,加速加载是无关紧要的。共享对象的全部意义在于,加载到物理内存中的单个共享对象现在可以映射到每个需要它的进程的虚拟地址空间。这可以通过更改一些页表来快速完成,并避免从磁盘加载库的新副本。
因此,如果动态链接器在共享库的主体中进行了任何重定位,这些更改将出现在也映射了该共享库的每个其他进程中,并且如果它出现在不同的虚拟地址上,它们将破坏该库。
正是出于这个原因,我们才有了 GOT 和 PLT。程序链接器将所有引用修改为对 GOT 和 PLT 的与位置无关的引用。然后动态链接器为每个进程唯一地重新定位 GOT 和 PLT 中的条目。共享库的主要内容在各进程间共享,但GOT和PLT对各进程是唯一的,不共享。
这种对 PLT 和 GOT 的理解正确吗?我根据我的理解推断了这里的一些机制,但我没有看到它可以工作的任何其他方式。
解决方案
您似乎缺少或不理解写时复制(CoW) 页面的概念。
两个进程可以mmap
将磁盘上的同一个文件放入它们不同的虚拟地址中,并且操作系统可以为两个映射使用单个 RAM 物理页面(即,进程共享一个物理内存页面)。但是一旦一个进程更改了内存,就会为该进程创建一个副本,并且更改不会出现在另一个进程中(物理内存页面不再共享)。
因此,如果动态链接器在共享库的主体中进行了任何重定位,这些更改将出现在也映射了该共享库的每个其他进程中,
如果内存是 CoW,则不会。
正是因为这个原因,我们才有了 GOT 和 PLT
不,原因是优化(必须复制更少的页面)而不是您的(错误)理解所暗示的正确性。
推荐阅读
- php-7 - 语法错误,视图中出现意外的“,”,但视图文件中没有“,”
- c# - Json 到 C# 对象处理动态属性
- c# - 我应该如何将我的刷新令牌换成新的访问令牌?
- visual-studio-2019 - 上下文菜单的“编辑 csproj”在 Visual Studio 2019 中间歇性不起作用
- azure - 当源数据集是 Blob 存储时,我可以在 Azure 数据工厂中一次填充不同的 SQL 表吗?
- c# - “在网络聊天中测试”现在无法正常工作,但 2 天前工作正常
- python - 是否可以在 python 中加快将列表转换为数组的速度?
- sql - 将表的一行与第二个表的每一行进行比较
- apollo-server - GraphQL:具有命名空间的 Apollo Server 订阅
- regex - 是否有一个正则表达式可以多次匹配 2 个字符?