driver-know-hows

device driver related stuff

View on GitHub

Chapter 4: Memory Management

Table of Contents

  1. Kernel Memory Architecture
  2. Memory Allocation Functions
  3. kmalloc and kfree
  4. vmalloc and vfree
  5. Memory Pools and Slabs
  6. Page Allocation
  7. DMA-Capable Memory
  8. Per-CPU Variables
  9. Memory Mapping Internals

Kernel Memory Architecture

Virtual Memory Layout

User Space (0x0000000000000000 - 0x00007fffffffffff)  [128 TB on x86-64]
├── User process memory
├── Heap
├── Stack
└── Shared libraries

Kernel Space (0xffff800000000000 - 0xffffffffffffffff) [128 TB on x86-64]
├── Direct mapping of all physical memory
├── vmalloc area
├── Kernel code and data
├── Module memory
└── Per-CPU data

Memory Zones

The kernel divides physical memory into zones:

/*
 * Memory Zones:
 * 
 * ZONE_DMA      - Low memory for DMA (0-16MB on x86)
 * ZONE_DMA32    - DMA memory accessible by 32-bit devices (0-4GB)
 * ZONE_NORMAL   - Normal memory (directly mapped)
 * ZONE_HIGHMEM  - High memory (>896MB on 32-bit systems, not on 64-bit)
 * ZONE_MOVABLE  - Movable memory for hot-plug
 */

Physical vs Virtual Memory

Virtual Address (used by driver)
    ↓
    Page Table (MMU translation)
    ↓
Physical Address (actual RAM)

Important:
- Kernel virtual addresses are NOT physical addresses
- Use virt_to_phys() / phys_to_virt() for conversion
- DMA requires physical addresses

Memory Allocation Functions

Overview of Allocation Functions

/*
 * Choosing the right allocation function:
 * 
 * Small allocations (<PAGE_SIZE):
 *   → Use kmalloc() - Fast, physically contiguous
 * 
 * Large allocations (>PAGE_SIZE):
 *   → Use vmalloc() - May not be physically contiguous
 *   → Or use __get_free_pages() for physically contiguous
 * 
 * DMA buffers:
 *   → Use dma_alloc_coherent() - DMA-safe memory
 * 
 * Frequent same-size allocations:
 *   → Use kmem_cache (slab allocator) - Efficient
 * 
 * Very large allocations:
 *   → Consider using vmalloc() or allocating pages directly
 */

GFP Flags (Get Free Pages)

#include <linux/gfp.h>

/*
 * GFP flags control allocation behavior
 * 
 * Most commonly used:
 */

/* GFP_KERNEL - Normal allocation, can sleep */
/* Use in: Process context, when sleeping is OK */
void *buffer = kmalloc(size, GFP_KERNEL);

/* GFP_ATOMIC - Allocation that cannot sleep */
/* Use in: Interrupt handlers, atomic context */
void *buffer = kmalloc(size, GFP_ATOMIC);

/* GFP_DMA - Allocate from DMA zone */
void *dma_buffer = kmalloc(size, GFP_DMA);

/* GFP_USER - Allocate user space memory */
void *user_mem = kmalloc(size, GFP_USER);

/*
 * Flag modifiers (can be OR'd):
 * 
 * __GFP_ZERO      - Zero the allocated memory
 * __GFP_NOWARN    - Suppress allocation failure warnings
 * __GFP_NORETRY   - Don't retry if allocation fails
 * __GFP_RETRY_MAYFAIL - May retry but can fail
 * __GFP_HIGH      - High priority allocation
 * __GFP_IO        - Can perform I/O operations
 * __GFP_FS        - Can call filesystem operations
 */

/* Common combinations: */
GFP_KERNEL = __GFP_RECLAIM | __GFP_IO | __GFP_FS
GFP_ATOMIC = __GFP_HIGH | __GFP_ATOMIC
GFP_NOIO   = __GFP_RECLAIM  /* Can sleep but no I/O */
GFP_NOFS   = __GFP_RECLAIM | __GFP_IO  /* Can sleep & I/O but no FS */

/*
 * Example: Allocate and zero memory
 */
void *buffer = kmalloc(size, GFP_KERNEL | __GFP_ZERO);
/* Or use kzalloc() which is equivalent */
void *buffer = kzalloc(size, GFP_KERNEL);

kmalloc and kfree

kmalloc - Kernel Memory Allocator

#include <linux/slab.h>

/*
 * kmalloc - Allocate kernel memory
 * 
 * @size: Number of bytes to allocate
 * @flags: GFP allocation flags
 * 
 * Return: Pointer to allocated memory, or NULL on failure
 * 
 * Properties:
 * - Returns physically contiguous memory
 * - Fast (uses slab allocator underneath)
 * - Limited by physically contiguous memory availability
 * - Typical maximum: 4MB (architecture dependent)
 * - Memory is from low memory (directly mapped)
 */
void *kmalloc(size_t size, gfp_t flags);

/*
 * kzalloc - Allocate and zero memory
 * 
 * Equivalent to: kmalloc(size, flags | __GFP_ZERO)
 */
void *kzalloc(size_t size, gfp_t flags);

/*
 * kfree - Free memory allocated by kmalloc/kzalloc
 * 
 * @ptr: Pointer returned by kmalloc/kzalloc
 * 
 * Safe to call with NULL pointer (does nothing)
 */
void kfree(const void *ptr);

/*
 * kcalloc - Allocate array and zero
 * 
 * @n: Number of elements
 * @size: Size of each element
 * @flags: GFP flags
 * 
 * Checks for overflow: n * size
 */
void *kcalloc(size_t n, size_t size, gfp_t flags);

/*
 * krealloc - Resize previously allocated memory
 * 
 * @p: Previously allocated memory
 * @new_size: New size
 * @flags: GFP flags
 * 
 * Return: Pointer to resized memory (may have moved)
 */
void *krealloc(const void *p, size_t new_size, gfp_t flags);

/*
 * ksize - Get actual size of allocated memory
 * 
 * @ptr: Pointer to allocated memory
 * 
 * Return: Actual allocated size (may be larger than requested)
 */
size_t ksize(const void *ptr);

kmalloc Examples

/*
 * Example 1: Simple allocation
 */
static int simple_alloc_example(void)
{
    char *buffer;
    
    /* Allocate 1024 bytes */
    buffer = kmalloc(1024, GFP_KERNEL);
    if (!buffer) {
        pr_err("Failed to allocate memory\n");
        return -ENOMEM;
    }
    
    /* Use the buffer */
    strcpy(buffer, "Hello, kernel!");
    pr_info("Buffer contains: %s\n", buffer);
    
    /* Free the buffer */
    kfree(buffer);
    
    return 0;
}

/*
 * Example 2: Allocating structures
 */
struct my_device {
    int id;
    char name[32];
    void *data;
};

static struct my_device *alloc_device(int id, const char *name)
{
    struct my_device *dev;
    
    /* Allocate and zero structure */
    dev = kzalloc(sizeof(*dev), GFP_KERNEL);
    if (!dev)
        return NULL;
    
    /* Initialize fields */
    dev->id = id;
    strncpy(dev->name, name, sizeof(dev->name) - 1);
    
    /* Allocate sub-buffer */
    dev->data = kmalloc(4096, GFP_KERNEL);
    if (!dev->data) {
        kfree(dev);
        return NULL;
    }
    
    return dev;
}

static void free_device(struct my_device *dev)
{
    if (dev) {
        kfree(dev->data);
        kfree(dev);
    }
}

/*
 * Example 3: Array allocation
 */
static int array_alloc_example(void)
{
    int *array;
    int i;
    int count = 100;
    
    /* Allocate array of integers */
    array = kcalloc(count, sizeof(int), GFP_KERNEL);
    if (!array)
        return -ENOMEM;
    
    /* Initialize array */
    for (i = 0; i < count; i++)
        array[i] = i * i;
    
    /* Use array */
    pr_info("array[10] = %d\n", array[10]);
    
    /* Free array */
    kfree(array);
    
    return 0;
}

/*
 * Example 4: Resizing allocation
 */
static int resize_example(void)
{
    char *buffer;
    char *new_buffer;
    
    /* Initial allocation */
    buffer = kmalloc(1024, GFP_KERNEL);
    if (!buffer)
        return -ENOMEM;
    
    strcpy(buffer, "Initial data");
    
    /* Resize to 2048 bytes */
    new_buffer = krealloc(buffer, 2048, GFP_KERNEL);
    if (!new_buffer) {
        kfree(buffer);  /* Original buffer still valid */
        return -ENOMEM;
    }
    
    /* Use resized buffer (original data preserved) */
    pr_info("Resized buffer: %s\n", new_buffer);
    
    /* Free (new_buffer is now the valid pointer) */
    kfree(new_buffer);
    
    return 0;
}

/*
 * Example 5: Error handling best practices
 */
static int proper_error_handling(void)
{
    void *buf1 = NULL, *buf2 = NULL, *buf3 = NULL;
    int ret = 0;
    
    buf1 = kmalloc(1024, GFP_KERNEL);
    if (!buf1) {
        ret = -ENOMEM;
        goto out;
    }
    
    buf2 = kmalloc(2048, GFP_KERNEL);
    if (!buf2) {
        ret = -ENOMEM;
        goto out;
    }
    
    buf3 = kmalloc(4096, GFP_KERNEL);
    if (!buf3) {
        ret = -ENOMEM;
        goto out;
    }
    
    /* Use buffers... */
    
out:
    /* Clean up (kfree handles NULL) */
    kfree(buf3);
    kfree(buf2);
    kfree(buf1);
    return ret;
}

vmalloc and vfree

vmalloc - Virtual Memory Allocator

#include <linux/vmalloc.h>

/*
 * vmalloc - Allocate virtually contiguous memory
 * 
 * @size: Number of bytes to allocate
 * 
 * Return: Pointer to allocated memory, or NULL on failure
 * 
 * Properties:
 * - Returns virtually contiguous memory (physically may be scattered)
 * - Slower than kmalloc (page table manipulation required)
 * - Can allocate much larger regions
 * - Memory is from high memory (not directly mapped)
 * - Cannot be used for DMA (not physically contiguous)
 * - Can sleep (uses GFP_KERNEL internally)
 */
void *vmalloc(unsigned long size);

/*
 * vzalloc - Allocate and zero virtually contiguous memory
 */
void *vzalloc(unsigned long size);

/*
 * vmalloc_user - Allocate memory for mapping to userspace
 */
void *vmalloc_user(unsigned long size);

/*
 * vfree - Free memory allocated by vmalloc
 * 
 * @addr: Address returned by vmalloc
 */
void vfree(const void *addr);

/*
 * virt_to_phys - Convert virtual address to physical (for kmalloc)
 * Cannot be used reliably for vmalloc addresses!
 */
unsigned long virt_to_phys(volatile void *address);

/*
 * phys_to_virt - Convert physical address to virtual
 */
void *phys_to_virt(unsigned long address);

vmalloc vs kmalloc

/*
 * Comparison: kmalloc vs vmalloc
 */

/* kmalloc:
 * + Physically contiguous (can be used for DMA)
 * + Fast allocation
 * + Direct mapping (no page table overhead)
 * - Limited size (typically max 4MB)
 * - Can fail if physical memory is fragmented
 */
void *kmalloc_example(void)
{
    void *buf;
    
    /* Good: Small allocation */
    buf = kmalloc(4096, GFP_KERNEL);  /* 1 page */
    
    /* Risky: Large allocation might fail */
    buf = kmalloc(16 * 1024 * 1024, GFP_KERNEL);  /* 16 MB */
    
    return buf;
}

/* vmalloc:
 * + Can allocate very large regions
 * + Won't fail due to fragmentation
 * + Useful for large buffers
 * - NOT physically contiguous (cannot use for DMA)
 * - Slower (requires page table setup)
 * - More memory overhead (page tables)
 */
void *vmalloc_example(void)
{
    void *buf;
    
    /* Good: Large allocation */
    buf = vmalloc(100 * 1024 * 1024);  /* 100 MB */
    
    /* Bad: Small allocation (overhead not worth it) */
    buf = vmalloc(64);  /* Wasteful */
    
    return buf;
}

/*
 * Decision flowchart:
 * 
 * Need DMA? ──Yes──> Use kmalloc or dma_alloc_coherent
 *     │
 *     No
 *     │
 *     ↓
 * Size < 1 page? ──Yes──> Use kmalloc
 *     │
 *     No
 *     │
 *     ↓
 * Size < 128KB? ──Yes──> Try kmalloc, fallback to vmalloc
 *     │
 *     No
 *     │
 *     ↓
 * Use vmalloc
 */

vmalloc Examples

/*
 * Example 1: Large buffer allocation
 */
static int large_buffer_example(void)
{
    char *buffer;
    size_t size = 10 * 1024 * 1024;  /* 10 MB */
    
    /* vmalloc for large buffer */
    buffer = vmalloc(size);
    if (!buffer) {
        pr_err("Failed to allocate %zu bytes\n", size);
        return -ENOMEM;
    }
    
    pr_info("Allocated %zu bytes at %p\n", size, buffer);
    
    /* Use buffer */
    memset(buffer, 0, size);
    
    /* Free buffer */
    vfree(buffer);
    
    return 0;
}

/*
 * Example 2: Fallback allocation
 */
static void *alloc_buffer_smart(size_t size)
{
    void *buffer;
    
    /* Try kmalloc first (faster if available) */
    buffer = kmalloc(size, GFP_KERNEL | __GFP_NOWARN);
    if (buffer) {
        pr_info("Allocated %zu bytes with kmalloc\n", size);
        return buffer;
    }
    
    /* Fallback to vmalloc for large allocation */
    buffer = vmalloc(size);
    if (buffer) {
        pr_info("Allocated %zu bytes with vmalloc\n", size);
    }
    
    return buffer;
}

static void free_buffer_smart(void *buffer, size_t size)
{
    /* Determine allocation type and free appropriately */
    if (is_vmalloc_addr(buffer)) {
        vfree(buffer);
    } else {
        kfree(buffer);
    }
}

/*
 * Example 3: User-mappable buffer
 */
static void *alloc_user_buffer(size_t size)
{
    void *buffer;
    
    /* Allocate buffer that can be mapped to userspace */
    buffer = vmalloc_user(size);
    if (!buffer)
        return NULL;
    
    /* Initialize */
    memset(buffer, 0, size);
    
    return buffer;
}

Memory Pools and Slabs

Slab Allocator (kmem_cache)

The slab allocator is efficient for frequently allocating objects of the same size.

#include <linux/slab.h>

/*
 * Memory cache (slab) - For frequent same-size allocations
 * 
 * Benefits:
 * - Very fast allocation/deallocation
 * - Reduces fragmentation
 * - Can have constructor/destructor
 * - Cache-friendly (objects stay "warm")
 */

struct kmem_cache {
    /* Opaque structure managed by kernel */
};

/*
 * kmem_cache_create - Create a memory cache
 * 
 * @name: Cache name (for debugging, appears in /proc/slabinfo)
 * @size: Size of each object
 * @align: Alignment requirement (0 for default)
 * @flags: Cache flags
 * @ctor: Constructor function (called when object is allocated)
 * 
 * Return: Pointer to cache, or NULL on failure
 */
struct kmem_cache *kmem_cache_create(const char *name, 
                                     unsigned int size,
                                     unsigned int align,
                                     slab_flags_t flags,
                                     void (*ctor)(void *));

/*
 * Common flags:
 * 
 * SLAB_HWCACHE_ALIGN  - Align objects to cache line
 * SLAB_POISON         - Fill with poison pattern (debugging)
 * SLAB_RED_ZONE       - Add red zones for overflow detection
 * SLAB_PANIC          - Panic if creation fails
 * SLAB_RECLAIM_ACCOUNT - Track reclaimable objects
 */

/*
 * kmem_cache_alloc - Allocate object from cache
 * 
 * @cachep: Cache to allocate from
 * @flags: GFP allocation flags
 * 
 * Return: Pointer to allocated object, or NULL
 */
void *kmem_cache_alloc(struct kmem_cache *cachep, gfp_t flags);

/*
 * kmem_cache_free - Free object back to cache
 * 
 * @cachep: Cache object was allocated from
 * @objp: Object to free
 */
void kmem_cache_free(struct kmem_cache *cachep, void *objp);

/*
 * kmem_cache_destroy - Destroy cache
 * 
 * @cachep: Cache to destroy
 * 
 * Must free all objects before destroying cache!
 */
void kmem_cache_destroy(struct kmem_cache *cachep);

Slab Allocator Examples

/*
 * Example: Using kmem_cache for device structures
 */

struct my_packet {
    unsigned int id;
    unsigned int length;
    unsigned char data[128];
    struct list_head list;
};

static struct kmem_cache *packet_cache;

/*
 * Initialize packet cache
 */
static int packet_cache_init(void)
{
    /* Create cache for packet structures */
    packet_cache = kmem_cache_create("my_packet_cache",
                                     sizeof(struct my_packet),
                                     0,  /* Default alignment */
                                     SLAB_HWCACHE_ALIGN,  /* Cache-line aligned */
                                     NULL);  /* No constructor */
    if (!packet_cache) {
        pr_err("Failed to create packet cache\n");
        return -ENOMEM;
    }
    
    pr_info("Packet cache created\n");
    return 0;
}

/*
 * Allocate packet
 */
static struct my_packet *alloc_packet(void)
{
    struct my_packet *pkt;
    
    /* Allocate from cache */
    pkt = kmem_cache_alloc(packet_cache, GFP_KERNEL);
    if (!pkt)
        return NULL;
    
    /* Initialize packet */
    memset(pkt, 0, sizeof(*pkt));
    INIT_LIST_HEAD(&pkt->list);
    
    return pkt;
}

/*
 * Free packet
 */
static void free_packet(struct my_packet *pkt)
{
    if (pkt)
        kmem_cache_free(packet_cache, pkt);
}

/*
 * Cleanup packet cache
 */
static void packet_cache_destroy(void)
{
    /*
     * Important: Free all allocated packets before destroying cache!
     */
    kmem_cache_destroy(packet_cache);
    pr_info("Packet cache destroyed\n");
}

/*
 * Example: Using constructor
 */

struct my_object {
    spinlock_t lock;
    struct list_head list;
    int ref_count;
    void *data;
};

/* Constructor - called when object is first allocated */
static void my_object_ctor(void *obj)
{
    struct my_object *o = obj;
    
    /* Initialize fields that should always be initialized */
    spin_lock_init(&o->lock);
    INIT_LIST_HEAD(&o->list);
    o->ref_count = 0;
    o->data = NULL;
}

static struct kmem_cache *object_cache;

static int object_cache_init(void)
{
    object_cache = kmem_cache_create("my_object_cache",
                                     sizeof(struct my_object),
                                     0,
                                     SLAB_HWCACHE_ALIGN,
                                     my_object_ctor);  /* Use constructor */
    return object_cache ? 0 : -ENOMEM;
}

/*
 * Complete example with module
 */
static struct kmem_cache *my_cache;
static struct my_packet *packets[10];

static int __init cache_example_init(void)
{
    int i;
    
    /* Create cache */
    if (packet_cache_init() < 0)
        return -ENOMEM;
    
    /* Allocate some packets */
    for (i = 0; i < 10; i++) {
        packets[i] = alloc_packet();
        if (!packets[i]) {
            pr_err("Failed to allocate packet %d\n", i);
            goto cleanup;
        }
        packets[i]->id = i;
        pr_info("Allocated packet %d\n", i);
    }
    
    return 0;

cleanup:
    for (i = 0; i < 10; i++) {
        if (packets[i])
            free_packet(packets[i]);
    }
    packet_cache_destroy();
    return -ENOMEM;
}

static void __exit cache_example_exit(void)
{
    int i;
    
    /* Free all packets */
    for (i = 0; i < 10; i++) {
        if (packets[i]) {
            free_packet(packets[i]);
            pr_info("Freed packet %d\n", i);
        }
    }
    
    /* Destroy cache */
    packet_cache_destroy();
}

module_init(cache_example_init);
module_exit(cache_example_exit);

Memory Pools (mempool)

Memory pools guarantee minimum number of preallocated objects.

#include <linux/mempool.h>

/*
 * mempool - Guaranteed memory allocation
 * 
 * Use when you need to guarantee allocation success
 * Example: In critical paths where allocation failure is unacceptable
 */

typedef struct mempool_s mempool_t;

/*
 * mempool_create - Create memory pool
 * 
 * @min_nr: Minimum number of elements to preallocate
 * @alloc_fn: Allocation function
 * @free_fn: Free function
 * @pool_data: Data for allocation function
 */
mempool_t *mempool_create(int min_nr,
                         mempool_alloc_t *alloc_fn,
                         mempool_free_t *free_fn,
                         void *pool_data);

/*
 * mempool_create_slab_pool - Create pool using slab cache
 */
mempool_t *mempool_create_slab_pool(int min_nr, struct kmem_cache *cache);

/*
 * mempool_alloc - Allocate from pool
 * 
 * Guaranteed to succeed if called with GFP_WAIT
 */
void *mempool_alloc(mempool_t *pool, gfp_t gfp_mask);

/*
 * mempool_free - Return object to pool
 */
void mempool_free(void *element, mempool_t *pool);

/*
 * mempool_destroy - Destroy pool
 */
void mempool_destroy(mempool_t *pool);

/*
 * Example: Creating mempool with slab cache
 */
static struct kmem_cache *my_cache;
static mempool_t *my_pool;

static int mempool_example_init(void)
{
    /* Create slab cache */
    my_cache = kmem_cache_create("my_cache",
                                 sizeof(struct my_packet),
                                 0, 0, NULL);
    if (!my_cache)
        return -ENOMEM;
    
    /* Create mempool with 32 preallocated objects */
    my_pool = mempool_create_slab_pool(32, my_cache);
    if (!my_pool) {
        kmem_cache_destroy(my_cache);
        return -ENOMEM;
    }
    
    pr_info("Mempool created with 32 objects\n");
    return 0;
}

static void mempool_example_exit(void)
{
    mempool_destroy(my_pool);
    kmem_cache_destroy(my_cache);
}

Page Allocation

Direct Page Allocation

#include <linux/gfp.h>

/*
 * __get_free_pages - Allocate 2^order pages
 * 
 * @gfp_mask: GFP flags
 * @order: Order of allocation (0 = 1 page, 1 = 2 pages, 2 = 4 pages, etc.)
 * 
 * Return: Address of first page, or 0 on failure
 * 
 * Maximum order is typically 10 or 11 (4MB or 8MB)
 */
unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order);

/*
 * __get_free_page - Allocate single page
 */
#define __get_free_page(gfp_mask) \
        __get_free_pages((gfp_mask), 0)

/*
 * get_zeroed_page - Allocate and zero single page
 */
unsigned long get_zeroed_page(gfp_t gfp_mask);

/*
 * free_pages - Free pages allocated by __get_free_pages
 */
void free_pages(unsigned long addr, unsigned int order);

/*
 * free_page - Free single page
 */
#define free_page(addr) free_pages((addr), 0)

/*
 * alloc_pages - Allocate pages (returns struct page *)
 */
struct page *alloc_pages(gfp_t gfp_mask, unsigned int order);

/*
 * alloc_page - Allocate single page (returns struct page *)
 */
#define alloc_page(gfp_mask) alloc_pages((gfp_mask), 0)

/*
 * __free_pages - Free pages (struct page version)
 */
void __free_pages(struct page *page, unsigned int order);

/*
 * __free_page - Free single page
 */
#define __free_page(page) __free_pages((page), 0)

/*
 * Page address conversion
 */
void *page_address(struct page *page);  /* Get virtual address */
struct page *virt_to_page(void *addr);  /* Get page struct */

Page Allocation Examples

/*
 * Example 1: Allocate single page
 */
static int single_page_example(void)
{
    unsigned long page_addr;
    
    /* Allocate one page */
    page_addr = __get_free_page(GFP_KERNEL);
    if (!page_addr) {
        pr_err("Failed to allocate page\n");
        return -ENOMEM;
    }
    
    pr_info("Allocated page at 0x%lx\n", page_addr);
    
    /* Use page (page_addr is virtual address) */
    memset((void *)page_addr, 0, PAGE_SIZE);
    
    /* Free page */
    free_page(page_addr);
    
    return 0;
}

/*
 * Example 2: Allocate multiple contiguous pages
 */
static int multi_page_example(void)
{
    unsigned long pages;
    unsigned int order = 2;  /* 2^2 = 4 pages */
    size_t size = PAGE_SIZE << order;  /* 4 * PAGE_SIZE */
    
    /* Allocate 4 contiguous pages */
    pages = __get_free_pages(GFP_KERNEL, order);
    if (!pages) {
        pr_err("Failed to allocate %u pages\n", 1 << order);
        return -ENOMEM;
    }
    
    pr_info("Allocated %zu bytes (%u pages) at 0x%lx\n",
            size, 1 << order, pages);
    
    /* Use pages */
    memset((void *)pages, 0, size);
    
    /* Free pages (must use same order!) */
    free_pages(pages, order);
    
    return 0;
}

/*
 * Example 3: Using struct page
 */
static int page_struct_example(void)
{
    struct page *page;
    void *virt_addr;
    
    /* Allocate page (returns struct page *) */
    page = alloc_page(GFP_KERNEL);
    if (!page) {
        pr_err("Failed to allocate page\n");
        return -ENOMEM;
    }
    
    /* Get virtual address */
    virt_addr = page_address(page);
    pr_info("Allocated page, virt_addr = %p\n", virt_addr);
    
    /* Use page */
    memset(virt_addr, 0, PAGE_SIZE);
    
    /* Free page */
    __free_page(page);
    
    return 0;
}

DMA-Capable Memory

DMA Memory Allocation

#include <linux/dma-mapping.h>

/*
 * dma_alloc_coherent - Allocate DMA-coherent memory
 * 
 * @dev: Device structure
 * @size: Size to allocate
 * @dma_handle: Output - DMA address for device
 * @gfp: GFP flags
 * 
 * Return: Virtual address, or NULL on failure
 * 
 * Properties:
 * - Memory is accessible by both CPU and device
 * - Coherent (no cache management needed)
 * - Expensive (may disable caching)
 * - Use for: Control structures, descriptors
 */
void *dma_alloc_coherent(struct device *dev, size_t size,
                        dma_addr_t *dma_handle, gfp_t gfp);

/*
 * dma_free_coherent - Free DMA-coherent memory
 */
void dma_free_coherent(struct device *dev, size_t size,
                      void *cpu_addr, dma_addr_t dma_handle);

/*
 * dma_map_single - Map buffer for DMA
 * 
 * For streaming DMA (single buffer)
 * Use for: Large data transfers
 */
dma_addr_t dma_map_single(struct device *dev, void *ptr,
                         size_t size, enum dma_data_direction dir);

void dma_unmap_single(struct device *dev, dma_addr_t addr,
                     size_t size, enum dma_data_direction dir);

/*
 * DMA directions:
 * 
 * DMA_TO_DEVICE     - Data is sent to device
 * DMA_FROM_DEVICE   - Data is received from device
 * DMA_BIDIRECTIONAL - Data moves both ways
 * DMA_NONE          - No DMA transfer
 */

DMA Examples

/*
 * Example: Allocating DMA buffer
 */
struct my_dma_buffer {
    void *virt_addr;      /* CPU virtual address */
    dma_addr_t dma_addr;  /* Device DMA address */
    size_t size;
};

static int alloc_dma_buffer(struct device *dev, 
                           struct my_dma_buffer *buf,
                           size_t size)
{
    /* Allocate DMA-coherent memory */
    buf->virt_addr = dma_alloc_coherent(dev, size,
                                        &buf->dma_addr,
                                        GFP_KERNEL);
    if (!buf->virt_addr) {
        dev_err(dev, "Failed to allocate DMA buffer\n");
        return -ENOMEM;
    }
    
    buf->size = size;
    
    dev_info(dev, "DMA buffer: virt=%p, dma=%pad, size=%zu\n",
             buf->virt_addr, &buf->dma_addr, buf->size);
    
    return 0;
}

static void free_dma_buffer(struct device *dev,
                           struct my_dma_buffer *buf)
{
    if (buf->virt_addr) {
        dma_free_coherent(dev, buf->size,
                         buf->virt_addr, buf->dma_addr);
        buf->virt_addr = NULL;
    }
}

/*
 * DMA will be covered in more detail in Chapter 7
 */

Per-CPU Variables

Per-CPU Memory

#include <linux/percpu.h>

/*
 * Per-CPU variables - Separate copy for each CPU
 * 
 * Benefits:
 * - No locking needed (each CPU has own copy)
 * - Cache-friendly (no cache line bouncing)
 * - Fast access
 * 
 * Use for: Statistics, per-CPU caches, temporary buffers
 */

/*
 * DEFINE_PER_CPU - Define per-CPU variable
 */
DEFINE_PER_CPU(int, my_percpu_var);

/*
 * get_cpu_var - Access per-CPU variable (disables preemption)
 * put_cpu_var - Release per-CPU variable (enables preemption)
 */
int value = get_cpu_var(my_percpu_var);
/* ... use value ... */
put_cpu_var(my_percpu_var);

/*
 * per_cpu - Access per-CPU variable for specific CPU
 */
int value = per_cpu(my_percpu_var, cpu_id);

/*
 * alloc_percpu - Dynamically allocate per-CPU data
 */
void __percpu *alloc_percpu(type);

/*
 * free_percpu - Free per-CPU data
 */
void free_percpu(void __percpu *ptr);

/*
 * Example: Per-CPU statistics
 */
struct stats {
    unsigned long packets;
    unsigned long bytes;
    unsigned long errors;
};

static DEFINE_PER_CPU(struct stats, device_stats);

static void update_stats(unsigned long bytes)
{
    struct stats *s;
    
    /* Get per-CPU stats (disables preemption) */
    s = &get_cpu_var(device_stats);
    
    s->packets++;
    s->bytes += bytes;
    
    /* Release (enables preemption) */
    put_cpu_var(device_stats);
}

static void print_total_stats(void)
{
    struct stats total = {0};
    int cpu;
    
    /* Sum stats from all CPUs */
    for_each_possible_cpu(cpu) {
        struct stats *s = &per_cpu(device_stats, cpu);
        total.packets += s->packets;
        total.bytes += s->bytes;
        total.errors += s->errors;
    }
    
    pr_info("Total: packets=%lu, bytes=%lu, errors=%lu\n",
            total.packets, total.bytes, total.errors);
}

Memory Mapping Internals

Understanding Virtual Memory Areas

/*
 * struct vm_area_struct - Represents a virtual memory region
 */
struct vm_area_struct {
    unsigned long vm_start;  /* Start address */
    unsigned long vm_end;    /* End address (exclusive) */
    unsigned long vm_flags;  /* Flags (VM_READ, VM_WRITE, etc.) */
    struct file *vm_file;    /* File being mapped (if any) */
    void *vm_private_data;   /* Driver private data */
    const struct vm_operations_struct *vm_ops;
    /* ... more fields ... */
};

/*
 * Common VM flags:
 * 
 * VM_READ, VM_WRITE, VM_EXEC - Access permissions
 * VM_SHARED - Modifications are shared
 * VM_IO - Memory mapped I/O
 * VM_RESERVED - Don't swap out
 * VM_DONTCOPY - Don't copy on fork
 * VM_DONTEXPAND - Cannot grow with mremap
 */

Summary

In this chapter, you learned:

Memory Architecture: Zones, virtual vs physical memory
kmalloc/kfree: Fast physically contiguous allocation
vmalloc/vfree: Large virtually contiguous allocation
Slab Allocator: Efficient same-size object caching
Page Allocation: Direct page-level allocation
DMA Memory: Device-accessible memory
Per-CPU Variables: Lock-free per-processor data

Key Takeaways

  1. Choose appropriate allocator based on size and requirements
  2. Always check return values - allocation can fail
  3. Free all allocated memory in cleanup paths
  4. Use GFP_KERNEL when you can sleep, GFP_ATOMIC in atomic context
  5. DMA requires special memory allocation functions

Next Steps

Proceed to 05-concurrency.md to learn about synchronization, locking, and concurrent access to shared data.


Quick Reference

Allocation Functions

/* Small allocations */
void *kmalloc(size, GFP_KERNEL);
void *kzalloc(size, GFP_KERNEL);
void kfree(ptr);

/* Large allocations */
void *vmalloc(size);
void vfree(ptr);

/* Page allocations */
unsigned long __get_free_pages(GFP_KERNEL, order);
void free_pages(addr, order);

/* DMA allocations */
void *dma_alloc_coherent(dev, size, &dma_handle, GFP_KERNEL);
void dma_free_coherent(dev, size, cpu_addr, dma_handle);

/* Slab allocator */
struct kmem_cache *kmem_cache_create(name, size, align, flags, ctor);
void *kmem_cache_alloc(cache, GFP_KERNEL);
void kmem_cache_free(cache, obj);
void kmem_cache_destroy(cache);

GFP Flags

GFP_KERNEL   /* Can sleep */
GFP_ATOMIC   /* Cannot sleep */
GFP_DMA      /* DMA-capable memory */
__GFP_ZERO   /* Zero memory */