Skip to main content

Command Palette

Search for a command to run...

Linux Kernel Memory Allocation Engine with a Control Plane / Data Plane Architecture

Updated
7 min read
Linux Kernel Memory Allocation Engine with a Control Plane / Data Plane Architecture

Building a Linux Kernel Memory Allocation Engine with a Control Plane / Data Plane Architecture

Introduction

Most Linux kernel module tutorials demonstrate a simple memory allocation example using kmalloc() and kfree().

While these examples are useful, they rarely discuss a broader systems-design question:

Can memory allocation be modeled using the same architectural ideas that power modern distributed systems?

To explore this idea, I built a small Linux kernel project that separates decision making from execution using a Control Plane / Data Plane architecture.

The project combines:

  • Linux Kernel Modules (LKM)
  • Kernel Heap Memory Management
  • Dynamic Allocation Tracking
  • Module Parameters
  • Dangling Pointer Analysis
  • Control Plane / Data Plane Design
  • F#-Driven Configuration Generation
  • Foundations for Future DSL and Compiler Work

Why Control Plane / Data Plane?

Modern infrastructure systems separate policy from execution.

Examples include:

System Control Plane Data Plane
Kubernetes API Server Pods
SDN Network Controller Switches
eBPF User-Space Loader Kernel Program
Service Mesh Management Layer Proxy Layer
This Project F# Generator Kernel Module

Traditional kernel code:

kmalloc(1024, GFP_KERNEL);
kmalloc(4096, GFP_KERNEL);

Control-plane driven approach:

Allocation Intent
        ↓
Kernel Execution

The control plane decides what should happen.

The data plane performs the actual work.


High-Level Architecture

┌──────────────────────────────────────────┐
│              CONTROL PLANE               │
│                                          │
│  F# Generator                            │
│  Allocation Definitions                  │
│  Configuration Generation                │
└───────────────────┬──────────────────────┘
                    │
                    ▼
            alloc_plan.txt
                    │
                    ▼
┌──────────────────────────────────────────┐
│               DATA PLANE                 │
│                                          │
│ Linux Kernel Module                      │
│ kmalloc()                                │
│ Allocation Tracking                      │
│ Resource Management                      │
│ kfree()                                  │
└───────────────────┬──────────────────────┘
                    │
                    ▼
┌──────────────────────────────────────────┐
│               KERNEL HEAP                │
│                                          │
│ Actual Physical Allocation               │
└──────────────────────────────────────────┘

This separation creates a cleaner mental model and mirrors patterns found in large-scale systems.


User-Space Input

Memory requests are provided using module parameters.

sudo insmod kmalloc_data_plane.ko \
names="buffer,big_buffer" \
values=1024,4096

Internally:

names[]  = [buffer, big_buffer]
values[] = [1024, 4096]
count    = 2

These arrays become allocation instructions.


Allocation Pipeline

During initialization:

for (i = 0; i < count; i++)
{
    buffers[i] = kmalloc(values[i], GFP_KERNEL);
}

Conceptually:

buffer      -> kmalloc(1024)
big_buffer  -> kmalloc(4096)

Each allocation produces a pointer to kernel memory.


Memory Lifecycle

The entire project revolves around memory ownership and lifecycle management.

Request Memory
      │
      ▼
kmalloc()
      │
      ▼
Kernel Heap
      │
      ▼
Store Pointer
      │
      ▼
Use Memory
      │
      ▼
kfree()
      │
      ▼
Memory Returned

Understanding this lifecycle is one of the most important skills in systems programming.


Kernel Heap Visualization

After allocation:

buffers[0]
      │
      ▼
0xA100
+----------------------+
| buffer               |
| 1024 bytes           |
+----------------------+

buffers[1]
      │
      ▼
0xB200
+----------------------+
| big_buffer           |
| 4096 bytes           |
+----------------------+

The addresses are returned by the kernel allocator and stored inside tracking tables.


Allocation Tracking Layer

The kernel module maintains ownership information.

void *buffers[MAX_BUFFERS];
size_t sizes[MAX_BUFFERS];

Runtime state:

buffers[0] -> 0xA100
buffers[1] -> 0xB200

sizes[0] = 1024
sizes[1] = 4096

This effectively acts as a miniature memory manager.


Why Use void*?

The tracking array is intentionally generic.

void *buffers[MAX_BUFFERS];

A void* can point to any type of memory.

Examples:

Memory Type Example
Raw Bytes Buffers
Structures Kernel Objects
Strings Character Arrays
Device Data Driver Memory
Network Data Packet Buffers

This makes the tracking subsystem reusable.


Understanding kmalloc()

Kernel memory allocation occurs through:

void *ptr = kmalloc(1024, GFP_KERNEL);

Parameters:

Parameter Purpose
1024 Allocation Size
GFP_KERNEL Standard Allocation Flag

Unlike user-space malloc(), kernel allocations use GFP flags to control allocator behavior.


Module Runtime State

After initialization:

┌────────────────────────────────────┐
│ buffers[]                          │
├────────────────────────────────────┤
│ [0] -> 0xA100                      │
│ [1] -> 0xB200                      │
└────────────────────────────────────┘

┌────────────────────────────────────┐
│ sizes[]                            │
├────────────────────────────────────┤
│ [0] -> 1024                        │
│ [1] -> 4096                        │
└────────────────────────────────────┘

The module now owns these allocations.


Cleanup Phase

When the module unloads:

sudo rmmod kmalloc_data_plane

Cleanup executes:

kfree(buffers[i]);

Visualization:

Before

0xA100 -> Allocated
0xB200 -> Allocated

After

0xA100 -> Freed
0xB200 -> Freed

Memory ownership returns to the kernel allocator.


The Dangling Pointer Problem

Many developers incorrectly assume that a pointer disappears after kfree().

Example:

kfree(ptr);

Reality:

Before Free

ptr
 │
 ▼
0xA100
+------------+
| Valid Data |
+------------+

After Free:

ptr
 │
 ▼
0xA100

Memory Already Released

The pointer still exists.

The memory does not.

This situation is known as a Dangling Pointer.


Why Dangling Pointers Matter

Accessing freed memory may cause:

Problem Impact
Garbage Reads Corrupted Results
Use-After-Free Security Vulnerability
Memory Corruption Undefined Behavior
Kernel Panic System Crash

Many real-world kernel vulnerabilities originate from stale pointer usage.


Defensive Programming

A common defensive pattern:

kfree(ptr);
ptr = NULL;

Result:

ptr -> NULL

The stale address is removed.

This significantly reduces accidental use-after-free bugs.


From Control Plane to DSL

The current implementation uses a lightweight control plane.

Current flow:

F# Generator
      ↓
Allocation Plan
      ↓
Kernel Module
      ↓
kmalloc()

However, the same idea can evolve into a complete compiler pipeline.

Future architecture:

DSL
 ↓
Lexer
 ↓
Parser
 ↓
AST
 ↓
Semantic Analyzer
 ↓
Intermediate Representation
 ↓
Verification Passes
 ↓
Optimization Passes
 ↓
Code Generator
 ↓
Linux Kernel Module

At that point, the control plane becomes a true compiler.


Control Plane vs Compiler vs Pure C

These approaches solve different problems.

Approach Goal
Pure C Direct Kernel Development
Control Plane Runtime Decisions
DSL Compiler Automated Code Generation

Pure C

Kernel Code
     ↓
Kernel Module

Control Plane

F#
 ↓
Configuration
 ↓
Kernel Module

Compiler

DSL
 ↓
IR
 ↓
Generated C
 ↓
Kernel Module

Each teaches a different layer of systems engineering.


Build Workflow

Generate configuration:

dotnet fsi control_plane.fsx
cp alloc_plan.txt /tmp/

Build:

make clean
make

Sign module:

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

Load:

sudo insmod kmalloc_data_plane.ko \
names="buffer,big_buffer" \
values=1024,4096

Inspect logs:

dmesg | tail

Unload:

sudo rmmod kmalloc_data_plane

Key Takeaways

  • kmalloc() allocates memory from the kernel heap.
  • kfree() releases memory ownership.
  • Pointers survive after memory is freed.
  • Dangling pointers are a major source of bugs.
  • Control Plane / Data Plane separation improves architectural clarity.
  • The same concepts can evolve into DSLs, IRs, and compiler pipelines.
  • Memory ownership and lifetime management are core systems-programming skills.

Conclusion

What started as a simple kernel memory allocation experiment evolved into a broader exploration of systems architecture.

The project demonstrates how concepts from distributed systems, compiler design, and kernel development can intersect in a surprisingly small codebase.

Whether you're interested in:

  • Linux Kernel Development
  • Memory Management
  • Systems Programming
  • DSL Design
  • Compiler Construction
  • Control Plane Architectures

the core lesson remains the same:

Correct ownership and lifetime management are the foundation of reliable systems.


Repository

GitHub Repository:

https://github.com/aj333git/linux\_kernel\_kmalloc\_f2