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



