The process for implementing an embedded application is well understood:
- Code is written (in C/C++/assembly or some other language) and placed in a number of files (modules).
- Each module is compiled/assembled to create a relocatable object file. This file contains the machine instructions for the target processor, but the address information is not yet committed.
- All the modules are integrated together using a linker (sometimes called a linker/locator). This process resolves all the memory references and results in an absolute object file: an image of the final system memory.
This view is somewhat simplistic, as there are a number of other nuances:
- Incremental linking may be used to join a number or relocatables together to form a single relocatable.
- The linking/locating process may be adjusted so that the code is stored in one place, but with addresses resolved for execution at another address, having been copied there by a boot loader.
- It is possible to link relocatable object files together is a special way to result in an object module library.
The word “library” is used and abused in a variety of contexts. Its meaning here is well defined. A library file may be presented to the linker, along with relocatable object files. Its function is to resolve symbols (usually function names) that have not been provided by the relocatable object files. For example, if your code in one module calls a function MyFun() and there is a definition of this function in another module, all is well. If the linker does not find this function, an error will result. However, if a library (or a number of libraries) is included, the linker will look there last of all to resolve the symbol. If a library contains a MyFun() function, the code is extracted and used in the final absolute file.
The point of a library may not be obvious. You could just link all the relocatables together in a simple way – why bother with a library? The idea is that the library contains a large number of functions, but the linker only extracts the ones required for the current application. The unused ones are never extracted from the library, so they do not use up (i.e. waste) target memory.
The primary purpose of a library is as a repository for a quantity of reusable code. This can be a very good way to work in a project with a large development team, where sharing code is very beneficial and “reinventing the wheel” undesirable, but common. A project library should be carefully planned and documented. The functions must be designed with reuse in mind: no use of global data, clean, well-defined interfaces, reentrancy etc.
Development tool vendors typically provide libraries, which are standardized for C/C++. These contain two types of functions. The obvious ones are the explicit functions that developers call when they need them – like printf(). The other library functions are implicit; they are called by compiler-generated code and provide commonly needed functionality, which is conveniently shared.
Software IP vendors are also likely to provide their products as libraries. Real time operating systems (RTOSes) are commonly distributed in this way. This enables the RTOS to be straightforwardly scalable; only the required RTOS functionality is included in the application.
An issue with library distributions is their “granularity”; how small a piece of code can be extracted? Some libraries are constructed from large pieces. This means that a module within the library might contain all the service functions that appertain to a particular RTOS facility. So, for example, using one RTOS call to operate on a semaphore would result in all the semaphore-related service call functions being included in the application. A very granular library would work with smaller units. So, using a single service call would result in the inclusion of just its code and not that of related functions. There is a trade-off. A very granular library will extend the link time, but target memory will not be wasted on unused service call functions.
All embedded software developers should have an appreciation of the way libraries work and the benefits they provide. Reusability of code is key to productive, efficient code development and ensuring maintainability.