I did something recently I haven’t done in a long time: I recompiled the Linux kernel. There was a time when this was a common occurrence. You might want a feature that the default kernel didn’t support, or you might have an odd piece of hardware. But these days, in almost all the cases where you need something like this, you’ll use loadable kernel modules (LKM) instead. These are modules that the kernel can load and unload at run time, which means you can add that new device or strange file system without having to rebuild or even restart the kernel.
Normally, when you write programs for Linux, they don’t have any special permissions. You typically can’t do direct port I/O, for example, or arbitrarily access memory. The kernel, however, including modules, has no such restriction. That can make debugging modules tricky because you can easily bring the system to its knees. If possible, you might think about developing on a virtual machine until you have what you want. That way, an errant module just brings down your virtual machine.
History
Some form of module support has been around since Linux 1.2. However, modern kernels can be built to include support for things or support them as modules. For example, you probably don’t want to put drivers for every single known video card in your kernel. But it is perfectly fine to build dozens or hundreds of modules you might need and then load the one you need at run time.
LKMs are at the heart of device drivers, file system drivers, and network drivers. In addition, modules can add new system calls, override existing system calls, add TTY line disciplines, and handle how executables run.
In Use
If you want to know what modules you have loaded, that’s the lsmod
command. You’ll see that some modules depend on other modules and some don’t. There are two ways to load modules: insmod
and modprobe
. The insmod command simply tries to load a module. The modprobe
command tries to determine if the module it is loading needs other modules and picks them up from a known location.
You can also remove modules with rmmod
assuming they aren’t in use. Of course, adding and removing modules requires root access. You can usually run lsmod
as a normal user if you like. You might also be interested in depmod
to determine dependencies, and modinfo
which shows information about modules.
Writing a Module
It is actually quite easy to write your own module. In fact, it is so simple that the first example I want to look at is a little more complex than necessary.
This simple module can load and unload. It leaves a message in the system messages (use dmesg
, for example) to tell you it is there. In addition, it allows you to specify a key
(just an arbitrary integer) when you load it. That number will show up in the output data. Here’s the code:
#include <linux/module.h> #include <linux/moduleparam.h> #include <linux/printk.h> MODULE_AUTHOR("Al Williams"); MODULE_DESCRIPTION("Hackaday LKM"); MODULE_LICENSE("GPLv2"); // many options, GPL, GPLv2, Proprietary, etc. static int somedata __initdata=0xbeef; // this is just some static variable available only at init static int key=0xAA; // you can override this using insmod // Note 0644 means that the sysfs entry will be rw-r--r-- module_param(key,int,0644); // use module_param_named if you want different names internal vs external MODULE_PARM_DESC(key,"An integer ID unique to this module"); static int __init had_init(void) { // This is the usual way to do this (don't forget \n and note no comma after KERN_INFO), but... printk(KERN_INFO "Hackaday is in control (%x %x)\n",key,somedata); return 0; } static void __exit had_exit(void) { // ... you can also use the pr_info macro which does the same thing pr_info("Returning control of your system to you (%x)!\n",key); } module_init(had_init); module_exit(had_exit);</pre>
This isn’t hard to puzzle out. Most of it is include files and macros that give modinfo
something to print out. There are some variables: somedata
is just a set variable that is readable during initialization. The key
variable has a default but can be set using insmod
. What’s more, is because module_param specifies 0644 — an octal Linux permission — there will be an entry in the /sys/modules
directory that will let the root set or read the value of the key.
At the end, there are two calls that register what happens when the module loads and unloads. The rest of the code is just something to print some info when those events happen.
I printed data in two ways: the traditional printk
and using the pr_info
macro which uses printk
underneath, anyway. You should probably pick one and stick with it. I’d normally just use pr_info
.
Building the modules is simple assuming you have the entire build environment and the headers for the kernel. Here’s a simple makefile (don’t forget to use tabs in your makefile):
obj-m += hadmod1.o PWD := $(CURDIR) # not needed in most cases, but useful if using sudo all: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules clean: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
Once you build things, you should have a .ko file (like hadmod.ko). That’s the module. Try a few things:
-
sudo insmod hadmod.ko # load the module
-
sudo dmesg # see the module output
-
cat /sys/modules/hadmodule/key # see the key (you can set it, too, if you are root)
-
sudo rmmod hadmod.ko # unload the module
-
sudo insmod hadmod.ko key=128 # set key this time and repeat the other steps
That’s It?
That is it. Of course, the real details lie in how you interact with the kernel or hardware devices, but that’s up to you. Just to give a slightly meatier example, I made a second version of the module that adds /proc/jollywrencher
to the /proc filesystem
. Here’s the code:
#include <linux/init.h> #include <linux/module.h> #include <linux/moduleparam.h> #include <linux/printk.h> #include <linux/uaccess.h> #include <linux/fs.h> #include <linux/proc_fs.h> // Module metadata #include <linux/version.h> MODULE_AUTHOR("Al Williams"); MODULE_DESCRIPTION("Hackaday LKM1"); MODULE_LICENSE("GPLv2"); // many options, GPL, GPLv2, Proprietary, etc. static char logo[]= " \n"\ " \n"\ " \n"\ " #@@@@@@ ,@@@@@@ \n"\ " &@@@@@* &@@@@@, \n"\ " @@@@@@% @@@@@@# \n"\ " @@ .@@@@@@@@@ .@@@@@@@@@ .@# \n"\ " &@@@& /@@@@@@@@@@@@ @@@@@@@@@@@@ @@@@* \n"\ " @@@@@@@@@@@@@@@@@@@@@# @@@@@@@@@@@@@@@@@@@@@, \n"\ " &@@@@@@@@@@@@@@@@@@@@@* ,@@@@@@@@@@@@% &@@@@@@@@@@@@@@@@@@@@@* \n"\ " ,*. @@@@@@@@@@@/ .@@@@@@@@@@@@@@@@@@@@& &@@@@@@@@@@# ** \n"\ " @@@@@@, &@@@@@@@@@@@@@@@@@@@@@@@@@, %@@@@@& \n"\ " ,@& /@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@ \n"\ " &@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@* \n"\ " %@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@. \n"\ " @@@@@@ #@@@@@@@. /@@@@@@ \n"\ " /@@@@& @@@@@@. @@@@@ \n"\ " ,@@@@% (@@@@@@@@@@&* @@@@@ \n"\ " @@@@@# @@@@@@@@@@@@@@@@@@% @@@@@& \n"\ " /@@@@@@@@@@@@@@@, #@@@@@@@@@@@@@@@ \n"\ " @@ *@@@@@@@@@@@@@& ( @@@@@@@@@@@@@@ .@( \n"\ " %@@@@@. @@@@@@@@@@@@@@@@@@@@@@@@@@@@% #@@@@@* \n"\ " (%&%((@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@% ,@@@@@@@@@@*#&&#/ \n"\ " @@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@( @@@@@@@@@@@@@@@@@@@@@& \n"\ " @@@@@@@@@@@@@@@@@@@@@ @@@@@@*@@@@@@/%@@@@@& *@@@@@@@@@@@@@@@@@@@@# \n"\ " @@@@. @@@@@@@@@@@. .. . . (@@@@@@@@@@# /@@@* \n"\ " @, %@@@@@@@@ .@@@@@@@@. &# \n"\ " ,@@@@@( @@@@@@ \n"\ " *@@@@@@ (@@@@@@ \n"\ " @@@@@@, %@@@@@@ \n"\ " \n"\ " "; static struct proc_dir_entry *proc_entry; static ssize_t had_read(struct file *f, char __user * user_buffer, size_t count, loff_t * offset) { size_t len; if (*offset>0) return 0; // no seeking, please! copy_to_user(user_buffer,logo,len=strlen(logo)); // skipped error check *offset=len; return len; } #if LINUX_VERSION_CODE >= KERNEL_VERSION(5,6,0) static struct proc_ops procop = // prior to Linux 5.6 you needed file_operations { .proc_read=had_read }; #else static struct file_operations procop = { .owner=THIS_MODULE, .read=had_read #endif static int __init had_init(void) { // This is the usual way to do this (don't forget \n and note no comma after KERN_INFO), but... printk(KERN_INFO "Hackaday<1>; is in control\n"); proc_entry=proc_create("jollywrencher",0644,NULL,&procop); return 0; } static void __exit had_exit(void) { // ... you can also use the pr_info macro which does the same thing pr_info("Returning control of your system to you...\n"); proc_remove(proc_entry); } module_init(had_init); module_exit(had_exit);
The only thing here is you have an extra function that you have to register and deregister with the kernel. However, that interface changed in Kernel 5.6, so the code tries to do the right thing. Until, of course, it gets changed again.
Once you load this module using insmod
, you can cat /proc/jollywrencher
to see your favorite web site’s logo.
Of course, this is a dead simple example, but it is enough to get you started. You can grab all the source code online. One great way to learn more is to find something similar to what you want to build and take it apart.
We don’t suggest it, but you can write an LKM in Scratch. If you really want to learn the kernel, maybe start at the beginning.
I wonder what the heading (“Kernel Modules Have Privileges”) is trying to convey. It sounded like some kind of way to limit what kernel modules can do, which would be great for security, but apparently it is just some wordplay?
Meaning they have access like the Kernel. Like “having root”….
Well beyond root. Root is trapped in userspace, the kernel has exceptional access beyond that.
Well I meant that by analogy that “having privilege” is like “having root” not that it had root privs…
I don’t think the makefile is supposed to have at the start and at the end
No, WordPress managed that just like it ate them in your comment ;-)
Fixed now, though. GitHub should be OK, in any event.
I never understood the draw to using an initrd.
Nor do I understand building every possible thing as a module.
So.. the theory is that it contains the modules without which the rest of the system cannot be booted right? All in a file that is stored somewhere that the bootloader, Grub or etc… can hand it to the kernel.
But seriously, what modules does one need to mount the root filesystem?
SATA drivers?
EXT4, Btrfs or whatever file system is being used?
In what conceivable universe am I using my desktop computer without those things for any appreciable length of time? Why would I EVER unload them? I mean maybe if I was building some sort of embedded Linux device… maybe…
I always preferred to keep those basic drivers built into my kernel, only building drivers for things that I may not be using all the time as modules and doing without the initrd altogether.
That worked really well for me in the old BIOS days!
It made for a very simple and clean config that loaded fast too.
But then I switched to UEFI so I could dual-boot newer versions of Winblows.
I tried for several hours but couldn’t get it to boot.
I don’t have the free time I used to back when I set up my old kernel so I gave up and let Genkernel do it for me, initrd and all.
This works and frankly I have better things to do with my time then learn all the ins and outs of UEFI and try to figure out how to get my simple setup back so it remains. But it still annoys me to have that damn initrd!
Now GTF off my lawn!
Yes, I also prefer keeping essential drivers in the kernel for booting. I find it makes for one less thing to go wrong when updating.
When dual booting with UEFI, I have been using a simple loader such as elilo. I then use the system’s firmware to select the boot target. This prevents problems with grub being too smart for it’s own good.
haha you’re speaking my soul. i prefer to build my own kernel and winnow it down to a reasonable set of built-in drivers. i still have a lot of things as modules, but i like to have disk and network statically linked. but that’s gotten to be an ever bigger pain in the butt because of how the kernel has grown. i still build my own kernel for almost everything but i know i remember giving up and accepting debian-generated initrd into my life. if only i could remember what computer that was on! oh well it’ll let me know
anyway UEFI isn’t so bad, which is to say i struggled for a while to accept how easy it is, and then i copied grub files into some VFAT filesystem and never thought about it since. accepting update-grub into my life was more traumatic than UEFI turned out to be. but man building the kernel i wanted for that UEFI laptop, that was a whole other ordeal.
> Nor do I understand building every possible thing as a module.
The developer start to do this 30years ago because it was very anoying to restart there linux 50 times a day when you developing something new. It was also very convinient at this times because of the lack of ram. In today of course it would very nice to build your own kernel with all the needed driver inside. But in these days >90% of the linux user did not have the skills anymore to build a kernel themself and install it. And in many distribution you did not have a plain kernel, it patched by the company who provide the distribution.
So many people now working on the kernel for so many years it is so complicate that I think nobody understood it by 100% anymore.
I am a first time linux user from kernel 0.94. I throw away any BS made by microsoft and had a happy live for 30years by 100% using linux. But even with this background sometimes you hit a wall. For example in the last day I compiled dozend of kernels between 5.4.113 and 5.4.207 and they all work, but no kernel from 208 above compiled. They all stop with many errors and I could not find the reason. If I will find probably one little peace of software that to old or wrong version in my system, I can change it and probably another mountain of worms jumps out of a can because other things are depends on it.
So I guess for most people it is better to just compile and load a module for the kernel if they not like to become a full time linux programmer. :-D
Olaf
That doesn’t make any sense.
I mean.. obviously from the context of what I wrote I was talking to the advanced Desktop user who builds their own kernel from source. And I was only talking about compiling in those drivers the computer always needs loaded anyway. I wasn’t speaking against the whole concept of having loadable/unloadable modules period.
I get why distros use an initrd. They are building to support a wide variety of machines and configurations all with one kernel. Your talk about kernel development.. I don’t see how that is relevant. Who writes kernel modules, in particular the ones the machine can’t run without and tests them on the same machine they are developing on? Sure.. they would build them as modules but run them on a VM, totally separate from what I was talking about!
1990s? RAM limitations? Again, just talking about the kernel modules that would be loaded ALL THE TIME anyway. This is about your hard drive, not about your scanner! How are you going to save RAM by building something as a module when you are never going to unload the module except at shutdown?
I’m sorry you are having troubles building new kernels.
I’m still building my kernels. I used to have a very custom .config where all the basic stuff my computer doesn’t run without anyway was built in, things I might use or might not were all modules and the rest wasn’t built at all. And it didn’t even require an initrd.
That’s what I was lamenting. I haven’t managed to do that again since switching from BIOS to UEFI.
I’m still building them though, even tweaking the config a little. I just used Genkernel one time to generate a config that actually boots on my new motherboard. Each time there is a kernel update I copy the config from the last one and do a “make oldconfig”. I’ve even made some changes since then.. which keep getting carried forward by running oldconfig.
I haven’t written a line of kernel code though. Even though I am a programmer by day! It hasn’t been necessary to become a Linux Programmer. I don’t know what you are talking about.
There if a buffer overflow in line 54 into user space.
Small differences on my Lubuntu 24.04:
parameters found in
/sys/module/hadmod/parameters/
(module was singular)
the MODULE_LICENSE tainted my kernel. It should probably be either “GPL” or “GPL v2”.