Skip to main content

Command Palette

Search for a command to run...

Building a Simple Linux Kernel Object Cache with the SLAB Allocator: From Module Signing to Cleanup

Updated
6 min read
Building a Simple Linux Kernel Object Cache with the SLAB Allocator: From Module Signing to Cleanup

Building a Simple Linux Kernel Object Cache Using SLAB Allocator

Linux kernel development is very different from user-space programming.

In user space, developers typically use:

malloc();
free();

Inside the kernel, memory allocation is handled using specialized allocators such as:

kmalloc();
kfree();

and for frequently created kernel objects:

kmem_cache_alloc();
kmem_cache_free();

In this article we build and load a simple kernel module that implements a tiny caching layer using the Linux SLAB allocator and then walk through module signing, loading, debugging, and cleanup.

Source Repository:

GitHub Repository


Why Object Caches Exist

Many kernel subsystems repeatedly allocate and destroy the same type of object.

Examples include:

  • Inodes
  • Dentries
  • Socket buffers
  • Process-related structures

Allocating these structures from a dedicated cache is usually faster than repeatedly requesting generic memory blocks.

The SLAB allocator provides:

  • Object reuse
  • Better cache locality
  • Reduced fragmentation
  • Optional constructors
  • Faster allocation for hot objects

High-Level Architecture

Our module creates a cache of objects and maintains them in a linked list.

Architecture Diagram

                 +------------------+
                 |  SLAB CACHE      |
                 | kmem_cache       |
                 +--------+---------+
                          |
          +---------------+--------------+
          |                              |
          v                              v

     cache_obj                     cache_obj
    +---------+                   +---------+
    | id = 1  |----next---------> | id = 2  |
    | alpha   |                   | beta    |
    +---------+                   +---------+

          Linked List of Objects

The Cache Object

Each object stored inside the cache contains:

struct cache_obj {
        int id;
        char name[32];
        unsigned long created;
        struct list_head list;
};

Each object stores:

Field Purpose
id Unique identifier
name Human readable label
created Creation timestamp
list Kernel linked-list node

Why Not Use kmalloc()

A beginner may wonder:

obj = kmalloc(sizeof(*obj), GFP_KERNEL);

Why create a cache at all?

Because the allocator only sees:

Allocate 64 bytes
Allocate 64 bytes
Allocate 64 bytes

It does not know these are identical objects.

SLAB caches know exactly what object type is being allocated.

Give me cache_obj
Give me cache_obj
Give me cache_obj

This enables object reuse.


kmalloc() vs kmem_cache_alloc()

Feature kmalloc() kmem_cache_alloc()
Generic allocator Yes No
Object aware No Yes
Reuse objects Limited Yes
Constructor support No Yes
Best for buffers Yes No
Best for kernel objects No Yes

Memory Allocation Flow

kmalloc()

Request
   |
   v
Allocate N Bytes
   |
   v
Return Pointer

kmem_cache_alloc()

Request Object
      |
      v

+------------------+
|     SLAB CACHE   |
|                  |
| free object      |
| free object      |
| free object      |
+---------+--------+
          |
          v

Return Prepared Object

Creating a Cache

A cache is created during module initialization.

Example:

cachep = kmem_cache_create(
            "cache_obj",
            sizeof(struct cache_obj),
            0,
            0,
            NULL);

Parameters:

Name         : cache_obj
Object Size  : sizeof(cache_obj)
Alignment    : default
Flags        : none
Constructor  : NULL

After creation the cache can hand out objects very quickly.


Allocating Objects

Objects are allocated from the cache.

obj = kmem_cache_alloc(cachep, GFP_KERNEL);

if (!obj)
        return -ENOMEM;

The NULL check is mandatory.

Without it:

obj->id = 1;

could become:

NULL->id = 1;

leading to:

BUG: kernel NULL pointer dereference

Understanding -ENOMEM

if (!obj)
        return -ENOMEM;

Meaning:

Memory allocation failed.

Stop execution and notify caller that
the system is out of memory.

Common kernel error codes:

-ENOMEM   // Out of memory
-EINVAL   // Invalid argument
-EFAULT   // Invalid address
-EBUSY    // Resource busy

Linking Objects Together

The kernel provides a generic linked-list implementation.

Example:

alpha --> beta --> gamma

Visualization:

+-------+      +-------+      +-------+
| alpha | ---> | beta  | ---> | gamma |
+-------+      +-------+      +-------+

This avoids writing custom linked-list code.


Building the Module

Compile:

make

Expected output:

CC [M] simple_cache.o
LD [M] simple_cache.ko

The generated file:

simple_cache.ko

is a loadable kernel module.


Why Module Signing Matters

Modern Linux distributions often enable Secure Boot.

Unsigned modules may fail to load.

Flow:

Kernel Module
      |
      v
 Module Signature
      |
      v
 Secure Boot Validation
      |
      v
 Load or Reject

Signing the Kernel Module

Sign the module using the kernel's signing utility.

sudo /usr/src/linux-headers-$(uname -r)/scripts/sign-file \
sha256 \
~/kernel_keys/MOK.key \
~/kernel_keys/MOK.crt \
simple_cache.ko

Explanation:

Component Purpose
sign-file Kernel signing utility
sha256 Digest algorithm
MOK.key Private key
MOK.crt Public certificate
simple_cache.ko Module being signed

Loading the Module

Insert the module:

sudo insmod simple_cache.ko

Initialization sequence:

Module Load
      |
      v
Create SLAB Cache
      |
      v
Allocate Objects
      |
      v
Insert Into Linked List
      |
      v
Print Kernel Logs

Monitoring Kernel Messages

Open a live kernel log viewer.

dmesg -w

Expected output:

CACHE: created 1 alpha
CACHE: created 2 beta

Architecture:

Kernel Module
      |
      v
 printk()
      |
      v
 Kernel Ring Buffer
      |
      v
    dmesg

Cleaning Up

Remove the module.

sudo rmmod simple_cache

Cleanup sequence:

Delete Objects
      |
      v
Free Objects
      |
      v
Destroy Cache
      |
      v
Module Exit

Correct Memory Pairing

Always pair allocation and free APIs correctly.

Correct:

ptr = kmalloc(size, GFP_KERNEL);
kfree(ptr);

Correct:

obj = kmem_cache_alloc(cachep, GFP_KERNEL);
kmem_cache_free(cachep, obj);

Incorrect:

obj = kmem_cache_alloc(cachep, GFP_KERNEL);
kfree(obj);

Incorrect:

ptr = kmalloc(size, GFP_KERNEL);
kmem_cache_free(cachep, ptr);

Mixing allocators can corrupt kernel memory.


Real Kernel Components Using SLAB

Many core Linux subsystems depend heavily on slab caches.

Examples:

inode cache
dentry cache
socket buffers
VFS metadata
network objects
task structures

Our module is a miniature version of the same concept.


Complete Execution Flow

make
 |
 v

simple_cache.ko
 |
 v

sign-file
 |
 v

Signed Module
 |
 v

insmod
 |
 v

Create SLAB Cache
 |
 v

Allocate Objects
 |
 v

Store in Linked List
 |
 v

Observe via dmesg
 |
 v

rmmod
 |
 v

Free Objects
 |
 v

Destroy Cache

Key Takeaways

  • kmalloc() is a generic kernel allocator.
  • kmem_cache_alloc() is optimized for repeatedly used object types.
  • SLAB caches improve performance and memory locality.
  • Always check allocation failures.
  • Pair allocation and deallocation APIs correctly.
  • Module signing is required on many Secure Boot systems.
  • dmesg -w is invaluable when debugging kernel modules.
  • Proper cleanup is mandatory inside kernel code.

References

Linux Kernel Documentation

  • Memory Management
  • SLAB Allocator
  • Loadable Kernel Modules

Repository:

https://github.com/aj333git/linux_kernel_kobjex2


Footer

Source code and experiments are available in the repository: https://github.com/aj333git/linux\_kernel\_kobjex2

Based on the kernel object cache concepts and notes provided in your source material.