Writing a Simple Linux Kernel Module

Prerequisites

Before we get started, we need to make sure we have the correct tools for the job. Most importantly, you’ll need a Linux machine. I know that comes as a complete surprise! While any Linux distribution will do, I am using Ubuntu 16.04 LTS in this example, so if you’re using a different distribution you may need to slightly adjust your installation commands.

Secondly, you’ll need either a separate physical machine or a virtual machine. I prefer to do my work in a virtual machine, but this is entirely up to you. I don’t suggest using your primary machine because data loss can occur when you make a mistake. I say when, not if, because you undoubtedly will lock up your machine at least a few times during the process. Your latest code changes may still be in the write buffer when the kernel panics, so it’s possible that your source files can become corrupted. Testing in a virtual machine eliminates this risk.

And finally, you’ll need to know at least some C. The C++ runtime is far too large for the kernel, so writing bare metal C is essential. For interaction with hardware, knowing some assembly might be helpful.

Installing the Development Environment

On Ubuntu, we need to run:

apt-get install build-essential linux-headers-`uname -r`

This will install the essential development tools and the kernel headers necessary for this example.

The examples below assume you are running as a regular user and not root, but that you have sudo privileges. Sudo is mandatory for loading kernel modules, but we want to work outside of root whenever possible.

Getting Started

Let’s start writing some code. Let’s prepare our environment:

mkdir ~/src/lkm_example
cd ~/src/lkm_example

Fire up your favorite editor (in my case, this is vim) and create the file lkm_example.c with the following contents:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
MODULE_LICENSE(“GPL”);
MODULE_AUTHOR(“Robert W. Oliver II”);
MODULE_DESCRIPTION(“A simple example Linux module.”);
MODULE_VERSION(“0.01”);
static int __init lkm_example_init(void) {
 printk(KERN_INFO “Hello, World!\n”);
 return 0;
}
static void __exit lkm_example_exit(void) {
 printk(KERN_INFO “Goodbye, World!\n”);
}
module_init(lkm_example_init);
module_exit(lkm_example_exit);

Now that we’ve constructed the simplest possible module, let’s example the important parts in detail:

· The “includes” cover the required header files necessary for Linux kernel development.

· MODULE_LICENSE can be set to a variety of values depending on the license of the module. To see a full list, run:
grep “MODULE_LICENSE” -B 27 /usr/src/linux-headers-`uname -r`/include/linux/module.h

· We define both the init (loading) and exit (unloading) functions as static and returning an int.

· Note the use of printk instead of printf. Also, printk doesn’t share the same parameters as printf. For example, the KERN_INFO, which is a flag to declare what priority of logging should be set for this line, is defined without a comma. The kernel sorts this out inside the printk function to save stack memory.

· At the end of the file, we call module_init and module_exit to tell the kernel which functions are or loading and unloading functions. This gives us the freedom to name the functions whatever we like.

We can’t compile this file yet, though. We need a Makefile. This basic example will work for now. Note that make is very picky about spaces and tabs, so ensure you use tab instead of space where appropriate.

obj-m += lkm_example.o
all:
 make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
 make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

If we run “make”, it should compile your module successfully. The resulting file is “lkm_example.ko”. If you receive any errors, check that your quotation marks in the example source file are correct and not pasted accidentally as UTF-8 characters.

Now we can insert the module to test it. To do this, run:

sudo insmod lkm_example.ko

If all goes well, you won’t see a thing. The printk function doesn’t output to the console but rather the kernel log. To see that, we’ll need to run:

sudo dmesg

You should see the “Hello, World!” line prefixed by a timestamp. This means our kernel module loaded and successfully printed to the kernel log. We can also check to see if the module is still loaded:

lsmod | grep “lkm_example”

To remove the module, run:

sudo rmmod lkm_example

If you run dmesg again, you’ll see “Goodbye, World!” in the logs. You can also use lsmod again to confirm it was unloaded.

As you can see, this testing workflow is a bit tedious, so to automate this we can add:

test:
 sudo dmesg -C
 sudo insmod lkm_example.ko
 sudo rmmod lkm_example.ko
 dmesg

at the end of our Makefile and now run:

make test

to test our module and see the output of the kernel log without having to run separate commands.

Now we have a fully functional, yet completely trivial, kernel module!

Missing separator in Makefile?

The following Makefile is not working and I am not sure what’s going on.

CC = gcc
CFLAGS = -Wall -g

demo:
    ${CC} ${CFLAGS} demo.c -o demo
lib:
    ${CC} ${CFLAGS} lib.c -o lib
clean:
    rm -f lib demo

Demo has the main function and lib has a set of methods used in demo.

I added the -c flag to lib. However when I run make, I get:

Makefile:5: *** missing separator.  Stop.

Solution:

Given your update with the error, check what you have on the line before those ${CC} commands. Many make programs require a real tab character before the commands and editors that put in eight spaces (for example) will break them. That’s more often than not the cause of the “Missing separator” errors.

You can see that with the following transcript. In the file, there are four spaces before the $(xyzzy):

xyzzy=echo
all:
    $(xyzzy) hello

So, when I make it, I get the same error as you:

pax> make
makefile:3: *** missing separator.  Stop.

But, when I edit it and turn those four spaces into a tab, it works fine:

pax> make
echo hello
hello

You also have a problem with the way you’re trying to combine the source files together.

Without a -c flag to gcc, it will try to create a separate executable from each of those commands, almost certainly leading to linker errors. You’re going to need something like (simple):

CC = gcc
CFLAGS = -Wall -g

# Just compile/link all files in one hit.
demo: demo.c lib.c
   ${CC} ${CFLAGS} -o demo demo.c lib.c

clean:
    rm -f demo

or (slightly more complex):

CC = gcc
CFLAGS1 = -Wall -g -c
CFLAGS2 = -g

# Link the two object files together.

demo: demo.o lib.o
   ${CC} ${CFLAGS2} -o demo demo.o lib.o

# Compile each source file to an object.

demo.o: demo.c
   ${CC} ${CFLAGS1} -o demo.o demo.c

lib.o: lib.c
   ${CC} ${CFLAGS1} -o lib.o lib.c

clean:
    rm -f demo

The problem with the first solution is that it unnecessarily compiles both programs even when only one is out of date. The second solution is a little more intelligent.

How to install Telnet on Mac OS High Sierra

With the last update of Mac OS High Sierra, some command tools are removed. If do you need use the Telnet tool, could be re-install this running the follow command lines:

curl http://ftp.gnu.org/gnu/inetutils/inetutils-1.9.4.tar.gz -o inetutils-1.9.4.tar.gz

tar xvzf inetutils-1.9.4.tar.gz

cd inetutils-1.9.4

./configure

make

sudo make install

Finally use telnet

python: uninstall python 2.7 install via source

To completely remove python you can execute:

rm -f /usr/local/bin/python2.7
rm -f /usr/local/bin/pip2.7
rm -f /usr/local/bin/pydoc
rm -rf /usr/local/bin/include/python2.7
rm -f /usr/local/lib/libpython2.7.a
rm -rf /usr/local/lib/python2.7

You might also have to do

rm -f /usr/local/share/man/python2.7.1
rm -rf /usr/local/lib/pkgconfig
rm -f /usr/local/bin/idle
rm -f /usr/local/bin/easy_install-2.7