Fundamentals 121 min read

Comprehensive Embedded Software Interview Guide: Memory Management, IPC, DMA, Kernel Allocation, and Core Concepts

This article provides an extensive overview of embedded software fundamentals, covering heap vs. stack differences, wild pointers, DMA roles, inter‑process communication methods, memory allocation strategies, malloc vs. new, volatile usage, pointer concepts, Linux kernel locks, FreeRTOS mechanisms, stack overflow prevention, compilation stages, quick‑sort algorithm, header inclusion, CAN identifiers, struct memory optimization, STM32 interrupt handling, user‑to‑kernel transitions, and condition‑variable thundering‑herd effects.

Deepin Linux
Deepin Linux
Deepin Linux
Comprehensive Embedded Software Interview Guide: Memory Management, IPC, DMA, Kernel Allocation, and Core Concepts

The piece begins by emphasizing the critical role of embedded software across smart devices and introduces a mock interview covering core topics.

It explains the differences between heap and stack memory in terms of allocation (dynamic vs. automatic), size limits (heap can grow to virtual memory limits, stack is fixed), allocation speed, fragmentation, growth direction, stored content, and allocation methods.

A wild pointer is defined as a pointer that references an unknown or freed memory region; causes include uninitialized pointers, failure to reset after free, and out‑of‑scope references. An example class demonstrates the issue:

class A { public: void Func(){ cout << "Func of class A" << endl; } };
void Test(){ A *p; A a; p = &a; p->Func(); }

DMA (Direct Memory Access) is described both as a financial indicator and a hardware feature that enables peripherals to transfer data without CPU involvement, improving throughput and reducing CPU load.

Inter‑process communication (IPC) methods are listed: pipes (anonymous and named), message queues, shared memory, semaphores, signals, and sockets, each with a brief implementation note.

Memory allocation techniques in programs are detailed: static storage (global/static variables), stack allocation for local variables, heap allocation via malloc/new, and the characteristics of each method, including size limits and performance trade‑offs.

The differences between malloc and new are compared, covering language level (C vs. C++), required size arguments, return types, failure handling (NULL vs. exception), constructor/destructor calls, overloadability, and underlying memory regions.

Uninitialized local variables are shown to yield indeterminate values because stack slots retain previous data; examples illustrate compiler‑specific debug patterns.

The volatile qualifier is explained as a way to prevent compiler optimizations on variables that may change asynchronously, with proper usage for hardware registers and multi‑threaded flags, and a sample of accessing a GPIO register:

volatile uint32_t *gpio_led_register;
void turn_on_led(){ *gpio_led_register = 1; }
void turn_off_led(){ *gpio_led_register = 0; }

Array pointer versus pointer‑array concepts are clarified with code snippets showing an array pointer and a pointer array.

Linux device‑driver architecture is summarized: a bus connects devices, each device is bound to a driver, and the driver registers with the bus to manage the device.

Red‑black tree properties (node colors, root black, leaf NIL black, red nodes have black children, equal black‑height paths) are listed, highlighting self‑balancing guarantees.

Pointer vs. reference differences are enumerated: pointers are variables holding addresses, can be null, reassigned, and support multiple indirection; references are aliases, must be initialized, cannot be reseated, and have no separate storage.

Inline functions and macro functions are contrasted: inline functions provide type checking, debugging, and respect scope, while macros perform textual substitution without type safety; guidelines for choosing each are given.

FreeRTOS synchronization primitives are compared: binary semaphores provide simple mutual exclusion, while mutexes support priority inheritance and recursive locking.

FreeRTOS task notifications are described as lightweight 32‑bit events that can be sent, waited on, and queried without the overhead of queues or semaphores.

Stack overflow causes (deep recursion, large locals, array overrun, illegal pointer use) and mitigation strategies (limit recursion, use heap, increase stack size, check parameters, tail‑call optimization) are outlined.

Deep copy creates independent copies of referenced objects, while shallow copy duplicates only top‑level pointers, leading to shared sub‑objects.

The compilation pipeline from source to executable is broken into preprocessing, compilation, assembly, and linking stages, with notes on each step for C and C++.

Quick‑sort algorithm steps (choose pivot, partition, recursive sort, combine) are summarized.

Header inclusion syntax differences are explained: #include <...> searches system directories, while #include "..." searches the local directory first.

Linux locking mechanisms (mutexes, read‑write locks, spinlocks, semaphores, condition variables) and their underlying implementations (atomic ops, scheduler sleep/wake, memory barriers) are described.

Common GDB commands (run, break, continue, next, step, print, backtrace, list, info breakpoints, delete, watch) are listed.

C++ initializer‑list usage scenarios are given: const/reference members, member objects with non‑default constructors, base‑class and virtual‑base initialization, and const data members.

CAN bus identifiers can be 11‑bit (standard) or 29‑bit (extended) fields.

Techniques for reducing struct memory usage in C are presented: ordering members to minimize padding, using unions, bit‑fields, #pragma pack, and the __attribute__((packed)).

STM32 interrupt handling is explained: the interrupt vector table maps peripheral interrupt numbers to handler addresses defined in the startup file; the CPU jumps to the handler on interrupt, saves context, and returns.

User‑mode to kernel‑mode transitions occur via system calls (software interrupt 0x80 or SWI), hardware interrupts, and exceptions (e.g., page faults), each saving context and invoking kernel handlers.

Condition‑variable thundering‑herd effect is defined as waking all waiting threads when a condition becomes true, leading to unnecessary context switches; mitigation includes re‑checking the condition inside a loop and using more selective wake‑ups.

Memory ManagementCompilationC++DMALinux KernelEmbedded SystemsFreeRTOSinterprocess communication
Deepin Linux
Written by

Deepin Linux

Research areas: Windows & Linux platforms, C/C++ backend development, embedded systems and Linux kernel, etc.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.