Why kmalloc Is the Secret Weapon for Fast Kernel Memory Allocation
This article explains how the Linux kernel's kmalloc function provides fast, contiguous physical memory allocation using the slab allocator, covering its API, internal mechanisms, allocation flags, memory management strategies, common pitfalls, and practical kernel module examples for developers.
1. What is kmalloc?
Anyone who develops Linux kernel code has certainly encountered the problem of needing a memory buffer for drivers that is physically contiguous, fast to allocate, and leaves minimal fragmentation. The kernel already provides a "handy assistant" – the generic memory pool kmalloc.
Calling kmalloc with a size and allocation flags quickly gives you the desired physically contiguous memory without the hassle of vmalloc or manual page handling. It works for temporary driver data or module data structures, offers high allocation and release efficiency thanks to the slab allocator, and automatically reduces memory fragmentation.
If you are still struggling with kernel memory allocation or looking for a universal, hassle‑free memory tool, the following kmalloc content is a must‑read – from usage to why it is great.
1.1 kmalloc Overview
kmalloc is a function provided by the Linux kernel for allocating physically contiguous memory in kernel space. It is defined in <linux/slab.h> with the prototype void *kmalloc(size_t size, gfp_t flags);. The size argument specifies the number of bytes to allocate, while flags is a set of bits that control allocation behavior, such as whether sleeping is allowed or which memory zone to allocate from. It is similar to the user‑space malloc, but kmalloc allocates physically contiguous memory, which is crucial for devices that need direct memory access (DMA), whereas user‑space malloc does not guarantee contiguity.
1.2 kmalloc Memory Allocation Mechanism
The kmalloc allocation mechanism relies on two key data structures: kmem_cache and slab.
kmem_cache manages a cache of objects of the same size (a slab cache). It stores important members such as buffer_size, which records the size of each object – like a warehouse manager knowing the dimensions of each cargo.
The num member indicates how many objects each slab contains – similar to knowing how many items fit on a shelf.
slab is the actual memory carrier, composed of one or more contiguous physical pages. A slab can be in one of three states, each managed by a separate linked list:
Full list : all objects are allocated – the shelf is completely full.
Partial list : some objects are allocated, some are free – the shelf is partially filled.
Empty list : no objects are allocated – the shelf is empty.
When allocating memory, kmalloc first checks the partial list for a suitable slab, then the empty list, and finally requests new pages from the system if needed.
1.3 kmalloc Memory Allocation Process
Search the partial list: kmalloc looks for a slab with free objects. If found, it allocates an object from that slab.
If not found, search the empty list: kmalloc takes a slab from the empty list, moves it to the partial list, and allocates an object.
If still no slab is available, request a new memory page from the system, create a new slab, add it to the partial list, and allocate an object.
1.4 Allocation Flags Impact
Allocation context flags : GFP_KERNEL allows the allocating process to sleep if memory is low – suitable for process context. GFP_ATOMIC forbids sleeping and is used in interrupt or other atomic contexts.
Physical memory characteristic flags : GFP_DMA requests memory from the DMA‑capable zone, while GFP_HIGHMEM allows allocation from high memory. These flags let kmalloc satisfy specific hardware requirements.
1.5 kmalloc Memory Release Mechanism
The kfree function releases memory allocated by kmalloc. It marks the memory block as free and returns it to the appropriate slab list. If a slab becomes completely free, it moves to the empty list; otherwise, it stays in the partial list.
Frequent allocation and release can cause fragmentation, but the slab allocator mitigates this by allocating fixed‑size objects and managing them in the three lists, thus keeping fragmentation low and performance high.
2. kmalloc Working Principle
2.1 Core Mechanism of the Slab Allocator
The slab allocator is the core component of kmalloc. It pre‑allocates a series of fixed‑size memory blocks (slabs), similar to a warehouse divided into sections for specific item sizes. Objects are allocated from the appropriate slab cache.
The allocator maintains three lists:
Full list: slabs where all objects are allocated.
Partial list: slabs with some free objects.
Empty list: slabs with no allocated objects.
This structure enables fast allocation without searching the entire memory space.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define OBJECT_SIZE 64
#define SLAB_SIZE (4096 / OBJECT_SIZE)
typedef struct { char data[OBJECT_SIZE]; } Object;
typedef struct Slab { Object objects[SLAB_SIZE]; int free_count; struct Slab* next; } Slab;
typedef struct { Slab* full_list; Slab* partial_list; Slab* empty_list; int total_objects; int used_objects; } SlabAllocator;
void slab_allocator_init(SlabAllocator* allocator) { memset(allocator, 0, sizeof(SlabAllocator)); printf("Slab allocator initialized
"); }
static Slab* create_slab() { Slab* slab = (Slab*)malloc(sizeof(Slab)); if (!slab) { printf("Failed to create slab
"); return NULL; } memset(slab, 0, sizeof(Slab)); slab->free_count = SLAB_SIZE; slab->next = NULL; printf("Created new slab, can hold %d objects
", SLAB_SIZE); return slab; }
void* slab_alloc(SlabAllocator* allocator) {
Slab* slab = NULL;
if (allocator->partial_list) { slab = allocator->partial_list; printf("Allocating from partial list
"); }
else if (allocator->empty_list) { slab = allocator->empty_list; allocator->empty_list = slab->next; slab->next = allocator->partial_list; allocator->partial_list = slab; printf("Allocating from empty list
"); }
else { slab = create_slab(); if (!slab) return NULL; slab->next = allocator->partial_list; allocator->partial_list = slab; allocator->total_objects += SLAB_SIZE; }
for (int i = 0; i < SLAB_SIZE; i++) {
if (slab->objects[i].data[0] == 0) {
slab->objects[i].data[0] = 1;
slab->free_count--;
allocator->used_objects++;
if (slab->free_count == 0) { allocator->partial_list = slab->next; slab->next = allocator->full_list; allocator->full_list = slab; printf("Slab is now full, moved to full list
"); }
return &slab->objects[i];
}
}
return NULL;
}
void slab_free(SlabAllocator* allocator, void* ptr) {
if (!ptr) return;
Slab* slab = (Slab*)((unsigned long)ptr & ~(sizeof(Slab) - 1));
int index = ((unsigned long)ptr - (unsigned long)slab) / sizeof(Object);
if (index < 0 || index >= SLAB_SIZE) { printf("Invalid pointer
"); return; }
memset(&slab->objects[index], 0, sizeof(Object));
slab->free_count++;
allocator->used_objects--;
if (slab->free_count == 1) { move_slab(&allocator->full_list, &allocator->partial_list, slab); }
else if (slab->free_count == SLAB_SIZE) { move_slab(&allocator->partial_list, &allocator->empty_list, slab); }
}2.2 Detailed Allocation Steps
When the kernel calls kmalloc, it follows a strict sequence:
Search the partial list for a slab with free objects.
If not found, search the empty list, move the slab to the partial list, and allocate.
If still unavailable, request a new memory page, create a slab, add it to the partial list, and allocate.
2.3 Memory Release Steps
When an object is no longer needed, kfree marks the block as free and moves the slab to the appropriate list based on its new state.
3. kmalloc Application Scenarios
3.1 Driver Development
In device drivers, kmalloc is widely used. For example, a network driver allocates a buffer for each received packet using kmalloc, ensuring fast, contiguous memory for DMA.
#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/netdevice.h>
void netcard_receive(struct net_device *dev) {
struct sk_buff *skb;
skb = dev_alloc_skb(ETH_FRAME_LEN);
if (!skb) { return; }
// Process packet and store data in skb
// ...
kfree_skb(skb);
}kmalloc provides fast allocation of contiguous memory, reduces fragmentation, and ensures stable driver operation.
3.2 Kernel Module Development
Kernel modules use kmalloc to allocate memory for data structures such as inodes. Example from an ext2 module:
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/slab.h>
struct inode *ext2_create_inode(struct super_block *sb) {
struct inode *inode;
inode = kmalloc(sizeof(struct inode), GFP_KERNEL);
if (!inode) { return NULL; }
// Initialize inode
// ...
return inode;
}kmalloc allows modules to dynamically obtain and release memory at runtime, enhancing flexibility.
3.3 Memory Buffer Management
In network stacks, kmalloc is used to allocate small buffers for each packet:
#include <linux/module.h>
#include <linux/skbuff.h>
#include <net/sock.h>
int network_protocol_process(struct sk_buff *skb) {
char *buffer = kmalloc(skb->len, GFP_ATOMIC);
if (!buffer) { return -ENOMEM; }
memcpy(buffer, skb->data, skb->len);
// Process data
// ...
kfree(buffer);
return 0;
}The slab allocator ensures fast allocation and release, minimizing fragmentation.
3.4 Unique Advantages of kmalloc
(1) Allocation Speed : Because slabs are pre‑created, kmalloc avoids complex page table updates, making it ideal for high‑throughput scenarios like network packet handling.
(2) Physical Contiguity : Essential for DMA devices; kmalloc guarantees contiguous physical addresses, enabling efficient data transfer.
(3) Reduced Fragmentation : Fixed‑size object allocation prevents the memory fragmentation common in general allocators.
4. kmalloc Usage Considerations
4.1 Size Limitations
kmalloc has an upper size limit that varies by architecture and kernel configuration. For portable code, avoid allocating more than 128 KB because kmalloc does not allocate directly from the page allocator for large requests. For larger allocations, use vmalloc.
4.2 Choosing Allocation Flags
GFP_KERNELmay sleep and is safe in process context. GFP_ATOMIC never sleeps and must be used in interrupt or other atomic contexts. Using the wrong flag can cause deadlocks.
4.3 Memory Release
Every kmalloc must be paired with a corresponding kfree; otherwise memory leaks will eventually exhaust system memory.
5. kmalloc Practical Module Example
5.1 Buffer Management Module
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/printk.h>
#include <linux/types.h>
struct kmalloc_buffer {
void *addr;
size_t size;
char name[32];
};
static struct kmalloc_buffer buf1;
static struct kmalloc_buffer buf2;
static int __init kmalloc_buffer_init(struct kmalloc_buffer *buf, const char *name, size_t size) {
if (!buf || !name || size == 0) {
pr_err("[kmalloc-demo] Invalid buffer init parameters
");
return -EINVAL;
}
buf->addr = kmalloc(size, GFP_KERNEL);
if (!buf->addr) {
pr_err("[kmalloc-demo] %s allocation failed (%zu bytes)
", name, size);
return -ENOMEM;
}
buf->size = size;
strncpy(buf->name, name, sizeof(buf->name) - 1);
buf->name[sizeof(buf->name) - 1] = '\0';
memset(buf->addr, 0, size);
pr_info("[kmalloc-demo] %s init success: addr=0x%pK, size=%zu
", buf->name, buf->addr, buf->size);
return 0;
}
static void write_buffer_data(struct kmalloc_buffer *buf, const char *data, size_t data_len) {
if (!buf || !buf->addr || !data) {
pr_err("[kmalloc-demo] Write failed: invalid parameters
");
return;
}
size_t write_len = min(data_len, buf->size - 1);
memcpy(buf->addr, data, write_len);
((char *)buf->addr)[write_len] = '\0';
pr_info("[kmalloc-demo] Written to %s: %s (actual %zu bytes)
", buf->name, (char *)buf->addr, write_len);
}
static void kmalloc_buffer_free(struct kmalloc_buffer *buf) {
if (!buf) return;
if (buf->addr) {
kfree(buf->addr);
pr_info("[kmalloc-demo] %s memory freed: original addr=0x%pK
", buf->name, buf->addr);
buf->addr = NULL;
buf->size = 0;
}
}
static int __init kmalloc_demo_init(void) {
int ret;
pr_info("[kmalloc-demo] Module loading...
");
ret = kmalloc_buffer_init(&buf1, "uart_data_buf", 1024);
if (ret) return ret;
ret = kmalloc_buffer_init(&buf2, "eth_frame_buf", 4096);
if (ret) { kmalloc_buffer_free(&buf1); return ret; }
write_buffer_data(&buf1, "UART hardware data: 0x12 0x34 0x56", 32);
write_buffer_data(&buf2, "Ethernet frame: Destination=00:11:22:33:44:55, Source=66:77:88:99:AA:BB", 64);
pr_info("[kmalloc-demo] Module loaded successfully!
");
return 0;
}
static void __exit kmalloc_demo_exit(void) {
pr_info("[kmalloc-demo] Module unloading...
");
kmalloc_buffer_free(&buf1);
kmalloc_buffer_free(&buf2);
pr_info("[kmalloc-demo] Module unloaded!
");
}
module_init(kmalloc_demo_init);
module_exit(kmalloc_demo_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("kmalloc Memory Pool Demo Module");
MODULE_AUTHOR("Kernel Developer");
MODULE_VERSION("1.0");This module demonstrates proper kmalloc usage: parameter validation, error handling, safe memory release, and logging with pr_info / pr_err. It allocates two buffers (1 KB and 4 KB), writes sample data, and frees them on module exit.
6. Compilation and Verification
6.1 Makefile
# Path to kernel source (adjust as needed)
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
MODULE_NAME := kmalloc_demo
obj-m += $(MODULE_NAME).o
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(PWD) clean6.2 Build, Load, and Test
Run make to build kmalloc_demo.ko.
Load the module with sudo insmod kmalloc_demo.ko and check logs via dmesg | grep "[kmalloc-demo]". Expected output shows successful initialization, data writes, and addresses.
Unload with sudo rmmod kmalloc_demo and verify that the free messages appear in dmesg.
These steps confirm that kmalloc provides fast, safe, and contiguous memory allocation for kernel components.
Deepin Linux
Research areas: Windows & Linux platforms, C/C++ backend development, embedded systems and Linux kernel, etc.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
