Getting started with Embedded Linux: Part Six

February 14, 2014

Eager-February 14, 2014

Author's note: This is Part 6 of my series on how to get started using Embedded Linux. The first five parts you can find on my Open Mike blog. Our goal is not to turn you into an overnight expert, but rather to give someone with experience in embedded systems a road map to extending that experience to using Linux.  

In our last segment, we gave an overview of how the Linux kernel source is organized, how to configure the kernel, and how to build a new kernel with your choice of options. As you may have noticed, the result of building the kernel is that three files are installed in /boot.  Each of these files has the version number appended to its name.

  • vmlinuz -- the compressed kernel
  • initramfs (or initrd) -- an initial file system used to boot the kernel
  • System.map -- a list of exported kernel symbols and their addresses.


To explain, vmlinuz is the executable image of the kernel, which has been compressed using gzip (or another compression algorithm). It also contains a decompression stub that unpacks the kernel when it is loaded. You can decompress this by using extract-vmlinux which you can find in the kernel scripts directory.

Temporary root file system initramfs (or its predecessor, initrd) is loaded into memory during the boot process. This root file system contains device drivers or other programs that might be needed to mount the root file system, but which may not be needed after the kernel is loaded. This allows one generic kernel to be created which can be run on a variety of systems that have different hardware.

The Linux kernel is designed as a monolithic system, with a single, large binary image. Everything making up the kernel is compiled and linked together to form this image. The exception to this are Linux Kernel Modules (LKMs) which are somewhat independent. LKMs are pieces of code that can be loaded into the kernel to extend its functionality. LKMs may be compiled either as part of the kernel build (when you select them as part of the kernel configuration) or they may be compiled separately. They may be linked into the kernel image (specified in the kernel configuration) or they can be loaded later, after the system boots and the kernel is running.

LKMs are used for several purposes, including supporting different file systems; adding new functionality or system calls, or, most commonly, to add support for new hardware. LKMs are tightly coupled to specific versions of the kernel and must be recompiled for each version of the kernel. You can see the list of LKMs which are installed on your Linux system by running the "lsmod" command or listing /proc/modules.

We are going to focus on the use of LKMs for device drivers, since this is the most common use in Embedded Linux. There are several classes of devices in Linux: Character, block, and network devices are the most common types. A character device is read or written as a sequential stream of bytes, like a keyboard or a printer. A block device is used to support a file system and is read or written as fixed size blocks. A network device is used to communicate with a different system sending or receiving data packets. Character and block devices are represented by file system entries under /dev; network interfaces have names like "eth0" for example.

Let's walk through building a simple LKM. We'll start with the simplest LKM, which we'll name lkm.c --

/*  Simple Linux Kernel Module */

#include <linux/module.h>

int init_module()
{
        printk(KERN_ALERT "LKM init\n");

        return 0;
}

void cleanup_module()
{
        printk(KERN_ALERT "LKM stopped\n");
}


This has a function, init_module( ), which it is called when the LKM is loaded into the kernel, and another, cleanup_module( ), which it's called when it is removed. Printk is similar to printf, except it directs output to the system log, /var/log/messages.

Our makefile, named Makefile, is also quite simple:

obj-m += lkm.o

KERNELDIR=/lib/modules/$(shell uname -r)/build

all:
        make -C ${KERNELDIR} M=$(PWD) modules

clean:
        make -C ${KERNELDIR} M=$(PWD) clean


The first line adds the name of our object file to the list of files to be compiled. We set KERNELDIR to point to the kernel build directory for the current version of the kernel. If you are running the kernel we built in the previous installment, this will be your build source directory. If building LKM with stock kernel, you may need to install the kernel development files. On Fedora, this is down with "yum install kernel-devel." If you want to build the LKM for a different version of the kernel, you can replace "$(shell uname -r)" with the version number. Of course, you have to have built that version or installed its development files.

When you run make, you will see something like the following:

$ make
make -C /lib/modules/3.12.8-200.fc19.x86_64/build M=/home/eager/lkm modules
make[1]: Entering directory `/usr/src/kernels/3.12.8-200.fc19.x86_64'
  CC [M]  /home/eager/lkm/lkm.o
  Building modules, stage 2.
  MODPOST 1 modules
  CC      /home/eager/lkm/lkm.mod.o
  LD [M]  /home/eager/lkm/lkm.ko
make[1]: Leaving directory `/usr/src/kernels/3.12.8-200.fc19.x86_64'

Our makefile sets up the parameter to tell what file(s) to compile and invokes the kernel Makefile to do the heavy lifting and actually build the LKM. The result is a file named lkm.ko, with the "ko" suffix indicating that this is a kernel module. We can use the "modinfo" command to see the information recorded with the module:

$ modinfo lkm.ko
filename:       /home/eager/lkm/lkm.ko
depends:        
vermagic:       3.12.8-200.fc19.x86_64 SMP mod_unload

We can load the module into the running kernel with the insmod command:

# insmod lkm.ko

Note that while we can compile our kernel module in user mode (always a good idea), you need to be root to run insmod. You can run this command in another shell where you have assumed superuser privileges using the "su" command or you can prefix the insmod command with "sudo," assuming that you have set up /etc/sudoers.

Unless there is an error, insmod doesn't issue any messages. To see that our module is loaded into the kernel, we use the lsmod command:

$ lsmod | grep lkm
lkm                    12426  0 

This indicates that our LKM is installed in the kernel, that its size is 12426 bytes, and that no other LKM depends on it. One LKM can depend on another, with a low level driver providing services to a higher level driver. You can see this if you run a command like:

$ lsmod | grep par
parport_pc             28048  0 
parport                40425  2 ppdev,parport_pc

Here we can see that the parport (parallel port) driver is used by two other drivers -- ppdev and parport_pc.

We can remove an LKM from the kernel by using the rmmod command, again, as superuser:

# rmmod lkm.ko

If we take a look at the system log, we can see the messages issued when our LKM was initialized and when it was removed:

# tail -n 2 /var/log/messages
Jan 26 17:58:36 fedora19 kernel: [27688.186462] LKM init
Jan 26 18:00:14 fedora19 kernel: [27786.528267] LKM stopped

You can install the LKM into your kernel by copying it to the correct system directory. If you look at /lib/modules you will see a subdirectory for each kernel which you have installed. If you look under the directory for the currently executing kernel, /lib/modules/$(uname -r)/kernel you will see a number of subdirectories for various kinds of kernel modules, such as crypto, drivers, memory management, and sound. We're going to copy our LKM to the drivers/misc directory as root:

# cp lkm.ko /lib/modules/kernel/$(uname -r)/kernel/drivers/misc

Next we execute the depmod command which updates the module map files which are under /lib/modules/$(uname -r):

# depmod -a

This will create an entry in modules.dep which modprobe uses to find the LKM:

# modprobe lkm

We can also remove the module using modprobe:

# modprobe -r lkm

Modprobe is a higher level command and calls insmod and rmmod to do the grunt work. It understands and resolves module prerequisites, supports aliases, and is used by the kernel to load LKMs as needed.

You may have noticed something when you look at the syslog:

Jan 26 18:38:34 fedora19 kernel: [118400.641718] lkm: module license 'unspecified' taints kernel.
Jan 26 18:38:34 fedora19 kernel: [118400.641724] Disabling lock debugging due to kernel taint
Jan 26 18:38:34 fedora19 kernel: [118400.642077] lkm: module verification failed: signature and/or required key missing - tainting kernel
Jan 26 18:38:34 fedora19 kernel: [118400.642981] LKM init
Jan 26 18:41:38 fedora19 kernel: [118585.066364] LKM stopped

The kernel module loader checks whether the LKM we are loading has a license that is compatible with the Linux kernel. Our simple LKM doesn't mention licensing and, indeed, the message says it is "unspecified." It also says it "taints" the kernel. This isn't some kind of bad smell, like milk that's too old. It means the kernel now contains a code which is not Open Source. If you ask a kernel developer for help with a bug, and he or she notices the "tainted kernel" message, you are likely going to be asked to reproduce the problem without the LKM which taints the kernel. This is for a couple reasons, one philosophical, the other practical. Linux kernel developers are interested in promoting Open Source and looking at bugs in kernels which contain only Open Source and do not contain proprietary code is one way to further this goal. The practical reason is that the proprietary code may be the cause of the bug and without access to the source it may be difficult or impossible to debug the kernel.

So much for a trivial kernel module; what about one which actually does something? In our next installment, we'll build a simple character device driver.

Michael Eager is principal consultant at Eager Consulting in Palo Alto, Calif. He has over four decades experience developing compilers, debuggers, and simulators for a wide range of processor architectures used in embedded systems. His current and former clients include major semiconductor companies and systems developers. Michael has been a member of the ISO C++ Standard Committee and ABI Committees for several processor architectures. He is chair of the Debugging Standards Committee for DWARF, a widely used debug data format. He is active in the open-source and Linux communities.

Related links:

Loading comments...

Parts Search Datasheets.com

KNOWLEDGE CENTER