在嵌入式开发中,实现嵌入式应用的过程很容易理解:
l 代码是用C/C++/汇编语言或其他语言编写的,并放在许多文件(模块)中。
l 每个模块都被编译/汇编成一个可重定位的目标文件。此文件包含目标处理器的机器指令,但尚未提交地址信息。
l 使用链接器(有时称为链接器/定位器)将所有模块集成在一起。此过程解析所有内存引用,并生成一个绝对对象文件:最终系统内存的映像。
这种观点有些过于简单,因为还有许多其他细微差别:
l 增量链接可用于将一个或多个可重定位变量连接在一起,以形成单个可重定位。
l 链接/定位过程可以被调整,使得代码被存储在一个地方,但是地址被解析为在另一个地址执行,并且已经被引导加载器复制到那里。
l 可以将可重新定位的对象文件链接在一起,这是一种生成对象模块库的特殊方式。
“库”一词在嵌入式开发很多情况下被使用和滥用。它在这里的含义很明确。库文件可以与可重定位对象文件一起呈现给链接器。它的功能是解析可重定位对象文件未提供的符号(通常是函数名)。例如,如果一个模块中的代码调用一个函数MyFun(),而另一个模块对此函数有定义,则一切正常。如果链接器找不到此函数,将导致错误。但是,如果包含一个库(或多个库),则链接器将最后查找该库以解析符号。如果库包含MyFun()函数,则提取代码并在最终的绝对文件中使用。
库的意义可能并不明显。你可以用一种简单的方式将所有的可重定位链接在一起——为什么要用库呢?其思想是库包含大量函数,但链接器仅提取当前应用程序所需的函数。未使用的内存从未从库中提取,因此它们不会耗尽(即浪费)目标内存。
库的主要目的是作为大量可重用代码的存储库。在大型开发团队的项目中,这可能是一种非常好的工作方式,在这种情况下,共享代码是非常有益的,而“重新发明轮子”是不可取的,但却是常见的。应该仔细规划和记录项目库。函数的设计必须考虑重用:不使用全局数据、干净、定义良好的接口、可重入性等。
开发工具供应商通常会提供针对C/C++而标准化的库。这些包含两种类型的函数。显而易见的是嵌入式开发人员在需要时调用的显式函数,比如printf()。其他库函数是隐式的,它们由编译器生成的代码调用,并提供通常需要的功能,这些功能可以方便地共享。
软件IP供应商也可能以库的形式提供他们的产品。实时操作系统(RTOS)通常以这种方式发布。这使得RTOS可以直接扩展;应用程序中仅包含必需的RTOS功能。
库发行版的一个问题是它们的“粒度”;可以提取多小的一段代码?有些库是由大块组成的。这意味着库中的一个模块可能包含属于某个特定RTOS设备的所有服务功能。因此,例如,使用一个RTOS调用来操作一个信号量会导致所有与信号量相关的服务调用函数都包含在应用程序中。一个非常细粒度的库可以处理较小的单元。因此,使用单个服务调用将导致只包含其代码,而不包含相关函数的代码。这里有一个权衡。一个非常细粒度的库会延长链接时间,但是目标内存不会浪费在未使用的服务调用函数上。
所有嵌入式开发人员都应该了解库的工作方式和它们提供的好处。代码的可重用性是高效代码开发和确保可维护性的关键。