Memory Allocation Methods in Linux Kernel and User Space Development
Memory allocation is a fundamental aspect of software development, especially when working with hardware interfaces like USB, SPI, I2C, or GPIO. In this article, we will explore different memory allocation methods available in both the Linux kernel and user space applications, providing concrete examples for each.
Linux Kernel Memory Allocation Methods
1. kmalloc/kfree
kmalloc is used to allocate small chunks of memory within the kernel. It does not initialize the allocated memory, which can be done using memset.
Example:
#include <linux/usb.h>
#include <linux/module.h>
static int usb_probe(struct usb_interface *interface, const struct usb_device_id *id)
{
char *buffer;
buffer = kmalloc(64, GFP_KERNEL); // Allocate 64 bytes
if (!buffer) return -ENOMEM;
memset(buffer, 0, 64); // Initialize buffer
// Use buffer...
kfree(buffer); // Free the allocated memory
return 0;
}
2. kzalloc/kfree
kzalloc combines allocation and zero-initialization in one step, making it simpler than using kmalloc followed by memset.
Example:
static int spi_probe(struct spi_device *spi)
{
char *buffer;
buffer = kzalloc(64, GFP_KERNEL); // Allocate and zero 64 bytes
if (!buffer) return -ENOMEM;
// Use buffer...
kfree(buffer); // Free the allocated memory
return 0;
}
3. devm_kzalloc
devm_kzalloc is similar to kzalloc, but it ties the allocated memory to the device lifecycle, automatically freeing the memory when the device is removed.
Example:
static int i2c_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
char *buffer;
buffer = devm_kzalloc(&client->dev, 64, GFP_KERNEL); // Allocate and zero 64 bytes
if (!buffer) return -ENOMEM;
// Use buffer...
// No need to free explicitly
return 0;
}
4. vmalloc/vfree
vmalloc allocates virtually contiguous memory, useful for larger allocations that cannot be satisfied by kmalloc.
Example:
static int gpio_probe(struct platform_device *pdev)
{
char *buffer;
buffer = vmalloc(1024 * 1024); // Allocate 1MB
if (!buffer) return -ENOMEM;
// Use buffer...
vfree(buffer); // Free the allocated memory
return 0;
}
5. dma_alloc_coherent/dma_free_coherent
These functions are used for allocating memory suitable for DMA operations, ensuring that the memory is both physically and virtually contiguous.
Example:
static int usb_dma_probe(struct usb_interface *interface, const struct usb_device_id *id)
{
void *buffer;
dma_addr_t dma_handle;
buffer = dma_alloc_coherent(&interface->dev, 64, &dma_handle, GFP_KERNEL);
if (!buffer) return -ENOMEM;
// Use buffer...
dma_free_coherent(&interface->dev, 64, buffer, dma_handle); // Free the allocated memory
return 0;
}
Recommended by LinkedIn
User Space Memory Allocation Methods
1. malloc/free
In user space, malloc is used to allocate memory dynamically, and free is used to release it.
Example (USB):
#include <stdlib.h>
int main() {
unsigned char *buffer;
buffer = (unsigned char *)malloc(64); // Allocate 64 bytes
if (!buffer) return -1;
// Use buffer...
free(buffer); // Free the allocated memory
return 0;
}
2. calloc/realloc
calloc initializes the allocated memory to zero, while realloc changes the size of previously allocated memory.
Example (SPI):
#include <stdlib.h>
int main() {
unsigned char *buffer;
buffer = (unsigned char *)calloc(64, sizeof(unsigned char)); // Allocate and zero 64 bytes
if (!buffer) return -1;
// Reallocate buffer to 128 bytes
buffer = realloc(buffer, 128);
if (!buffer) return -1;
// Use buffer...
free(buffer); // Free the allocated memory
return 0;
}
3. mmap/munmap
mmap maps files or devices into memory, allowing direct access to device memory or file contents from user space. This is particularly useful for interacting with hardware peripherals such as GPIO, I2C, or SPI.
Understanding mmap and munmap
Concrete Example: Using mmap with GPIO on Raspberry Pi
Let's consider an example where we use mmap to interact directly with GPIO registers on a Raspberry Pi.
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#define BCM2708_PERI_BASE 0x3F000000 // Base address for peripherals on Raspberry Pi
#define GPIO_BASE (BCM2708_PERI_BASE + 0x200000) // GPIO controller base address
#define BLOCK_SIZE (4*1024)
int main() {
int mem_fd;
void *gpio_map;
// Open /dev/mem
if ((mem_fd = open("/dev/mem", O_RDWR|O_SYNC)) < 0) {
perror("Failed to open /dev/mem");
return -1;
}
// mmap GPIO
gpio_map = mmap(
NULL, // Any address will do
BLOCK_SIZE, // Map length
PROT_READ|PROT_WRITE, // Enable reading & writing to mapped memory
MAP_SHARED, // Shared with other processes
mem_fd, // File to map
GPIO_BASE // Offset to GPIO peripheral
);
close(mem_fd); // No longer need the file descriptor after mapping
if (gpio_map == MAP_FAILED) {
perror("mmap error");
return -1;
}
// Now use gpio_map as if it were a pointer to the GPIO registers
volatile unsigned int *gpio = (volatile unsigned int *)gpio_map;
// Example: Set GPIO pin 18 as output
*(gpio + (18/10)) &= ~(7 << ((18%10)*3)); // Clear bits
*(gpio + (18/10)) |= (1 << ((18%10)*3)); // Set mode to output
// Set GPIO pin 18 high
*(gpio + 7) = 1 << 18;
// Clean up
munmap(gpio_map, BLOCK_SIZE);
return 0;
}
Explanation
Why Use mmap?
Using mmap for hardware interaction provides several advantages:
By understanding how to use mmap and munmap, you can create more efficient and cleaner user-space programs that interact directly with hardware peripherals.
Each of these methods has its specific use cases and benefits depending on the requirements of your application or driver. Understanding these differences helps in choosing the right tool for the job, whether you're working in the kernel or user space.
Thank you for sharing
MMAP is great tool used by some DB internals, fault handlers, DMA by devices...