DSP-17 #3: "Say hello to my little friend" - writing a "Hello, World!" kernel module for Linux
Linux is capable of many interesting things, and loadable module functionality is yet another. Linux kernel modules simply put are just pieces of binary that can get linked on-the-fly against a running kernel.
This approach is especially nice for speeding up your write-compile-run cycle, systems that frown upon downtimes or creating drivers that require minimum work when porting between kernel versions.
Today, we’re going to harness that power and write a module that will print two messages on module load and unload, respectively.
Prerequisites
- A (virtual) machine running Linux - If you’re already running Linux the VM is optional, as it’s hard to break anything with this particular module
- Basic understanding of C and simple GNU Makefiles - There’s finally gonna be C code involved and for our build, we’ll use a simple Makefile
Coding style
Before we write any code it’s important to mention how to write it. Some of the most notable guidelines involve:
- 8-space-wide tabs - Most programmers would probably expect 4 spaces for indentation; with Linux we’re using actual tab characters set to the width of 8 spaces; Vim users will be happy to know that a plugin called vim-sleuth can deduce the indentation from the current buffer and comply with it accordingly, thus eliminating the need to adjust the settings manually
- 80-column line width limit - Linux is not much different here from other coding conventions, i.e. if a longer line significantly benefits readability, it’s acceptable to leave it be
- When in doubt, comply with K&R - When Brian W. Kernighan and Dennis Ritchie wrote “The C programming language” (a.k.a. K&R), their creation also established a robust standard for C coding style. In fact, what Linux is using can be thought of as a compatible subset of those rules
For a full explanation of the preferred coding style, see this doc.
The code
At last, some source code. Here’s what my module, called hello.c
, looks like:
#include <linux/module.h> /* Needed by all modules */
#include <linux/kernel.h> /* For KERN_INFO */
int __init hello_init(void)
{
printk(KERN_INFO "Hello, world!\n");
return 0;
}
void __exit hello_exit(void)
{
printk(KERN_INFO "Goodbye, world!\n");
return;
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_DESCRIPTION("My \"Hello, World!\" module");
MODULE_AUTHOR("Stan Drozd <drozdziak1@gmail.com>");
MODULE_LICENSE("GPL");
You can also find the code in the course’s repository.
Not too scary, right? Let’s talk about what the module does.
First, we include linux/module.h
- a header essential for all Linux kernel
modules. Then, we want linux/kernel.h
for printk()
and log
levels. Both of those headers are to be found in the ./include/linux/
directory of your kernel’s source tree or header directory.
hello_init
is our module’s init function - it’s supposed to make all
preparations necessary to get a module ready to do its job (in our case we only
want to greet the globe). Init functions are usually called only once on module
load. The __init
attribute tells your kernel to free the memory hello_init
was occupying. For printing the message we’re using printk()
- a printf()
kernel equivalent - with a log level macro called KERN_INFO
, which is meant
for information that is neither error nor debug related. The init function has
no parameters and is supposed to return an int
that is either equal to 0
or
a negative error code, e.g. -ENOMEM
if a memory allocation fails. To
learn some more error codes and their meaning, see this header.
hello_exit
on the other hand cares about everything that has to do with
cleaning up when your module unloads. It too has an attribute, __exit
, which
tells your compiler to ignore hello_exit
when building your code as a built-in
module, as those aren’t unloadable anyway (We sure will talk about in-tree
modules in the future ). Exit functions don’t take
arguments and have no return values.
Last but not least, we specify some general info about our module. The macros
module_init
and module_exit
will tell kbuild which functions you want to use
for module initialization and cleanup, respectively. As you might expect, the
last three MODULE_*
macros specify the module’s description, author and
license.
The build
The Makefile for hello.c
is as simple as it gets:
obj-m += hello.o
KERNEL_VERSION := $(shell uname -r)
KDIR ?= /lib/modules/$(KERNEL_VERSION)/build
all:
$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean
If you have enough of a grasp on Makefiles, you’ll quickly see that all we do
here is just visit a kernel directory and run its Makefile. We specify an M
parameter which stands for the path to our module’s sources. I added a
KDIR
variable for specifying the desired kernel tree, which defaults to your
currently running kernel.
obj-m
is a list of objects to compile as loadable modules. Other kernel Makefiles
will also specify obj-y
and obj-n
as the collections of modules to build
into the kernel and not to compile at all, respectively.
Compiling against the running kernel
To build our module, all we need to do is run make -jN
:
$ make -j4
make -C /lib/modules/4.10.4-1-MANJARO/build M=/home/drozdziak1/kernel-safari/dsp17-3 modules
make[1]: Entering directory '/usr/lib/modules/4.10.4-1-MANJARO/build'
CC [M] /home/drozdziak1/kernel-safari/dsp17-3/hello.o
Building modules, stage 2.
MODPOST 1 modules
CC /home/drozdziak1/kernel-safari/dsp17-3/hello.mod.o
LD [M] /home/drozdziak1/kernel-safari/dsp17-3/hello.ko
make[1]: Leaving directory '/usr/lib/modules/4.10.4-1-MANJARO/build'
Compiling against a specific kernel tree
If you’re not using Linux on your machine or simply don’t want to run your
module against the running kernel, you can just take a virtual machine (like the
one I showed you here) and use its’ kernel instead. To do that, we’re
going to overwrite KDIR
in our make
invocation:
$ make -j4 KDIR=../linux
make -C ../linux M=/home/drozdziak1/kernel-safari/dsp17-3 modules
make[1]: Entering directory '/home/drozdziak1/kernel-safari/linux'
CC [M] /home/drozdziak1/kernel-safari/dsp17-3/hello.o
Building modules, stage 2.
MODPOST 1 modules
CC /home/drozdziak1/kernel-safari/dsp17-3/hello.mod.o
LD [M] /home/drozdziak1/kernel-safari/dsp17-3/hello.ko
make[1]: Leaving directory '/home/drozdziak1/kernel-safari/linux'
When building against a kernel tree of your choosing, remember to compile that
kernel first before pointing at it with KDIR
.
Loading and unloading of kernel modules
After your module builds, you should end up with a bunch of new files:
.
├── Makefile
├── hello.c
│
├── Module.symvers - Information about symbols declared in your module (empty at
│ this point)
│
├── hello.mod.c - Generated C code for embedding a module's info into the *.ko
│ file
│
├── hello.mod.o - Same, but compiled and ready for linking
│
├── hello.o - A raw, freshly compiled version of your module (not linked yet)
│
├── hello.ko - The finished module binary, which we'll insert into the kernel
│ shortly
│
└── modules.order - Defines the order in which your module's dependencies
should be met
If you’d like to get a better idea about how Linux modules are compiled, see this Makefile.
To see the information about your module, use modinfo
:
$ modinfo hello.ko
filename: /home/drozdziak1/kernel-safari/dsp17-3/hello.ko
license: GPL
author: Stan Drozd <drozdziak1@gmail.com>
description: My "Hello, World!" module
depends:
vermagic: 4.10.6-1-ARCH SMP preempt mod_unload modversions
For attaching, listing and removing our module from the kernel, we’ll
use insmod
, lsmod
and rmmod
(usually provided by the kmod
package,
present in the VM thanks to busybox
):
/ # insmod hello.ko
[ 14.15021] hello: loading out-of-tree module taints kernel.
[ 14.159069] Hello, world!
/ # lsmod | grep hello
hello 16384 0 - Live 0xffffffffa0005000 (O)
/ # rmmod hello.ko
[ 18.773422] Goodbye, world!
/ # lsmod
/ #
Keep in mind that messing with a running kernel requires root privileges. Use
sudo
as needed; lsmod
will swarm you with module listings when used on the
host, so grep
might come in handy.
The taints kernel
part indicates that after inserting a module built outside
the original kernel’s directory, the kernel’s behavior may not be 100%
predictible; Linux can also get tainted for various other
reasons.
If you used your host’s running kernel for testing the module, and you sport a
terminal emulator like gnome-terminal
or xfce4-terminal
, you may miss the
module’s log output. That’s because kernel logs get printed in real time only to
“real” terminals, and not pseudoterminals, which terminal emulators use (see
here for more details).
The easiest way to see full kernel logs from anywhere is using dmesg
. By
default, it should spew it all out from the very boot sequence to the most
recent entries at your default log level:
$ dmesg
[ 0.000000] Linux version 4.10.6-1-ARCH (builduser@tobias) (gcc version 6.3.1 20170306 (GCC) ) #1 SMP PREEMPT Mon Mar 27 08:28:22 CEST 2017
[ 0.000000] Command line: BOOT_IMAGE=/vmlinuz-linux root=UUID=dafec227-4989-4485-a1c5-59520fbb51be rw cryptdevice=UUID=a2feb722-1089-4620-83c8-573596dab444:root
[ 0.000000] x86/fpu: Legacy x87 FPU detected.
[ 0.000000] e820: BIOS-provided physical RAM map:
[ 0.000000] BIOS-e820: [mem 0x0000000000000000-0x000000000009fbff] usable
[ 0.000000] BIOS-e820: [mem 0x00000000000f0000-0x00000000000fffff] reserved
[ 0.000000] BIOS-e820: [mem 0x0000000000100000-0x00000000cfe0abff] usable
[ 0.000000] BIOS-e820: [mem 0x00000000cfe0ac00-0x00000000cfe5cbff] ACPI NVS
[ 0.000000] BIOS-e820: [mem 0x00000000cfe5cc00-0x00000000cfe5ebff] ACPI data
[ 0.000000] BIOS-e820: [mem 0x00000000cfe5ec00-0x00000000cfffffff] reserved
[ 0.000000] BIOS-e820: [mem 0x00000000e0000000-0x00000000efffffff] reserved
[ 0.000000] BIOS-e820: [mem 0x00000000fe000000-0x00000000feffffff] reserved
[ 0.000000] BIOS-e820: [mem 0x00000000ffb00000-0x00000000ffffffff] reserved
[ 0.000000] BIOS-e820: [mem 0x0000000100000000-0x000000022fffffff] usable
[ 0.000000] NX (Execute Disable) protection: active
[ 0.000000] SMBIOS 2.5 present.
[...]
[11623.122335] ath: Country alpha2 being used: CN
[11623.122336] ath: Regpair used: 0x52
[11623.127174] ieee80211 phy1: Atheros AR9271 Rev:1
[11623.129346] ath9k_htc 1-8:1.0 wlp0s29f7u8: renamed from wlan0
[11623.165843] IPv6: ADDRCONF(NETDEV_UP): wlp0s29f7u8: link is not ready
[11623.350130] IPv6: ADDRCONF(NETDEV_UP): wlp0s29f7u8: link is not ready
[11623.596588] IPv6: ADDRCONF(NETDEV_UP): wlp0s29f7u8: link is not ready
[11623.648622] IPv6: ADDRCONF(NETDEV_UP): wlp0s29f7u8: link is not ready
[11625.417598] IPv6: ADDRCONF(NETDEV_UP): wlp0s29f7u8: link is not ready
[11626.743869] wlp0s29f7u8: authenticate with 8c:04:ff:f5:df:9f
[11626.999492] wlp0s29f7u8: send auth to 8c:04:ff:f5:df:9f (try 1/3)
[11627.001355] wlp0s29f7u8: authenticated
[11627.002584] wlp0s29f7u8: associate with 8c:04:ff:f5:df:9f (try 1/3)
[11627.006338] wlp0s29f7u8: RX AssocResp from 8c:04:ff:f5:df:9f (capab=0x1411 status=0 aid=7)
[11627.016317] wlp0s29f7u8: associated
[11627.016374] IPv6: ADDRCONF(NETDEV_CHANGE): wlp0s29f7u8: link becomes ready
[14961.125874] Hello, world!
[14967.349120] Goodbye, world!
Looks good!
If you still don’t get your module’s log output, try explicitly printing the
KERN_INFO
log level with dmesg -l info
.
Can I use my module with any kernel?
Short answer: No, a module that doesn’t match the running kernel will not load and get rejected.
Long answer: You can, and in some cases it would run perfectly fine. Module
compatibility is guarded with the vermagic
string we’ve seen earlier in
modinfo
output. You could load the module with modprobe
, a more
sophisticated module management tool, which offers a --force
flag for ignoring
version mismatches. Don’t ever do that unless you know what you’re doing.
Conclusion
This post took a surprisingly long time to write, so I’ll just hope that it proved useful to someone . If you see an error or have a question, help yourself to the comment section. See you next time, when we’ll talk about the kernel source directory and where to look for various things.
Further reading
- The fancy new kernel documentation
- Documentation/process/coding-style.rst - The full kernel docs on coding style
- Documentation/kbuild/modules.txt - More on kernel modules
-
include/linux/init.h - More on
__init
and__exit
- The modprobe man page