1. Overview
Imagine a programmer who wants to design and code three different programs. He figures that the three programs share several common functions that can be reused by the programs. To make his life easier, he needs to collect those functions into a single entity, called a library.
In general, libraries are collections of data and functions written to be reused by other programmers or programs. On Linux, archive libraries end with the .a extension, and shared object libraries end with the .so extension.
In this article, we’ll take a look at how programs run under Linux and the purposes served by archive and shared objects libraries. Apart from that, we’ll also see a couple of examples of how we can build libraries for our programs. We will use the GNU C Compiler and the GNU ar utility.
2. How Programs Run Under Linux
Most Linux users have probably come across the /lib and /usr/lib directories. These are the directories where we store all the common functions used by the programs installed on our Linux machine. As a convention, a library name starts with “lib”, and the extension determines the type of the library:
- .a — stands for “archive”
- .so — stands for “shared object”
A program might have dependencies on more than one shared object. So, manually installing the shared objects can be cumbersome. To help with this, we need a package manager — a tool that calculates and determines the dependencies before installing the actual program.
When we run the program, it will look for the required dependencies in the /usr/lib and /share directories. However, the program will fail to start if the required dependencies are missing.
3. Program Libraries
Program libraries consist of related data and subroutines needed to carry out a common task. For instance, multiple programs might need to use complex numbers to calculate a result. A good example of a library that provides this functionality is the GNU C Library, which is linked into our programs by the gcc compiler during the linking stage.
3.1. Static Libraries
The traditional static library is a type of library that is linked with the program at compile time. Therefore, the object code of the library is included in the object code of the program executable. Eventually, if we have multiple programs linked to a static library, then each resulting program binary will include the referenced library’s object code. Consequently, this will lead to larger executable files.
Static libraries usually end with the .a extension — for example, glibc.a.
3.2. Shared Libraries
To address the issue of larger executable binaries, programmers use shared libraries instead. Shared libraries are also called dynamic libraries. These shared libraries are linked in at runtime by the dynamic linker available on the operating system.
Shared libraries usually end with the .so extension — an example is libboost.5.6.so. Unlike static libraries, a program referencing a shared library will not include the shared library object code in its resulting executable. Thus, we get smaller executable files.
Similarly, when we have multiple programs that reference the same shared library, the library will have a single copy that can be reused by the programs simultaneously. This is safely managed by the operating system, and it’s a foundation for modern computing. Let’s take a look at the /usr/lib/xorg directory:
$ ls -halF /usr/lib/xorg/modules
-rwxr-xr-x 1 root root 95K Apr 13 20:12 libexa.so*
-rwxr-xr-x 1 root root 23K Apr 13 20:12 libfbdevhw.so*
-rwxr-xr-x 1 root root 111K Apr 13 20:12 libfb.so*
-rwxr-xr-x 1 root root 213K Apr 13 20:12 libglamoregl.so*
-rwxr-xr-x 1 root root 143K Apr 13 20:12 libint10.so*
-rwxr-xr-x 1 root root 15K Apr 13 20:12 libshadowfb.so*
-rwxr-xr-x 1 root root 39K Apr 13 20:12 libshadow.so*
As we can see, Xorg depends on the listed shared libraries. These libraries, in turn, can also be used by other programs such as dwm.
4. Building .a or Static Library
For this illustration to work, we will need gcc to compile our source code. Suppose we have a bunch of C programming source files — we’ll need to compile the source files into object code. We can achieve that by issuing the gcc command with the -Wall option:
$ gcc -Wall -c *.c
We need to make sure that we’re at the root of the source directory. The -Wall option tells the compiler to print all the warnings it encounters. The asterisk in the *.c argument tells the compiler to compile all the *.c source files. Upon issuing the command above, the compiler will compile the *.c files into corresponding object files. Thus, we get all the required *.o files that we will need to build a library from.
Next up, we’re going to create a library file from the object code using the ar utility, which is included with GNU Binutils:
$ ar -cvq libfile.a *.o
The -c option suppresses the errors, the -v option is provided for verbose output, and the -q option is for quickly appending the specified files to the archive. If the archive does not exist, then a new archive is created instead. When the ar command executes successfully, we should get a static library file libfile.a. Let’s see what is included inside the libfile.a file:
$ ar -t libfile.a
This will list all the object files that are archived into the libfile.a. Later on, when we want to include the library in our programs, we can simply reference the library in the compilation command:
$ gcc -o MyProgram *.o -L path/to/libdir -lfile.a
The -L option is used to specify the library directory. Mind the library filename in the command. We substituted the lib prefix with -l. The MyProgram executable will include the object code of the libfile.a library.
5. Building .so or Shared Library
A shared library can easily be built using gcc. Like before, we first need to compile our source files to their corresponding object files:
$ gcc -Wall -c *.c
Once the code is compiled, we will need to feed the object code files to our next command to create a shared library:
$ gcc -shared -o libfile.so *.o
The -shared option specifies to the compiler that we are building a shared library. Upon successful compilation, we will have built a shared library that we can install on our systems to be usable by all programs at runtime.
6. Conclusion
In this tutorial, we looked at how most programs run on Linux machines and how the programs are dependent on libraries. Then we looked at static and shared libraries and the purposes they serve. Finally, we looked at an example of how we can build static and shared libraries for our own programs using the gcc compiler and its helper utility, ar.