4.3 C Library Alternatives

Given the constraints and limitations of embedded systems, the size of the standard GNU C library makes it an unlikely candidate for use on our target. Instead, we need to look for a C library that will have sufficient functionality while being relatively small.

Over time, a number of libraries have been implemented with these priorities in mind. In the following, we will discuss the two most important C library alternatives, uClibc and diet libc. For each library, I will provide background information, instructions on how to build the library for your target, and instructions on how to build your applications using the library.

4.3.1 uClibc

The uClibc library originates from the uClinux project, which provides a Linux that runs on MMU-less processors. The library, however, has since become a project of its own and supports a number of processors that may or may not have an MMU or an FPU. At the time of this writing, uClibc supports all the processor architectures discussed in depth in Chapter 3. uClibc can be used as a shared library on all these architectures, because it includes a native shared library loader for each architecture. If a shared library loader were not implemented in uClibc for a certain architecture, glibc's shared library loader would have to be used instead for uClibc to be used as a shared library.

Although it does not rely on the GNU C library, uClibc provides most of the same functionality. It is, of course, not as complete as the GNU library and does not attempt to comply with all the standards with which the GNU library complies. Functions and function features that are seldom used, for instance, are omitted from uClibc. Nevertheless, most applications that can be compiled against the GNU C library will also compile and run using uClibc. To this end, uClibc developers focus on maintaining compatibility with C89, C99, and SUSv3.[9] They regularly use extensive test suites to ensure that uClibc conforms to these standards.

[9] Single UNIX Specification Version 3.

uClibc is available for download as a tar-gzipped or tar-bzip2'd archive or by using CVS from the project's web site at http://uclibc.org/. It is distributed under the terms of the LGPL. An FAQ is available on the project's web site, and you can subscribe to the uClibc mailing list or browse the mailing list archive if you need help. In the following description, we will be using Version 0.9.16 of uClibc, but the explanation should apply to subsequent versions as well. Versions earlier than 0.9.16 depended on a different configuration system and are not covered by the following discussion.

4.3.1.1 Library setup

The first step in the setup is to download uClibc and extract it in our ${PRJROOT}/build-tools directory. In contrast to the GNU toolchain components, we will be using the package's own directory for the build instead of a separate directory. This is mainly because uClibc does not support building in a directory other than its own. The rest of the build process, however, is similar to that of the other tools, with the main steps being configuration, building, and installation.

After extracting the package, we move into the uClibc directory for the setup:

$ cd ${PRJROOT}/build-tools/uClibc-0.9.16

For its configuration, uClibc relies on a file named .config that should be located in the package's root directory. To facilitate configuration, uClibc includes a configuration system that automatically generates a .config file based on the settings we choose, much like the kernel configuration utility we will discuss in Chapter 5.[10]

[10] The uClibc configuration system is actually based on Roman Zippel's kernel configuration system, which was included in the 2.5 development series.

The configuration system can be operated in various ways, as can be seen by looking at the INSTALL file included in the package's directory. The simplest way to configure uClibc is to use the curses-based terminal configuration menu:

$ make CROSS=i386-linux- menuconfig

This command displays a menu that can be navigated using the arrow, Enter, and Esc keys. The main menu includes a set of submenus, which allow us to configure different aspects of uClibc. At the main menu level, the configuration system enables us to load and save configuration files. If we press Esc at this level, we are prompted to choose between saving the configuration to the .config file or discarding it.

In the command above, we set CROSS to i386-linux-, since our cross-platform tools are prepended by this string, as I explained earlier. We could also edit the Rules.mak file and set CROSS to ${TARGET}- instead of specifying CROSS= for each uClibc Makefile target.

The main configuration menu includes the following submenus:

  • Target Architecture Features and Options

  • General Library Settings

  • Networking Support

  • String and Stdio Support

  • Library Installation Options

  • uClibc hacking options

Through its submenus, the configuration system allows us to configure many options. Fortunately, we can obtain information regarding each option using the "?" key. When this key is pressed, the configuration system displays a paragraph explaining how this option is used and provides its default values. There are two types of options: paths for tools and directories needed for building, installing, and operating uClibc, and options for selecting the functionality to be included in uClibc.

We begin by setting the tool and directory paths in the "Target Architecture Features and Options" and "Library Installation Options" submenus. Table 4-7 lists the values we must set in those submenus to have uClibc compile and install in accordance with our workspace. For each option, the name of the variable used internally by uClibc's configuration system is given in parentheses. Knowing this name is important for understanding the content of the .config file, for example.

Table 4-7. uClibc tool and directory path settings

Option

Setting

Linux kernel header location (KERNEL_SOURCE)

${PRJROOT}/kernel/linux-2.4.18

Shared library loader path (SHARED_LIB_LOADER_PATH)

/lib

uClibc development environment directory (DEVEL_PREFIX)

${PRJROOT}/tools/uclibc

uClibc development environment system directory (SYSTEM_DEVEL_PREFIX)

$(DEVEL_PREFIX)

uClibc development environment tool directory (DEVEL_TOOL_PREFIX)

$(DEVEL_PREFIX)/usr

Notice that we use ${PRJROOT}/tools instead of ${PREFIX}, although the former is the value we gave to the PREFIX environment variable in our script. This is because uClibc's use of the PREFIX variable in its build Makefiles and related scripts differs from our use. Mainly, it uses this variable to install everything in an alternate location, whereas we use it to point to the main install location.

KERNEL_SOURCE should point to the sources of the kernel you will be using on your target. If you don't set this properly, your applications may not work at all, because uClibc doesn't attempt to provide binary compatibility across kernel versions.

SHARED_LIB_LOADER_PATH is the directory where shared libraries will be located on your target. All the binaries you link with uClibc will have this value hardcoded. If you later change the location of your shared libraries, you will need to rebuild uClibc. We have set the directory to /lib, since this is the traditional location of shared libraries.

DEVEL_PREFIX is the directory where uClibc will be installed. As with the other tools, we want it to be under ${PRJROOT}/tools. SYSTEM_DEVEL_PREFIX and DEVEL_TOOL_PREFIX are other installation variables that are used to control the installation of some of the uClibc binaries and are mostly useful for users who want to build RPM or dpkg packages. For our setup, we can set SYSTEM_DEVEL_PREFIX to the same value as DEVEL_PREFIX, and DEVEL_TOOL_PREFIX to $(DEVEL_PREFIX)/usr. This results in all uClibc binaries prepended with the target name, such as i386-uclibc-gcc, to be installed in ${PRJROOT}/tools/uclibc/bin, and all uClibc binaries not prepended with the target name, such as gcc, to be installed in ${PRJROOT}/tools/uclibc/usr/bin. As we shall see later, we only need to add ${PRJROOT}/tools/uclibc/bin to our path to use uClibc.

Let us now take a look at the options found in each configuration submenu. As I said earlier, you can use the "?" key to obtain more information about each option from the configuration system. Because some options depend on the settings of other options, some of the options listed below may not be displayed in your configuration. While most options are either enabled or disabled, some are string fields, such as the paths we discussed earlier, which must be filled.

The "Target Architecture Features and Options" submenu includes the following options:

  • Target Processor Type.

  • Target CPU has a memory management unit (MMU) (UCLIBC_HAS_MMU).

  • Enable floating (UCLIBC_HAS_FLOATS).

  • Target CPU has a floating point unit (FPU) (HAS_FPU).

  • Enable full C99 math library support (DO_C99_MATH).

  • Compiler Warnings (WARNINGS). This is a string field that allows you to set the compiler flags used for reporting warnings.

  • Linux kernel header location (KERNEL_SOURCE). This is the kernel path we discussed earlier.

The "General Library Settings" submenu includes the following options:

  • Generate Position Independent Code (PIC) (DOPIC).

  • Enable support for shared libraries (HAVE_SHARED).

  • Compile native shared library loader (BUILD_UCLIBC_LDSO).

  • Native shared library loader `ldd' support (LDSO_LDD_SUPPORT).

  • POSIX Threading Support (UCLIBC_HAS_THREADS).

  • Large File Support (UCLIBC_HAS_LFS).

  • Malloc Implementation. This is a submenu that allows us to choose between two malloc implementations, malloc and malloc-930716.

  • Shadow Password Support (HAS_SHADOW).

  • Regular Expression Support (UCLIBC_HAS_REGEX).

  • Supports only Unix 98 PTYs (UNIXPTY_ONLY).

  • Assume that /dev/pts is a devpts or devfs filesystem (ASSUME_DEVPTS).

The "Networking Support" submenu includes the following options:

  • IP Version 6 Support (UCLIBC_HAS_IPV6).

  • Remote Procedure Call (RPC) support (UCLIBC_HAS_RPC).

  • Full RPC support (UCLIBC_HAS_FULL_RPC).

The "String and Stdio support" submenu includes the following options:

  • Wide Character Support (UCLIBC_HAS_WCHAR).

  • Locale Support (UCLIBC_HAS_LOCALE).

  • Use the old vfprintf implementation (USE_OLD_VFPRINTF).

We already covered all the options in the "Library Installation Options" submenu earlier in this section. Here they are nevertheless for completeness:

  • Shared library loader path (SHARED_LIB_LOADER_PATH).

  • uClibc development environment directory (DEVEL_PREFIX).

  • uClibc development environment system directory (SYSTEM_DEVEL_PREFIX).

  • uClibc development environment tool directory (DEVEL_TOOL_PREFIX).

Though you should not normally need to enter the "uClibc hacking options" submenu, here are the options it includes:

  • Build uClibc with debugging symbols (DODEBUG).

  • Build uClibc with runtime assertion testing (DOASSERTS).

  • Build the shared library loader with debugging support (SUPPORT_LD_DEBUG).

  • Build the shared library loader with early debugging support (SUPPORT_LD_DEBUG_EARLY).

For our DAQ module, we left the options to their default values. For most targets, you should not need to change the options either. Remember that you can always revert to the defaults by removing the .config file from the uClibc's directory.

With uClibc now configured, we can compile it:

$ make CROSS=i386-linux-

The compilation takes approximately 10 minutes in our setup. As with the GNU toolchain, you may see warnings during the build that you can safely ignore.

With the build complete, we can install uClibc:

$ make CROSS=i386-linux- PREFIX="" install

Given the values we set above, this will install all the uClibc components in the ${PRJROOT}/tools/uclibc directory. If we had already installed uClibc, the installation procedure will fail while trying to copy files to the ${PRJROOT}/tools/uclibc directory. In such a case, we should erase the content of that directory before issuing the make install command.

4.3.1.2 Usage

We are now ready to link our applications with uClibc instead of the GNU C library. To facilitate this linking, a couple of utilities have been installed by uClibc in ${PRJROOT}/tools/uclibc/bin. Mainly, uClibc installed an alternate compiler and alternate linker, i386-uclibc-gcc and i386-uclibc-ld. Instead of using the i386-linux- prefix, the utilities and symbolic links installed by uClibc have the i386-uclibc- prefix. Actually, the uClibc compiler and linker are wrappers that end up calling the GNU utilities we built earlier while ensuring that your application is properly built and linked with uClibc.

The first step in using these utilities is to amend our path:

$ export PATH=${PREFIX}/uclibc/bin:${PATH}

You will also want to modify your development environment script to automate this path change. In the case of develdaq, here is the new line for the path:

export PATH=${PREFIX}/bin:${PREFIX}/uclibc/bin:${PATH}

Using the same Makefile as earlier, we can compile the control daemon as follows:

$ make CROSS_COMPILE=i386-uclibc-

Since uClibc is a shared library by default on the x86, this will result in a dynamically linked binary. We could still compile our application statically, however:

$ make CROSS_COMPILE=i386-uclibc- LDFLAGS="-static"

The same "Hello World!" program we used earlier is only 2 KB in size when linked with the shared uClibc and 18 KB when linked statically with it. This is a big difference with the figures I gave above for the same program when it was linked with glibc.

4.3.2 Diet libc

The diet libc project was started and is still maintained by Felix von Leitner with aims similar to uClibc. In contrast with uClibc, however, diet libc did not grow from previous work on libraries but was written from scratch with an emphasis on minimizing size and optimizing performance. Hence, diet libc compares quite favorably to glibc in terms of footprint and in terms of code speed. In comparison to uClibc, though, I have not noticed any substantial difference.

Diet libc does not support all the processor architectures discussed in Chapter 3. It supports the ARM, the MIPS, the x86, and the PPC. Also, the authors of diet libc favor static linking over dynamic linking. So, although diet libc can be used as a shared library on some platforms, it is mostly intended to be used as a static library.

One of the most important issues to keep in mind while evaluating diet libc is its licensing. In contrast to most other libraries, including uClibc, which are usually licensed under the LGPL, diet libc is licensed under the terms of the GPL. As I explained in Chapter 1, this means that by linking your code to diet libc, the resulting binary becomes a derived work and you can distribute it only under the terms of the GPL. A commercial license is available from the package's main author if you wish to distribute non-GPL code linked with diet libc.[11] If, however, you would prefer not to have to deal with such licensing issues, you may want to use uClibc instead.

[11] It is not clear whether this license covers the contributions made to diet libc by developers other than the main author.

Diet libc is available for download both as a tar-bzip2'd archive or using CVS from the project's web site at http://www.fefe.de/dietlibc/.[12] The package comes with an FAQ and installation instructions. In the following, we will be using Version 0.21 of diet libc, but my explanations should also apply to previous and subsequent versions.

[12] Notice the final "/". If you omit this slash, the web server will be unable to locate the web page.

4.3.2.1 Library setup

As with uClibc, the first step to setting up diet libc is to download it into our ${PRJROOT}/build-tools directory. Here too, we will build the library within the package's source directory and not in another directory as was the case for the GNU toolchain. Also, there is no configuration required for diet libc. Instead, we can proceed with the build stage immediately.

Once the package is extracted, we move into the diet libc directory for the setup:

$ cd ${PRJROOT}/build-tools/dietlibc-0.21

Before building the package for our target, we will build it for our host. This is necessary to create the diet utility, which is required to build diet libc for the target and later to build applications against diet libc:

$ make

In our setup, this creates a bin-ppc directory containing a PPC diet libc. We can now compile diet libc for our target:

$ make ARCH=i386 CROSS=i386-linux-

`You will see even more warnings than with the other packages, but you can ignore them. Here, we must tell the Makefile both the architecture for which diet libc is built and the prefix of the cross-platform development tools.

With the package now built, we can install it:

$ make ARCH=i386 DESTDIR=${PREFIX}/dietlibc prefix="" install

This installs diet libc components in ${PREFIX}/dietlibc. Again, as when building the package for our target, we provide the Makefile with the architecture. We also specify the install destination using the DESTDIR variable and reset the Makefile's internal prefix variable, which is different from the capital PREFIX environment variable.

Diet libc has now been installed in the proper directory. There is, however, one correction we need to make to diet libc's installation. By installing the x86 version of diet libc, we installed the x86 version of the diet utility in ${PREFIX}/dietlibc/bin. Since we intend to compile our applications on the host, we need to overwrite this with the native diet utility we built earlier:

$ cp bin-ppc/diet ${PREFIX}/dietlibc/bin
4.3.2.2 Usage

As with uClibc, using diet libc involves modifying the path and using the wrapper provided by diet libc to link our applications. In contrast to uClibc, however, instead of substituting the cross-development tools with tools specific to the library, we only need to prepend the calls we make to the tools with the diet libc wrapper.

First, we must change our path to include the directory containing the diet libc binary:

$ export PATH=${PREFIX}/dietlibc/bin:${PATH}

Again, you will also want to change your development environment script. For example, the path line in our develdaq script becomes:

export PATH=${PREFIX}/bin:${PREFIX}/dietlibc/bin:${PATH}

Notice that I assume that you won't be using both uClibc and diet libc at the same time. Hence, the path line has only diet libc added to it. If you would like to have both diet libc and uClibc on your system during development, you need to add both paths.

To compile the control daemon with diet libc, we use the following command line:

$ make CROSS_COMPILE="diet i386-linux-"

Since diet libc is mainly a static library, this will result in a statically linked binary by default and you don't need to add LDFLAGS="-static" to the command line. Using the same "Hello World!" program as earlier, I obtain a 24 KB binary when linked with diet libc.