Memory layout

The linker script, as we already know, contains the instructions for the linker on how to assemble together the components of an embedded system. More specifically, it describes the sections mapped in memory and how they are deployed into the flash and the RAM of the target, as in the example provided in Chapter 2Work Environment and Workflow Optimization.

In most embedded devices, and in particular our reference platform, the .text area, which contains all the executable code, should include the special subsection dedicated to store the IV at the very beginning of the executable image.

We integrate the linker script by adding the .isr_vector section at the beginning of .text area, before the rest of the code:

.text :
{
*(.isr_vector)
*(.text*)
*(.rodata*)
} > FLASH

Defining a read-only section in flash which is dedicated to the vector table is the only strict requirement for our system to boot up properly, as the address of the isr_reset function is retrieved by the CPU at boot time from address 0x04 in memory.

Right after the definition for the text and read-only areas in flash, the linker script should export the value of the current address, which is the beginning of the .data section stored in flash. This area contains the initial value of all the global and static variables that have been initialized in the code. In the example linker script, the beginning of the .data section is marked by the linker script variable _stored_data, as follows:

_stored_data = .;

The data section will eventually be mapped in RAM, but its initialization is done manually in the isr_reset function by copying the content from flash to the actual .data region in RAM. The linker script provides a mechanism to separate the Virtual Memory Address (VMA) and the Load Memory Address (LMA) for a section, using the keyword AT in the definition of the section. If no AT word is specified, the LMA is by default set to the same address as the VMA. In our case, the VMA of the .data region is in RAM, and exported using the _start_data pointer, which will be used by the isr_vector as destination address when copying the values of the symbols stored from flash. The LMA of .data, though, is located in the flash memory, so we set the LMA address to the _stored_data pointer in flash while defining the .data region:

.data : AT (_stored_data)
{
_start_data = .;
*(.data*)
. = ALIGN(4);
_end_data = .;
} > RAM

For .bss, there is no LMA, as no data is stored in the image for this section. When including the .bss region, its VMA will automatically be set to the end of the .data section:

.bss :
{
_start_bss = .;
*(.bss*)
. = ALIGN(4);
_end_bss = .;
_end = .;
} > RAM

Finally, in this design, the linker is expected to provide the initial value for the execution stack. Using the highest address in memory is a common choice for a single-threaded application, even though, as discussed in the next chapter, this may cause problems in the case of stack overflow. For this example, however, this is an acceptable solution, and we define the symbol END_STACK by adding the following line to the linker script:

END_STACK = ORIGIN(RAM) + LENGTH(RAM);

To better understand where each symbol will be placed in memory, variable definitions can be added to the startup file in different places within the code. This way, we can check the locations where the variables are stored in memory when running the executable in the debugger for the first time. Supposing that we have variables stored in both the .data and .bss sections, the memory layout for the example startup code may look like the following:

Memory layout in the example startup code

When the executable is linked, the symbols are automatically set at compile time to indicate the beginning and the end of each section in memory. In our case, variables indicating the beginning and the end of each section are automatically assigned to the right value, depending on the size of the sections that the linker will include when creating the executable. Since the size of each section is known at compile time, the linker is able to identify those situations where the .text and .data do not fit into the flash, and a linker error is generated at the end of the build. Creating a .map file is useful to check the size and the location of each symbol. In our boot-up example code, here is how the .text section appears within the .map file:

.text 0x0000000000000000 0x168
0x0000000000000000 _start_text = .
*(.isr_vector)
.isr_vector 0x0000000000000000 0xf0 startup.o
0x0000000000000000 IV
*(.text*)
.text 0x00000000000000f0 0x78 startup.o
0x00000000000000f0 isr_reset
0x0000000000000134 isr_fault
0x000000000000013a isr_empty
0x0000000000000146 main

Similarly, we can find the boundaries of each section, exported by the linker script at compile time:

0x0000000000000000 _start_text = .
0x0000000000000168 _end_text = .
0x0000000020000000 _start_data = .
0x0000000020000004 _end_data = .
0x0000000020000004 _start_bss = .
0x0000000020000328 _end_bss = .
0x0000000020000328 _end = .

The section .rodata, which is empty in this minimalist example, is mapped in the flash memory area, in between .text and the data LMA. This is reserved for constant symbols, because constants do not have to be mapped in RAM. It is advisable to enforce the C modifier const when defining constant symbols, because RAM is often our most precious resource, and in some cases even sparing a few bytes of writable memory by moving constant symbols to the flash can make the difference in the project development, as flash memory is usually much bigger, and its usage can be easily determined at linking time.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.189.177