Memory Management Implementation in Embedded Systems: From Static Allocation to Dynamic Memory Pools

Memory Management Implementation in Embedded Systems: From Static Allocation to Dynamic Memory Pools

Introduction

In resource-constrained embedded systems, the efficiency and reliability of memory management are particularly important. This article will detail commonly used memory management techniques in embedded systems, from simple static allocation to complex dynamic memory pool implementations.

Static Memory Management

Fixed Partition Management

// Definition of fixed-size partition
typedef struct {
    uint8_t *pool;         // Start address of the memory pool
    uint32_t block_size;   // Block size
    uint32_t total_blocks; // Total number of blocks
    uint32_t free_blocks;  // Number of free blocks
    uint8_t *bitmap;       // Bitmap marker
} fixed_pool_t;

// Initialize fixed partition
void fixed_pool_init(fixed_pool_t *pool, 
                    void *memory,
                    uint32_t block_size,
                    uint32_t total_blocks)
{
    pool->pool = (uint8_t *)memory;
    pool->block_size = block_size;
    pool->total_blocks = total_blocks;
    pool->free_blocks = total_blocks;
    
    // Initialize bitmap
    uint32_t bitmap_size = (total_blocks + 7) / 8;
    pool->bitmap = (uint8_t *)calloc(bitmap_size, 1);
}

// Allocate memory block
void *fixed_pool_alloc(fixed_pool_t *pool)
{
    if (pool->free_blocks == 0) {
        return NULL;
    }
    
    // Find free block
    for (uint32_t i = 0; i < pool->total_blocks; i++) {
        if (!(pool->bitmap[i / 8] & (1 << (i % 8)))) {
            // Mark as used
            pool->bitmap[i / 8] |= (1 << (i % 8));
            pool->free_blocks--;
            return pool->pool + (i * pool->block_size);
        }
    }
    
    return NULL;
}

Dynamic Memory Management

Dynamic Memory Pool Implementation

// Memory block header structure
typedef struct block_header {
    uint32_t size;              // Block size
    uint8_t used;              // Usage flag
    struct block_header *next;  // Pointer to the next block
    uint32_t magic;            // Magic number for validation
} block_header_t;

// Memory pool structure
typedef struct {
    block_header_t *start;     // Start address
    uint32_t total_size;       // Total size
    uint32_t used_size;        // Used size
    uint32_t peak_used;        // Peak usage
} mem_pool_t;

// Initialize memory pool
void mem_pool_init(mem_pool_t *pool, void *memory, uint32_t size)
{
    pool->start = (block_header_t *)memory;
    pool->total_size = size;
    pool->used_size = 0;
    pool->peak_used = 0;
    
    // Initialize the first free block
    block_header_t *first = pool->start;
    first->size = size - sizeof(block_header_t);
    first->used = 0;
    first->next = NULL;
    first->magic = BLOCK_MAGIC;
}

// Memory allocation
void *mem_pool_alloc(mem_pool_t *pool, uint32_t size)
{
    // Align size
    size = (size + 3) & ~3;
    
    block_header_t *current = pool->start;
    block_header_t *best_fit = NULL;
    uint32_t best_size = UINT32_MAX;
    
    // Best-fit algorithm
    while (current != NULL) {
        if (!current->used && 
            current->size >= size && 
            current->size < best_size) {
            best_fit = current;
            best_size = current->size;
        }
        current = current->next;
    }
    
    if (best_fit == NULL) {
        return NULL;
    }
    
    // Split block
    if (best_fit->size >= size + sizeof(block_header_t) + MIN_BLOCK_SIZE) {
        block_header_t *new_block = (block_header_t *)((uint8_t *)best_fit + 
                                    sizeof(block_header_t) + size);
        new_block->size = best_fit->size - size - sizeof(block_header_t);
        new_block->used = 0;
        new_block->next = best_fit->next;
        new_block->magic = BLOCK_MAGIC;
        
        best_fit->size = size;
        best_fit->next = new_block;
    }
    
    best_fit->used = 1;
    pool->used_size += best_fit->size + sizeof(block_header_t);
    
    if (pool->used_size > pool->peak_used) {
        pool->peak_used = pool->used_size;
    }
    
    return (void *)((uint8_t *)best_fit + sizeof(block_header_t));
}

Memory Fragmentation Management

// Merge adjacent free blocks
void merge_free_blocks(mem_pool_t *pool)
{
    block_header_t *current = pool->start;
    
    while (current != NULL && current->next != NULL) {
        if (!current->used && !current->next->used) {
            // Merge adjacent free blocks
            current->size += current->next->size + sizeof(block_header_t);
            current->next = current->next->next;
        } else {
            current = current->next;
        }
    }
}

// Free memory
void mem_pool_free(mem_pool_t *pool, void *ptr)
{
    if (ptr == NULL) return;
    
    block_header_t *block = (block_header_t *)((uint8_t *)ptr - 
                            sizeof(block_header_t));
    
    // Validate magic number
    if (block->magic != BLOCK_MAGIC) {
        // Memory corruption
        assert(0);
        return;
    }
    
    block->used = 0;
    pool->used_size -= block->size + sizeof(block_header_t);
    
    // Merge free blocks
    merge_free_blocks(pool);
}

Memory Pool Performance Optimization

Multi-Level Memory Pools

// Multi-level memory pool configuration
typedef struct {
    uint32_t block_size;   // Block size
    uint32_t block_count;  // Number of blocks
} pool_config_t;

// Multi-level memory pool structure
typedef struct {
    fixed_pool_t *pools;   // Array of fixed-size pools
    uint32_t pool_count;   // Number of pools
    mem_pool_t *large_pool; // Large memory pool
} multi_pool_t;

// Initialize multi-level memory pool
void multi_pool_init(multi_pool_t *mp, 
                    pool_config_t *configs,
                    uint32_t config_count,
                    void *large_memory,
                    uint32_t large_size)
{
    mp->pool_count = config_count;
    mp->pools = (fixed_pool_t *)malloc(sizeof(fixed_pool_t) * config_count);
    
    // Initialize each fixed pool
    for (uint32_t i = 0; i < config_count; i++) {
        void *pool_memory = malloc(configs[i].block_size * 
                                 configs[i].block_count);
        fixed_pool_init(&mp->pools[i],
                       pool_memory,
                       configs[i].block_size,
                       configs[i].block_count);
    }
    
    // Initialize large memory pool
    mp->large_pool = (mem_pool_t *)malloc(sizeof(mem_pool_t));
    mem_pool_init(mp->large_pool, large_memory, large_size);
}

Cache Alignment Optimization

// Cache line size
#define CACHE_LINE_SIZE 32

// Aligned memory block structure
typedef struct __attribute__((aligned(CACHE_LINE_SIZE))) {
    block_header_t header;
    uint8_t padding[CACHE_LINE_SIZE - sizeof(block_header_t)];
} aligned_block_t;

// Allocate aligned memory
void *aligned_alloc(mem_pool_t *pool, uint32_t size, uint32_t alignment)
{
    // Calculate required extra space
    uint32_t extra = alignment - 1 + sizeof(void *);
    
    // Allocate memory including extra space
    void *raw = mem_pool_alloc(pool, size + extra);
    if (raw == NULL) return NULL;
    
    // Calculate aligned address
    void *aligned = (void *)(((uintptr_t)raw + sizeof(void *) + 
                             alignment - 1) & ~(alignment - 1));
    
    // Store original address
    *((void **)((uintptr_t)aligned - sizeof(void *))) = raw;
    
    return aligned;
}

Memory Monitoring and Diagnostics

Memory Usage Statistics

typedef struct {
    uint32_t total_allocs;    // Total allocation count
    uint32_t total_frees;     // Total free count
    uint32_t current_allocs;  // Current allocation count
    uint32_t failed_allocs;   // Allocation failure count
    uint32_t peak_memory;     // Peak memory usage
    uint32_t fragmentation;    // Fragmentation rate
} mem_stats_t;

// Update statistics
void update_stats(mem_stats_t *stats, mem_pool_t *pool)
{
    uint32_t free_blocks = 0;
    uint32_t largest_free = 0;
    block_header_t *current = pool->start;
    
    while (current != NULL) {
        if (!current->used) {
            free_blocks++;
            if (current->size > largest_free) {
                largest_free = current->size;
            }
        }
        current = current->next;
    }
    
    // Calculate fragmentation rate
    stats->fragmentation = (uint32_t)((1.0f - 
                          (float)largest_free / pool->total_size) * 100);
}

Memory Leak Detection

// Memory allocation record
typedef struct alloc_record {
    void *ptr;               // Allocated pointer
    uint32_t size;           // Allocation size
    const char *file;       // File name
    uint32_t line;          // Line number
    struct alloc_record *next;
} alloc_record_t;

// Track memory allocation
void *tracked_alloc(mem_pool_t *pool, 
                   uint32_t size, 
                   const char *file, 
                   uint32_t line)
{
    void *ptr = mem_pool_alloc(pool, size);
    if (ptr != NULL) {
        alloc_record_t *record = malloc(sizeof(alloc_record_t));
        record->ptr = ptr;
        record->size = size;
        record->file = file;
        record->line = line;
        // Add to tracking list
        record->next = g_alloc_records;
        g_alloc_records = record;
    }
    return ptr;
}

// Check for memory leaks
void check_leaks(void)
{
    alloc_record_t *record = g_alloc_records;
    while (record != NULL) {
        printf("Memory leak: %p size %u allocated at %s:%u\n",
               record->ptr, record->size, record->file, record->line);
        record = record->next;
    }
}

Conclusion

Key considerations for memory management in embedded systems:

  1. Performance and Efficiency

  • Select appropriate allocation algorithms
  • Optimize memory alignment
  • Reduce fragmentation
  • Reliability Assurance

    • Memory boundary checks
    • Leak detection
    • Statistics and monitoring
  • Resource Optimization

    • Multi-level memory pools
    • Fragment merging
    • Cache-friendly design
  • Debugging Support

    • Memory usage tracking
    • Error detection
    • Performance analysis

    Further research is recommended on:

    • Memory allocation algorithms
    • Fragmentation handling strategies
    • Cache optimization techniques
    • Debugging tool development

    Mastering these techniques is crucial for developing high-quality embedded systems.

    Leave a Comment