Memory Allocation Methods in Linux Kernel and User Space Development

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;
}
        

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

  • mmap: Maps files or devices into memory, treating them as part of your process’s address space.
  • munmap: Unmaps the memory region previously mapped by mmap, releasing resources.

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

  1. Opening /dev/mem: The program starts by opening /dev/mem to gain access to physical memory.
  2. Mapping GPIO Peripheral:
  3. Interacting with GPIO Registers:
  4. Unmapping Memory:

Why Use mmap?

Using mmap for hardware interaction provides several advantages:

  • Performance: Direct memory access can be faster than using ioctl calls or similar methods.
  • Simplicity: It simplifies the code by treating hardware registers as if they were part of your process’s address space.
  • Flexibility: You can easily map different parts of the address space based on your needs.

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

Like
Reply

MMAP is great tool used by some DB internals, fault handlers, DMA by devices...

To view or add a comment, sign in

More articles by David Zhu

Others also viewed

Explore content categories