问题描述
我正在读一本教科书,上面写着:
延迟绑定的动机是,典型的应用程序将仅调用共享库(如libc.so)导出的成百上千个函数中的少数几个。通过将函数地址的分辨率推迟到实际调用之前,动态链接程序可以避免在加载时成百上千的不必要的重定位。
我有点主意,但还是很困惑。假设一个程序仅调用共享库的10个函数,而共享库中有100个功能。如果没有延迟绑定,则链接程序将只需要对程序使用的功能进行10次重定位,因此,使用延迟绑定,动态链接程序如何避免成百上千的不必要的重定位?就像您尝试解决实际上不存在的问题一样?
根据我对延迟绑定的了解,加载时不需要重定位,因此您确实节省了进行10次重定位的时间,这是我唯一能看到的好处,我的理解正确吗?但是作者似乎表明没有延迟绑定,链接器需要进行100次重定位?
解决方法
教科书有点误导。符号绑定曾经很慢,而懒惰绑定的要点是,有了它,程序可以更快地完成 something 操作。基本上,启动性能会降低。即使在今天,如果一个小程序依赖于一个大型库,而大型库又依赖于另一个大型库,那么这可能会引起注意。如果没有延迟绑定,则第一个库需要在流程开始时完全重定位,而对于延迟绑定,仅实际使用的部分会被重定位。但是正如您所指出的,在没有这两个库的情况下,延迟绑定没有太大的区别。
延迟绑定在语义上也有差异,这可能是当今更重要的方面。使用它,您可以dlopen
另一个共享对象,它提供了当前共享对象的符号依赖性。对于非延迟绑定,符号在初始重定位期间不存在,从而导致加载失败。这样的方法可能真的很难摆脱延迟绑定,例如为了增强安全性。
文本在编写时令人困惑,但是它所指的是引用但从未调用过的函数,而不是从未引用过的函数。无论是否使用了惰性绑定,对于未引用的函数都不需要查找。
如果您的程序在库中引用了10个函数,但调用了0个函数,则延迟绑定与立即绑定的区别是0 vs 10查找。如果您的程序在库中引用了100个函数,但仅调用了10个,则相差10对100。延迟绑定应该为您节省时间的预期情况是,由于一个共享库依赖另一个而导致引用大量函数时。例如,假设您的程序使用依赖于库B的库A,并且库A引用了库B中的所有10000个函数,但是您的程序仅使用库A中的一小部分功能,而该功能仅调用库B中的一个函数。现在,只需要完成一次,而不是10000次查找。这听起来像是一大好处。
但是,在另一方面,延迟绑定非常容易出错(由于延迟解析器的调用机制,它如何与调用ABI合约进行交互(关于寄存器使用情况的机制)以及第一次调用发生的可能性一个非常尴尬的上下文(例如来自信号处理程序的上下文)并在调用函数时一次执行一次查找,而不是一次执行一次查找,这对程序流,缓存利用率等造成了更大的破坏,并且将花费更多的总时间如果实际上调用了库中的所有函数。当然,这是一次性(每个流程实例)成本。