Overview of Dynamic Libraries

决定app性能的两个重要因素是他们的启动时间和他们的内存跟踪。当程序运行时减少一个app的执行的文件的大小和最少化它内存的使用率会使app更快地启动并且使用更少的内存。使用动态库来代替静态库回减少一个app执行文件的大小。他们同样也能让app去在加载库被需要时才加载库的特定功能,而不是在他们启动的时候就被加载。这个特性能够减少启动时间和让内存更有效地利用。
这篇文章介绍了动态库和展示如何使用动态库去减少文件大小和(减少)最初使用它们的内存足迹。这篇文章同样提供了apps在运行时用作去与动态库一起工作的动态加载器兼容性函数的概况。

What Are Dynamic Libraries?

大多数的app的功能是被实现在libraries的执行文件中。当一个app通过静态连接器与一个library链接时,app所使用到的代码会被复制到生成的执行文件中。一个静态链接器收集了编译了的源代码,即object code(对象文件,在能构建的Mach-O文件中能看到)。然后library中的代码会被编写进一个执行文件中,这个执行文件在运行期被完全地加载到内存中。这类会变成app执行文件的library被称之为static library。static library是对象文件的集合。

当一个app被启动的时候,这个app的代码(包含了所链接的static library的代码)会被加载到app的地址空间中。链接大量的static library到一个app会产生大量的app执行文件。Figure 1展示了使用了static library实现的函数在app的内存用量。有大量执行文件的应用程序会启动很慢的启动时间并且有大量的内存足迹。同样,当一个静态库升级之后,它的客户端应用程序不会得益于静态库所做的提升。为了获得那些优化的性能,app的开发者必须链接app的对象文件的新版本库。并且app的用户还必须用新版本的静态库的复制件代替之前的库。因此,保持一个app中static library的最新功能状态需要开发者和用户的破坏性工作。

一个更好的方法是当实际需要的时候app才去加载代码到它的地址空间中,无论是在加载时还时运行时。这类的能提供这方面便利的库称之为动态库。动态库是没有被静态地链接到客户端的app;它们不会变成执行文件的一部分。反之,动态库能够在一个app启动或者运行时才被加载。
Figure 2展示了一些功能用了动态库实现会减少使用静态库时app运行的内存空间。

使用动态库,程序能自动得益于库所作的优化,因为他们链接到库时是动态的。因此,客户端app的功能能够被优化或者扩展而不需要app的开发者去重编译apps。用于OS X的App得益于这个特征因为所有的系统库都是动态库,这是为什么使用了Carbon或者Cocoa 技术的apps能从OS X的升级中得益。
另一个动态库提供的优点是他们能够在加载时被初始化并且能够在客户端app正常终结时执行清除任务。Static libraries没有这种特性,详情查看Module Initializers and Finalizers

当开发动态库时开发者必须清楚的一个问题是更新的时候必须维持一个客户端app兼容性。因为library能够被更新,且不被客户端的开发者知道,这个app必须能在不改变他的代码时继续使用这个新版本的library。即library的API不需要改变。然而,总会有需要API改变的时候,在这种情况,之前library的版本必须保留在用户的电脑以让客户端app合适地运行。Dynamic Library Design Guidelines探讨了当一个动态库更新时管理客户端应用程序兼容性的主题。

How Dynamic Libraries Are Used

当一个app启动时,OS X内核加载了app的代码和数据到一个新进程的内存地址空间中。内核同时会加载动态加载器到进程并且控制它。动态加载器之后会加载app依赖的库。这些是app会链接的dynamic libraries。静态链接器会在app link阶段记录每一个依赖库的文件名。这个文件名即是动态库的安装名。动态加载器使用了app的依赖库的安装名去在文件系统中定位他们。如果动态加载器在启动时没有找到所有的app的依赖库或者任意的库没有与app相适配,启动的过程会被终止。对于更多关于依赖库的兼容性的内容,查看Managing Client Compatibility With Dependent Libraries。当他们用gcc -install_name选项去编译它时,动态库的开发者能够给一个库设置不同的安装名字。详情用man gcc命令去查看。

在app请求的时候,动态加载器(除了在启动时自动地加载动态库)会在运行时加载动态库。因此,当一个app启动时不需要那些动态库,开发者能够选择不去用动态库链接app的对象文件,并且,只在部分需要它的app中加载动态库。通过这种方式使用动态库提高启动进程效率。这种在运行时加载的动态库被称之为dynamically loaded libraries。为了在运行时加载库,apps在他们正在运行的平台上能够使用与动态加载器交互的函数。(注意,客户端和动态库的architecture必须是一样的,不然动态加载器不会加载这个库)。

不同的平台会不同地实现他们的动态加载器。他们也必须有自定义的动态代码--加载接口会让代码难以跨平台。为了促进移植一个app从Unix到Linux,如Jorge Acereda和 Peter O’Gorman开发的动态加载器兼容性函数。他们提供给开发者一个标准、可移植的方式去在他们的ap中使用动态库。
DLC 功能被定义在/usr/include/dlfcn.h。他们有五个:

  1. dlopen 打开一个动态库。一个app在使用任何一个库的exported symbols前调用这个函数。如果动态库还没有被当前线程打开,这个库会被加载到这个进程的地址空间。这个函数返回一个句柄用来给被打开的库去调用dlsym和dlclose函数。这个句柄被称为dynamic library handle。这个函数维护了当前线程用dlopen去打开一个特定的动态库的引用计数。
  2. dlsym 返回被动态地加载了的库输出的symbol的地址。在通过dlopen调用获得一个library的句柄后,一个app会调用这个函数。dlsym函数被作为dlopen返回句柄的参数或者一个意味着symbol search code 和symbol name的常量。
  3. dladdr 返回地址提供的信息。如果地址与在app地址空间中的一个动态加载库的地址相匹配,这个函数会返回一个地址的信息。这个消息在一个为D1_info结构体中返回,这个结构体他所了动态库的pathname,这个库的基本地址,然后这个地址和最近这个地址的symbol提供的值,如果在提供的地址中没有找到动态库,这个函数不返回任何信息。
  4. dlclose 结束一个动态地加载的库。这个函数作为dlopen返回句柄的参数。当句柄的引用计数到达0的时候,这个库会在当前地址空间中卸载。
  5. dlerror 返回一个描述错误条件的字符串,这个错误是最后一次调用dlopen,dlsys或者dlclose函数产生的。