3
votes

Linux/Unix systems often run on x86 CPU architecture which provides a MMU for memory mapping, but I guess embedded systems lack this, so no malloc and free.

I saw that ES programmers can create a big static buffer to reserve memory:

unsigned char mem[10240];

But I wonder how it works, where does the buffer start exactly, and how the C compiler can map variable definitions to actual memory locations on some platforms.

I have also seen such constants in ES headers (extracted from the ARM STM32L1xx):

#define SRAM_BASE ((uint32_t)0x20000000) /*!< SRAM base address in the alias region */

I understand that SRAM stands for "Static" RAM but does "base address" means the beginning of the memory area reserved to heap/stack access, so a programmer could allocate and free chunks of RAM using a pointer to this base address and a linked list of blocks ?

2
Malloc and free do not require an MMU.Yunnosch
I saw some "homemade" implementations of malloc using various techniques, but the malloc I use in my system uses the mmap system call, which uses the MMU.explogx
Also, ES implementations of malloc are hardware-dependent: it must use these base addresses constants for DMA since you don't have a MMU.explogx
unsigned char *mem[10240]; is not probably typed as you'd want... Perhaps you meant unsigned char mem[10240];Antti Haapala
No. Accessing physical memory is not what is described by "Direct memory access". DMA is, simplified, accessing memory without using the CPU, e..g a peripheral storing larger chunks of data slowly and then only notifying CPU when done.Yunnosch

2 Answers

3
votes

I get the impression that there are some misunderstandings related to this question. Let me try to sort them out.

The function malloc and free do not require an MMU.
The fact that you have seen one implementation using the MMU does not mean it is always needed.
While an MMU might make implementing easier and in case of a PMMU even postpone the failure of attempted mallocs (by using virtual memory), implementing those two functions on statically allocated variables is possible. Using appropriatly large arrays (as you mention) is a way to do so. I did so in at least two different cases, to accommodate immplementations of applications which use dynamic memory allocation.
Note that on embedded systems extended knowledge about the system behaviour is needed, in order to use appropriate algorithms and size to avoid running out of memory (usually caused or exarcerbated by fragementation). But for embedded system that knowledge is sometimes available.

Using such an array means that it is first allocated perfectly normally by the linker, like any other (large) variable.
The address is therefor determined by the linker, according to configuration and system attributes. One likely location is inside the BSS segment (global or static variables without specific value initialisation).

The last paragraph of your question is especially unclear and seems to switch between multiple topics.
Yes, SRAM is for static RAM. The "static" in that is however not related to any of the meanings according to C syntax. The "static" of SRAM refers to the hardware implementation of the memory. It is in contrast to dynamic RAM. Dynamic RAM is much cheaper than static RAM, it does however require regular refreshing. The refreshing is practically always handled by an MMU but I do not see any relation to the other case of MMU-need you mentioned. SRAM can keep its content just with power supply, DRAM loses content after quite short a time. SRAM therefor can be used in embedded systems to keep content across phases in which the CPU is practically dead, often referred to as "low power modes".

The term "base address" has many meanings. It can refer to e.g. the lowest address of an area used to allocate BSS variables, or the lowest address of an area used for dynamic memory management.

A progorammer allocates memory dynamically NOT by giving a pointer, it is done by asking for a pointer instead, which is the return value of e.g. a successful malloc attempt.

The implementation allocates dynamic memory inside a memory area available for specifically that purpose (which might be the mentioned large array). If memory is available (e.g. there are still blocks found in a linked list of free blocks), it returns an address inside one of them (which is of course followed by enough free space to accomodate the requested size) and keeps information on the remaining (if any) free memory (e.g. by inserting that now smaller space back into the linked list). If not enough memory is left anymore, the return value indicates failure.

3
votes

Embedded System is a very broad term. It may mean the micro controller without the operating system or "normal" computer with the OS.

Some examples:

  • uC reading the sensors and controlling the water flow through the pipe.
  • Raspberry Pi run under Linux controlling the drone (it is embedded into the drone)
  • PC computer controlling the laser cutter .
  • mainframe computer in a radio telescope system

So there is no one answer to your question.

uCs will be programmed completely different way than the RPi used in the drone.

But your question is probably about the uC systems I think.

uC systems are usually programming is sometimes called a "bare metal" because programmers do not have the immediate software (abstraction layer) between their application and the actual hardware. So they access the memory, hardware registers and another resources directly. Even operating systems used in the bare metal development (called RTOS-es) are not even similar to the normal OSes like Linux or Windows. They are more libraries linked together with the bare metal application and just provide mechanisms of task management and communication, synchronization and data exchange.

Some questions you asked in the comments

DMA - Direct memory access - allows data transfer between the memory and peripherals or memory with no use of the processor core. Some uC have very complicated DMA & event systems - for example the timer overflow can trigger the ADC which triggers the DMA transfer storing the converted data into the memory.

Linker Script - defines what, how and where is stored in the memory. They may me very complicated and instruct that code or data to include or exclude, and how to organize the program memory. Below you have an example of the linker script for the STM32 family uC

/* Entry Point */
ENTRY(Reset_Handler)

_estack = 0x10004000;    /* end of RAM */

/* Generate a link error if heap and stack don't fit into RAM */
_Min_Heap_Size = 0x100;      /* required amount of heap  */
_Min_Stack_Size = 0x1000; /* required amount of stack */

/* Specify the memory areas */
MEMORY
{
RAM (xrw)      : ORIGIN = 0x20000000, LENGTH = 64K
CCMRAM (rw)      : ORIGIN = 0x10000000, LENGTH = 16K
FLASH (rx)      : ORIGIN = 0x8000000 + 32K, LENGTH = 512K - 34K
}

_FLASH_SIZE = LENGTH(FLASH);

/* Define output sections */
SECTIONS
{
  /* The startup code goes first into FLASH */
  .isr_vector :
  {
    . = ALIGN(4);
    _vectors_start = .;
    KEEP(*(.isr_vector)) /* Startup code */
    . = ALIGN(4);
    _vectors_end = .;
  } >FLASH

  .sizedata :
  {
    . = ALIGN(4);
    KEEP(*(.sizedata))
    KEEP(*(.sizedata*)) 
    . = ALIGN(4);
  } >FLASH  

    .flashdata :
  {
    . = ALIGN(4);
    KEEP(*(.rodata))         /* .rodata sections (constants, strings, etc.) */
    KEEP(*(.rodata*))       /* .rodata* sections (constants, strings, etc.) */
    . = ALIGN(4);
  } >FLASH

  /* The program code and other data goes into FLASH */
  .text :
  {
    . = ALIGN(4);
    *(.text)           /* .text sections (code) */
    *(.text*)          /* .text* sections (code) */
    *(.glue_7)         /* glue arm to thumb code */
    *(.glue_7t)        /* glue thumb to arm code */
    *(.eh_frame)

    KEEP (*(.init))
    KEEP (*(.fini))

    . = ALIGN(4);
    _etext = .;        /* define a global symbols at end of code */
  } >FLASH

  /* Constant data goes into FLASH */
  .rodata :
  {
    . = ALIGN(4);
    *(.rodata)         /* .rodata sections (constants, strings, etc.) */
    *(.rodata*)        /* .rodata* sections (constants, strings, etc.) */
    . = ALIGN(4);
  } >FLASH




  .ARM.extab   : { *(.ARM.extab* .gnu.linkonce.armextab.*) } >FLASH
  .ARM : {
    __exidx_start = .;
    *(.ARM.exidx*)
    __exidx_end = .;
  } >FLASH

  .preinit_array     :
  {
    PROVIDE_HIDDEN (__preinit_array_start = .);
    KEEP (*(.preinit_array*))
    PROVIDE_HIDDEN (__preinit_array_end = .);
  } >FLASH
  .init_array :
  {
    PROVIDE_HIDDEN (__init_array_start = .);
    KEEP (*(SORT(.init_array.*)))
    KEEP (*(.init_array*))
    PROVIDE_HIDDEN (__init_array_end = .);
  } >FLASH
  .fini_array :
  {
    PROVIDE_HIDDEN (__fini_array_start = .);
    KEEP (*(SORT(.fini_array.*)))
    KEEP (*(.fini_array*))
    PROVIDE_HIDDEN (__fini_array_end = .);
  } >FLASH


  /* used by the startup to initialize data */
  _sidata = LOADADDR(.data);
   _ROMEND = .;


  /* Initialized data sections goes into CCMRAM, load LMA copy after code */
  .data : 
  {
    . = ALIGN(4);
    _sdata = .;        /* create a global symbol at data start */
    *(.data)           /* .data sections */
    *(.data*)           /* .data sections */
    . = ALIGN(4);
    _edata = .;        /* define a global symbol at data end */
  } >CCMRAM AT> FLASH

   _ROMSIZE = _ROMEND - ORIGIN(FLASH) + _edata - _sdata;

  _siccmram = ORIGIN(CCMRAM);
    _sconfig = _ROMSIZE;
  _econfig = _sconfig + 2K;
    .bss :
  {
    /* This is used by the startup in order to initialize the .bss secion */
    _sbss = .;         /* define a global symbol at bss start */
    __bss_start__ = _sbss;
    *(.bss)
    *(.bss*)
    *(COMMON)

    . = ALIGN(4);
    _ebss = .;         /* define a global symbol at bss end */
    __bss_end__ = _ebss;
  } >CCMRAM

  /* RAM section 
  */

      /* Uninitialized data section */
   .dummy :
   {
   . = ALIGN(4);
   *(.dummy)
   *(.dummy*)
   . = ALIGN(4);
   } > RAM AT > FLASH

  .ram :
  {  
    . = ALIGN(4);
    _sram = .;       /* create a global symbol at ccmram start */
    *(.ram)
    *(.ram*)   
    . = ALIGN(4);
    _eram = .;       /* create a global symbol at ccmram end */
  } >RAM 

  /* User_heap_stack section, used to check that there is enough RAM left */
  ._user_heap_stack (NOLOAD):
  {
    . = ALIGN(8);
    PROVIDE ( end = . );
    PROVIDE ( _end = . );
    . = . + _Min_Heap_Size;
    . = . + _Min_Stack_Size;
    . = ALIGN(8);
  } >CCMRAM



  /* Remove information from the standard libraries */
  /DISCARD/ :
  {
    libc.a ( * )
    libm.a ( * )
    libgcc.a ( * )
  }


  .ARM.attributes 0 : { *(.ARM.attributes) }
}

MMU - some uCs have memory management units - but usually this peripheral only protects some memory areas. As you do not have the OS, you also do not have the file system - so nothing like mmap exists.

  #define SRAM_BASE ((uint32_t)0x20000000)

it only defines in the human readable form the address of something. In this case probably it is just the address of the beginning of the SRAM memory. Another example:

#define PERIPH_BASE           ((uint32_t)0x40000000U)
#define APB2PERIPH_BASE       (PERIPH_BASE + 0x00010000U)
#define SPI1_BASE             (APB2PERIPH_BASE + 0x00003000U)
#define SPI1                ((SPI_TypeDef *) SPI1_BASE)