From c2b6a8de3e15fa66d48a373b3504fcebf12d905b Mon Sep 17 00:00:00 2001 From: Riku Isokoski Date: Sun, 21 Aug 2022 14:50:09 +0300 Subject: Fix markdown format with autoformatter (#1284) --- doc/MemoryAnalysis.md | 91 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 55 insertions(+), 36 deletions(-) (limited to 'doc/MemoryAnalysis.md') diff --git a/doc/MemoryAnalysis.md b/doc/MemoryAnalysis.md index 846d5079..0feb0f9e 100644 --- a/doc/MemoryAnalysis.md +++ b/doc/MemoryAnalysis.md @@ -1,25 +1,28 @@ # Memory analysis + The PineTime is equipped with the following memories: - - The internal RAM : **64KB** - - The internal Flash : **512KB** - - The external (SPI) Flash : **4MB** + +- The internal RAM : **64KB** +- The internal Flash : **512KB** +- The external (SPI) Flash : **4MB** Note that the NRF52832 cannot execute code stored in the external flash : we need to store the whole firmware in the internal flash memory, and use the external one to store graphicals assets, fonts... This document describes how the RAM and Flash memories are used in InfiniTime and how to analyze and monitor their usage. It was written in the context of [this memory analysis effort](https://github.com/InfiniTimeOrg/InfiniTime/issues/313). ## Code sections + A binary is composed of multiple sections. Most of the time, these sections are : .text, .rodata, .data and .bss but more sections can be defined in the linker script. Here is a small description of these sections and where they end up in memory: - - **TEXT** = code (FLASH) - - **RODATA** = constants (FLASH) - - **DATA** = initialized variables (FLASH + RAM) - - **BSS** = uninitialized variables (RAM) - +- **TEXT** = code (FLASH) +- **RODATA** = constants (FLASH) +- **DATA** = initialized variables (FLASH + RAM) +- **BSS** = uninitialized variables (RAM) ## Internal FLASH + The internal flash memory stores the whole firmware: code, variable that are not default-initialized, constants... The content of the flash memory can be easily analyzed thanks to the MAP file generated by the compiler. This file lists all the symbols from the program along with their size and location (section and addresses) in RAM and FLASH. @@ -41,62 +44,71 @@ Using this tool, you can compare the relative size of symbols. This can be helpf Also, as Linkermapviz is written in Python, you can easily modify and adapt it to your firmware or export data in another format. For example, [here it is modified to parse the contents of the MAP file and export it in a CSV file](https://github.com/InfiniTimeOrg/InfiniTime/issues/313#issuecomment-842338620). This file could later be opened in LibreOffice Calc where sort/filter functionality could be used to search for specific symbols in specific files... ### Puncover + [Puncover](https://github.com/HBehrens/puncover) is another useful tools that analyses the binary file generated by the compiler (the .out file that contains all debug information). It provides valuable information about the symbols (data and code): name, position, size, max stack of each functions, callers, callees... ![Puncover](./memoryAnalysis/puncover.png) Puncover is really easy to install: - - Clone the repo and cd into the cloned directory - - Setup a venv - - `python -m virtualenv venv` - - `source venv/bin/activate` - - Install : `pip install .` - - Run : `puncover --gcc_tools_base=/path/to/gcc-arm-none-eabi-10.3-2021.10/bin/arm-none-eabi- --elf_file /path/to/build/directory/src/pinetime-app-1.1.0.out --src_root /path/to/sources --build_dir /path/to/build/directory` - - Replace - * `/path/to/gcc-arm-none-eabi-10.3-2021.10/bin` with the path to your gcc-arm-none-eabi toolchain - * `/path/to/build/directory/src/pinetime-app-1.1.0.out` with the path to the binary generated by GCC (.out file) - * `/path/to/sources` with the path to the root folder of the sources (checkout directory) - * `/path/to/build/directory` with the path to the build directory - - Launch a browser at http://localhost:5000/ +- Clone the repo and cd into the cloned directory +- Setup a venv + - `python -m virtualenv venv` + - `source venv/bin/activate` +- Install : `pip install .` +- Run : `puncover --gcc_tools_base=/path/to/gcc-arm-none-eabi-10.3-2021.10/bin/arm-none-eabi- --elf_file /path/to/build/directory/src/pinetime-app-1.1.0.out --src_root /path/to/sources --build_dir /path/to/build/directory` + - Replace + - `/path/to/gcc-arm-none-eabi-10.3-2021.10/bin` with the path to your gcc-arm-none-eabi toolchain + - `/path/to/build/directory/src/pinetime-app-1.1.0.out` with the path to the binary generated by GCC (.out file) + - `/path/to/sources` with the path to the root folder of the sources (checkout directory) + - `/path/to/build/directory` with the path to the build directory +- Launch a browser at http://localhost:5000/ ### Analysis + Using the MAP file and tools, we can easily see what symbols are using most of the flash memory. In this case, unsurprisingly, fonts and graphics are the largest use of flash memory. ![Puncover](./memoryAnalysis/puncover-all-symbols.png) This way, you can easily check what needs to be optimized. We should find a way to store big static data (like fonts and graphics) in the external flash memory, for example. -It's always a good idea to check the flash memory space when working on the project. This way, you can easily check that your developments are using a reasonable amount of space. +It's always a good idea to check the flash memory space when working on the project. This way, you can easily check that your developments are using a reasonable amount of space. ### Links - - Analysis with linkermapviz : https://github.com/InfiniTimeOrg/InfiniTime/issues/313#issuecomment-842338620 - - Analysis with Puncover : https://github.com/InfiniTimeOrg/InfiniTime/issues/313#issuecomment-847311392 + +- Analysis with linkermapviz : https://github.com/InfiniTimeOrg/InfiniTime/issues/313#issuecomment-842338620 +- Analysis with Puncover : https://github.com/InfiniTimeOrg/InfiniTime/issues/313#issuecomment-847311392 ## RAM + RAM memory contains all the data that can be modified at run-time: variables, stack, heap... ### Data + RAM memory can be *statically* allocated, meaning that the size and position of the data are known at compile-time: You can easily analyze the memory used by variables declared in the global scope using the MAP. You'll find them in the .BSS or .DATA sections. Linkermapviz and Puncover can be used to analyze their memory usage. Variables declared in the scope of a function will be allocated on the stack. It means that the stack usage will vary according to the state of the program, and cannot be easily analyzed at compile time. + ``` -uint8_t buffer[1024] +uint8_t buffer[1024] int main() { - int a; + int a; } ``` #### Analysis -In Infinitime 1.1, the biggest buffers are the buffers allocated for LVGL (14KB) and the one for FreeRTOS (16KB). Nimble also allocated 9KB of RAM. + +In Infinitime 1.1, the biggest buffers are the buffers allocated for LVGL (14KB) and the one for FreeRTOS (16KB). Nimble also allocated 9KB of RAM. ### Stack + The stack will be used for everything except tasks, which have their own stack allocated by FreeRTOS. The stack is 8192B and is allocated in the [linker script](https://github.com/InfiniTimeOrg/InfiniTime/blob/develop/nrf_common.ld#L148). An easy way to monitor its usage is by filling the section with a known pattern at boot time, then use the firmware and dump the memory. You can then check the maximum stack usage by checking the address from the beginning of the stack that were overwritten. #### Fill the stack section by a known pattern: + Edit /modules/nrfx/mdk/gcc_startup_nrf52.S and add the following code after the copy of the data from read only memory to RAM at around line 243: ``` @@ -138,6 +150,7 @@ bne .L_fill ``` #### Dump RAM memory and check usage + Dumping the content of the ram is easy using JLink debugger and `nrfjprog`: ``` @@ -193,13 +206,16 @@ On the following dump, the maximum stack usage is 520 bytes (0xFFFF - 0xFDF8): 000fff0 7fc0 2000 4217 0001 3f0a 0001 0000 6100 ``` -#### Analysis +#### Analysis + According to my experimentations, we don't use the stack that much, and 8192 bytes is probably way too big for InfiniTime! #### Links - - https://github.com/InfiniTimeOrg/InfiniTime/issues/313#issuecomment-851035070 + +- https://github.com/InfiniTimeOrg/InfiniTime/issues/313#issuecomment-851035070 ### Heap + The heap is declared in the [linker script](https://github.com/InfiniTimeOrg/InfiniTime/blob/develop/nrf_common.ld#L136) and its current size is 8192 bytes. The heap is used for dynamic memory allocation(`malloc()`, `new`...). Heap monitoring is not easy, but it seems that we can use the following code to know the current usage of the heap: @@ -210,6 +226,7 @@ NRF_LOG_INFO("heap : %d", m.uordblks); ``` #### Analysis + According to my experimentation, InfiniTime uses ~6000bytes of heap most of the time. Except when the Navigation app is launched, where the heap usage exceeds 9500 bytes (meaning that the heap overflows and could potentially corrupt the stack). This is a bug that should be fixed in #362. To know exactly what's consuming heap memory, you can `wrap` functions like `malloc()` into your own functions. In this wrapper, you can add logging code or put breakpoints: @@ -225,6 +242,7 @@ void* __wrap_malloc(size_t size) { } } ``` + Now, your function `__wrap_malloc()` will be called instead of `malloc()`. You can call the actual malloc from the stdlib by calling `__real_malloc()`. Using this technique, I was able to trace all malloc calls at boot (boot -> digital watchface): @@ -239,12 +257,14 @@ Using this technique, I was able to trace all malloc calls at boot (boot -> digi - hr task = 304 #### Links - - https://github.com/InfiniTimeOrg/InfiniTime/issues/313#issuecomment-851035625 - - https://www.embedded.com/mastering-stack-and-heap-for-system-reliability-part-1-calculating-stack-size/ - - https://www.embedded.com/mastering-stack-and-heap-for-system-reliability-part-2-properly-allocating-stacks/ - - https://www.embedded.com/mastering-stack-and-heap-for-system-reliability-part-3-avoiding-heap-errors/ + +- https://github.com/InfiniTimeOrg/InfiniTime/issues/313#issuecomment-851035625 +- https://www.embedded.com/mastering-stack-and-heap-for-system-reliability-part-1-calculating-stack-size/ +- https://www.embedded.com/mastering-stack-and-heap-for-system-reliability-part-2-properly-allocating-stacks/ +- https://www.embedded.com/mastering-stack-and-heap-for-system-reliability-part-3-avoiding-heap-errors/ ## LVGL + I did a deep analysis of the usage of the buffer dedicated to lvgl (managed by lv_mem). This buffer is used by lvgl to allocated memory for drivers (display/touch), screens, themes, and all widgets created by the apps. @@ -262,11 +282,13 @@ Then, initializing the digital clock face costs **1541 bytes**. For example a simple lv_label needs **~140 bytes** of memory. I tried to monitor this max value while going through all the apps of InfiniTime 1.1 : the max value I've seen is **5660 bytes**. It means that we could probably **reduce the size of the buffer from 14KB to 6 - 10 KB** (we have to take the fragmentation of the memory into account). + ### Links - - https://github.com/InfiniTimeOrg/InfiniTime/issues/313#issuecomment-850890064 +- https://github.com/InfiniTimeOrg/InfiniTime/issues/313#issuecomment-850890064 ## FreeRTOS heap and task stack + FreeRTOS statically allocate its own heap buffer in a global variable named `ucHeap`. This is an array of *uint8_t*. Its size is specified by the definition `configTOTAL_HEAP_SIZE` in *FreeRTOSConfig.h* FreeRTOS uses this buffer to allocate memory for tasks stack and all the RTOS object created during runtime (timers, mutexes...). @@ -276,7 +298,6 @@ The function `xPortGetFreeHeapSize()` returns the amount of memory available in NRF_LOG_INFO("Free heap : %d", xPortGetFreeHeapSize()); ``` - The function `uxTaskGetSystemState()` fetches some information about the running tasks like its name and the minimum amount of stack space that has remained for the task since the task was created: ``` @@ -285,5 +306,3 @@ auto nb = uxTaskGetSystemState(tasksStatus, 10, NULL); for (int i = 0; i < nb; i++) { NRF_LOG_INFO("Task [%s] - %d", tasksStatus[i].pcTaskName, tasksStatus[i].usStackHighWaterMark); ``` - - -- cgit v1.2.3