In 2021 I spent a lot of time studying low-level in Linux, as I'm a low-level addict in general, I only now decided to change my area to introduce myself to the market, and I wrote a repository on github that I'll later turn into a paper, teaching me how from a hello world lkm to an lkm rootkit, I realized that many people don't even know what the linux loadable kernel module is and how important it is.So I decided to make this post that teaches how to do a hello world lkm, which prints hello world in the system logs. This serves as a basic example to learn how to write and load modules in the Linux kernel.

The code below is a simple example of how to print a hello world in the system logs:


#endif /* _KERNEL_MODULE_ */

#include <linux/module.h> /* included for all kernel modules       */
#include <linux/kernel.h> /* included for KERN_INFO                */
#include <linux/init.h>   /* Needed for the macros */


static int __init hello_init(void)
    printk(KERN_INFO "<1>Hello World\n");
    return 0; // Returns 0 to indicate initialization was successful

static void __exit hello_exit(void)
    printk(KERN_INFO"<1> Bye bye!");


To run and test this code you need to install some dependencies:


C compiler of your preference, I recommend gcc or cc

After installing the dependencies to compile you need to write a makefile that is a little different from the conventional one, I will leave an example of a makefile for some lkm code:

obj-m := example.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)

    $(MAKE) -C $(KDIR) M=$(PWD) modules

    $(MAKE) -C $(KDIR) M=$(PWD) clean

How to install dependencies?

This may vary from system to system, be sure to adapt these code snippets according to your linux distributions.

sudo apt install linux-headers-$(uname -r) 
sudo apt install build-essential

You can see the modules being loaded into the kernel with the lsmod command it checks the information in /proc/modules.

Tested on:

Manjaro 5.11.2-amd64
Debian 12 6.1.0-amd64

For those who want to know more about lkm, this is the link to my repository: LinuxLowlevelAddict

Image of tux linux mascot

  • \$\begingroup\$ Isn't #endif /* _KERNEL_MODULE_ */ added too early? Now this only defines the guard, but still emits all code on second inclusion... \$\endgroup\$
    – STerliakov
    Commented Jun 14 at 19:01

1 Answer 1


bonjour tout le monde

This is very nice, thank you. It is wonderfully simple, a pre-requisite for Hello examples.


As you know, systems differ, bit rot happens, and gremlins arise, which is why we assemble such examples in the first place. We strive for an easy win, and build upon it from there. A "working system" can teach us more, and more quickly, than a "broken system", as it encourages us to tweak it and see if it still works.

Please, please, tell us which version of Ubuntu (or CentOS?) you built this on. And the tools and kernel versions you used. Oh, wait, this sounds tedious. I bet there's a solution for such needs...


Consider publishing the hash of a docker image you rely on. That way a learner can quickly get up and running, see "<1>Hello world", and start tweaking an already-working setup.

Regarding the C code? No notes. Well, ok, consider making MODULE_AUTHOR map more directly to your GitHub repo, which you might choose to update in future.

EDIT: The OP used manjaro with kernel 5.11.2, and debian with kernel version 6.1.0-18-amd64.


C compiler of your preference, I recommend gcc or cc

Sorry, I didn't get that last one. You might want to mention a particular software vendor, perhaps with a version that you used.

Even mentioning the month + year in a posting is invaluable to folks a decade from now who are trying to understand and reproduce your results. It narrows down the set of versions that could possibly be involved.

GNU make

PWD := $(shell pwd)

Ummm, we don't like $(CURDIR)? The docs explain:

For your convenience, when GNU make starts (after it has processed any -C options) it sets the variable CURDIR to the pathname of the current working directory. This value is never touched by make again ...

This codebase is clearly written and achieves its design goals.

I would be willing to delegate or accept maintenance tasks on it.

  • \$\begingroup\$ I tested on manjaro in 2021 with kernel 5.11.2, and on debian this year I tested on kernel version 6.1.0-18-amd64. And to test on your system you have to install the linux headers manually with sudo apt install linux-headers-$(uname -r) linux-source the command may vary depending on the system, and regarding compilation I used gnu make to generate the binary, sorry for the confusion, I'll edit it to make it clearer. I'm Brazilian and my English is very bad, so there are some things that weren't very clear. \$\endgroup\$ Commented Jun 14 at 1:19
  • \$\begingroup\$ "...and my English..." -- Oh, no, no, not at all. Your English is terrific, not an impediment to understanding in any way. I was just giving voice to my standard grumbles about "cite your reference", "what versions for dependencies?", "nail down the SHA hash!", things like that. Something that's easy for an author to lose track of is, Right Now, it is sort of obvious what a Manjaro or an Arch user would do. But as years go by, things change, and it can be hard to make sense of an old ReadMe that omits date and version details, hard to reproduce its results. \$\endgroup\$
    – J_H
    Commented Jun 14 at 1:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.