为什么对位置无关的代码函数调用使用惰性绑定
why use lazy binding for Position-Independent Code Function Calls
我正在阅读一本教科书,上面写着:
惰性绑定的动机是典型的应用程序只会调用共享库导出的成百上千个函数中的一小部分,例如 libc.so .通过将函数地址的解析推迟到实际调用时,动态链接器可以在加载时避免成百上千次不必要的重定位。
我有点明白了,但仍然很困惑。假设一个程序只调用共享库的 10 个函数,其中有 100 个函数。如果没有惰性绑定,链接器只需要为程序使用的函数做 10 次重定位,那么有了惰性绑定,动态链接器如何避免数百或数千(在这种情况下为 100 个函数)不必要的重定位?这就像你试图解决一个实际上不存在的问题?
据我所知,关于惰性绑定,加载时不需要重定位,因此您确实节省了执行 10 次重定位的时间,这是我能看到的唯一好处,我的理解是否正确?但是作者似乎表示没有惰性绑定,链接器需要做100次重定位?
教科书有点误导。符号绑定过去非常慢,而惰性绑定的要点在于,有了它,程序可以更快地到达他们做某事的点。基本上,启动性能会降低。即使在今天,如果一个小程序依赖于一个大型库,而这个大型库又依赖于另一个大型库,这一点即使在今天也会很明显。没有惰性绑定,第一个库需要在进程开始时完全重定位,而使用惰性绑定,只有实际使用的部分被重定位。但是正如您所指出的,在没有这两个库的情况下,延迟绑定并没有太大的区别。
惰性绑定也存在语义差异,这可能是当今更重要的方面。有了它,你可以 dlopen
另一个提供当前共享对象的符号依赖的共享对象。使用 non-lazy 绑定,符号在初始重定位期间不存在,导致加载失败。这种方法会使从惰性绑定中迁移出来变得非常困难,比如增加安全性强化。
文字写的很混乱,但它说的是被引用但从未被调用的函数,而不是从未被引用的函数。无论是否使用惰性绑定,对于未被引用的函数都不需要查找。
如果您的程序引用了库中的 10 个函数但调用了其中的 0 个,则延迟绑定与立即绑定的区别是 0 次与 10 次查找。如果您的程序引用了库中的 100 个函数但只调用了其中的 10 个,则差异是 10 次与 100 次查找。延迟绑定应该为您节省时间的预期情况是由于一个共享库依赖于另一个共享库而引用大量函数。例如,假设你的程序使用了库 A,它依赖于库 B,而库 A 引用了库 B 中的所有 10000 个函数,但你的程序只使用了库 A 中的一小部分功能,它只调用了库 B 中的一个函数。现在,不再是 10000 次查找,而是只完成一次。这听起来像是一个主要的好处。
然而,另一方面,惰性绑定是严重的error-prone(由于惰性解析器的调用机制,它如何与有关寄存器使用的调用 ABI 契约交互,以及第一次调用发生在一个非常尴尬的上下文中,比如来自信号处理程序)并且在调用函数时一次进行一个查找,而不是一次全部进行,这对程序流、缓存利用率等更具破坏性,并且需要如果实际调用了库中的所有函数,则总时间会明显增加。当然,这是 one-time(每个流程实例)成本。
我正在阅读一本教科书,上面写着:
惰性绑定的动机是典型的应用程序只会调用共享库导出的成百上千个函数中的一小部分,例如 libc.so .通过将函数地址的解析推迟到实际调用时,动态链接器可以在加载时避免成百上千次不必要的重定位。
我有点明白了,但仍然很困惑。假设一个程序只调用共享库的 10 个函数,其中有 100 个函数。如果没有惰性绑定,链接器只需要为程序使用的函数做 10 次重定位,那么有了惰性绑定,动态链接器如何避免数百或数千(在这种情况下为 100 个函数)不必要的重定位?这就像你试图解决一个实际上不存在的问题?
据我所知,关于惰性绑定,加载时不需要重定位,因此您确实节省了执行 10 次重定位的时间,这是我能看到的唯一好处,我的理解是否正确?但是作者似乎表示没有惰性绑定,链接器需要做100次重定位?
教科书有点误导。符号绑定过去非常慢,而惰性绑定的要点在于,有了它,程序可以更快地到达他们做某事的点。基本上,启动性能会降低。即使在今天,如果一个小程序依赖于一个大型库,而这个大型库又依赖于另一个大型库,这一点即使在今天也会很明显。没有惰性绑定,第一个库需要在进程开始时完全重定位,而使用惰性绑定,只有实际使用的部分被重定位。但是正如您所指出的,在没有这两个库的情况下,延迟绑定并没有太大的区别。
惰性绑定也存在语义差异,这可能是当今更重要的方面。有了它,你可以 dlopen
另一个提供当前共享对象的符号依赖的共享对象。使用 non-lazy 绑定,符号在初始重定位期间不存在,导致加载失败。这种方法会使从惰性绑定中迁移出来变得非常困难,比如增加安全性强化。
文字写的很混乱,但它说的是被引用但从未被调用的函数,而不是从未被引用的函数。无论是否使用惰性绑定,对于未被引用的函数都不需要查找。
如果您的程序引用了库中的 10 个函数但调用了其中的 0 个,则延迟绑定与立即绑定的区别是 0 次与 10 次查找。如果您的程序引用了库中的 100 个函数但只调用了其中的 10 个,则差异是 10 次与 100 次查找。延迟绑定应该为您节省时间的预期情况是由于一个共享库依赖于另一个共享库而引用大量函数。例如,假设你的程序使用了库 A,它依赖于库 B,而库 A 引用了库 B 中的所有 10000 个函数,但你的程序只使用了库 A 中的一小部分功能,它只调用了库 B 中的一个函数。现在,不再是 10000 次查找,而是只完成一次。这听起来像是一个主要的好处。
然而,另一方面,惰性绑定是严重的error-prone(由于惰性解析器的调用机制,它如何与有关寄存器使用的调用 ABI 契约交互,以及第一次调用发生在一个非常尴尬的上下文中,比如来自信号处理程序)并且在调用函数时一次进行一个查找,而不是一次全部进行,这对程序流、缓存利用率等更具破坏性,并且需要如果实际调用了库中的所有函数,则总时间会明显增加。当然,这是 one-time(每个流程实例)成本。