Skip to content

Pocket monster nesting in the kernel

Quentin Delhaye edited this page May 12, 2026 · 5 revisions

In this tutorial, you will:

  • Use module parameters.
  • Manage simple internal kernel state.
  • Implement periodic behaviour inside the kernel.
  • Correctly clean up resources when unloading a module.

The aim is to create a virtual pocket monster living inside the kernel. When the module is loaded, the monster is born, it receives a name and some initial state. As time passes, it becomes hungry, loses energy and its mood can change. When the module is unloaded, the monster disappears cleanly from the kernel.

There are three parts in this tutorial:

  • Part A: birth and death, load the module in the kernel, print to the log and get some parameters.
  • Part B: state and evolution.
  • Part C: autonomous monster, it evolves periodically.
  • Bonus: /proc interface, expose its state in procfs.

The Makefile to use for this module is the same as the basic hello world example.

Preliminaries -- Before we begin

printk is a good function, albeit a bit low-level. Wrappers exist and are more often used:

pr_info("Hello");
pr_warning("World");
pr_error(": )");

Each correspond to a specific printk flag:

printk(KERN_INFO "Hello");
printk(KERN_WARNING "World");
printk(KERN_ERR ": )");

In order to easily parse the kernel log, you can automatically prepend your print string with a message with the following macro:

#define pr_fmt(fmt) "kernel_monster: " fmt

When calling your pr_* function, like pr_info("Module loaded\n");, you should see it as kernel_monster: Module loaded. It makes it easy to parse the log by asking dmesg | grep "kernel_monster".

Note

Add this definition before any include, otherwise you will get a compilation warning as that macro will already have been defined in another header.

Part A: Monster birth and death

Write a module that prints a message when loaded and unloaded, and accepts some module parameters:

  • name → the monster name, string
  • hunger → initial hunger value, integer in [0, 100]
  • energy → initial energy value, integer in [0, 100]

If entered values are outside the range, you can either clamp them, or print a warning and replace them with defaults.

Add a description for each parameter using the MODULE_PARM_DESC() macro. For example, the following C code:

module_param(patate, charp, S_IRUGO);
MODULE_PARM_DESC(patate, "Name you go by.");

produces the following modinfo:

parm:           patate:Name you go by. (charp)

Part B: State and evolution

Define a structure such as:

struct kernel_monster {
    char name[32];
    int hunger;
    int energy;
    int age;
    char mood[16];
};

Implement a function that updates the monster state:

static void monster_tick(void)
{
    /* update the monster state */
}

Each tick should:

  • Increase age by 1
  • Increase hunger, staying within the range stated earlier
  • Decrease energy, staying within the range stated earlier
  • Recompute mood

Use the following rules for the mood:

if energy < 20 → "sleepy"
else if hunger > 80 → "angry"
else if hunger > 50 → "hungry"
else → "happy"

Each call to monster_tick should print a status line to the kernel log, e.g.:

kernel_monster: Tick 1 -- name=Poupoule hunger=30 energy=85 mood=happy
kernel_monster: Tick 2 -- name=Poupoule hunger=40 energy=80 mood=happy
kernel_monster: Tick 3 -- name=Poupoule hunger=50 energy=75 mood=happy
kernel_monster: Tick 4 -- name=Poupoule hunger=60 energy=70 mood=hungry

The monster update does not need to be automated yet. Simply call the monster_tick() function in your __init function a few times to trigger the changes.

Part C: Autonomous monster

The monster should now evolve automatically every second. To that end, we will schedule a delayed_work. The basic idea is as follows:

  1. Tell the kernel about a delayed_work structure.
  2. Link it to a handler, that is a function.
  3. Tell the kernel to execute the handler after a fixed amount of time.
  4. In the handler, do stuff, then schedule it again.

Here is what a very basic implementation could look like (lacking in a lot of other aspects):

#include <linux/init.h>
#include <linux/jiffies.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/workqueue.h>

static struct kernel_monster monster;
static struct delayed_work monster_work;

static void monster_work_handler(struct work_struct *work)
{
	/* Call the function updating the monster status */
	monster_tick(&monster);

	/* Schedule again in 1000 ms */
	schedule_delayed_work(&monster_work, msecs_to_jiffies(1000));
}

static int __init kernel_monster_init(void)
{
	/* Start periodic updates */
	INIT_DELAYED_WORK(&monster_work, monster_work_handler);
	schedule_delayed_work(&monster_work, msecs_to_jiffies(1000));

	return 0;
}

static void __exit kernel_monster_exit(void)
{
	/* Clean up by cancelling the delayed work */
	cancel_delayed_work_sync(&monster_work);
}

module_init(kernel_monster_init);
module_exit(kernel_monster_exit);

Warning

Don't forget to clean up everything you started or initialised in the kernel when you unload the module.

Part D: /proc interface

Reading the monster status in kernel log is cumbersome, expose it in a read-only procfs file /proc/kernel_monster. This will be our first example of kernel-to-userspace interface, without needing a character device.

Here is what yet another basic implementation could look like:

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/string.h>

static struct kernel_monster monster;
static struct proc_dir_entry *monster_proc_entry;

static int monster_proc_show(struct seq_file *m, void *v)
{
	seq_printf(m, "name: %s\n", monster.name);
	seq_printf(m, "age: %d\n", monster.age);
	seq_printf(m, "hunger: %d\n", monster.hunger);
	seq_printf(m, "energy: %d\n", monster.energy);
	seq_printf(m, "mood: %s\n", monster.mood);

	return 0;
}

static int monster_proc_open(struct inode *inode, struct file *file)
{
	return single_open(file, monster_proc_show, NULL);
}

static const struct proc_ops monster_proc_ops = {
	.proc_open    = monster_proc_open,
	.proc_read    = seq_read,
	.proc_lseek   = seq_lseek,
	.proc_release = single_release,
};

static int __init kernel_monster_init(void)
{
	/* Create /proc entry */
	monster_proc_entry = proc_create("kernel_monster", S_IRUGO, NULL, &monster_proc_ops);
	if (!monster_proc_entry) {
		pr_err("Failed to create /proc/kernel_monster\n");
		return -ENOMEM;
	}

	return 0;
}

static void __exit kernel_monster_exit(void)
{
	/* Clean-up! */
	if (monster_proc_entry)
		proc_remove(monster_proc_entry);
}

module_init(kernel_monster_init);
module_exit(kernel_monster_exit);

Calling cat /proc/kernel_monster should print:

name: Poupoule
age: 12
hunger: 80
energy: 15
mood: angry

Possible extensions

  • Add a tick_ms module parameter to set the update rate.
  • Random event, e.g. "found food", "fell asleep".
  • max_age parameter, after which the monster dies.
  • /proc/kernel_monster_log history of recent events.

Clone this wiki locally