Repository navigation
Pocket monster nesting in the kernel
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:
/procinterface, expose its state in procfs.
The Makefile to use for this module is the same as the basic hello world example.
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: " fmtWhen 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.
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)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
ageby 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=hungryThe 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.
The monster should now evolve automatically every second.
To that end, we will schedule a delayed_work.
The basic idea is as follows:
- Tell the kernel about a
delayed_workstructure. - Link it to a handler, that is a function.
- Tell the kernel to execute the handler after a fixed amount of time.
- 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.
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- Add a
tick_msmodule parameter to set the update rate. - Random event, e.g. "found food", "fell asleep".
-
max_ageparameter, after which the monster dies. -
/proc/kernel_monster_loghistory of recent events.
→ Stuck ? Check the troubleshooting page.
→ More resources.
→ Suggest updates and new topics by submitting an issue.