4/04/2022

ESP32 의 Memory 구성-5 (Software 분석 )

1. ESP32의 Software Memory 분석


  • ESP32의 HW Memory 관련분석 
ESP32 Memory Address Map 과 전체구성 


ESP32 External Memory 관련구성내용  


상위내용들은 ESP32의 HW입장에서 Memory 사용분석이며, SW입장에서의 Memory 구성을 Linker Script 와 Map파일로 알아보기로 한다.  

Linker Script의 기본이해 


1.1. ESP32의 지원 Macro attribute 확인  

Linker Script와 같이 ESP32에서 제공해주는 MACRO로  _attribute_ 을 이용하여 Section을 정의하고 거기에 할당하는 것인데, 
각 이름을 쉽게 변경하여 제공해주고 있다.  
예를들면 IRAM_DATA_ATTR 은 __attribute__((section(".iram.data"))) 이런식으로 Linker Script에서 정의한 특정영역에 배치가 가능하다. 

물론 본인이 원하면, 새로운 영역을 만들어서, attribute로 정해 별도의 할당도 가능하다. 
오래전에 리눅스에서 Linker Script와 같이 attribute를 이용하여 많이 설정 사용하던 방법이나, 이미 ESP32에서 만들어놓은 것들이 있음므로, 
가급적 아래의 룰을 지키면서 하면 거의 수정할 필요는 없다. 

  • ESP32 Memory Type (Macro Attribuite)
각 Attribute에 정의된 Section는 Linker Script와 같이 연결되어 동작되므로 반드시 숙지해야하며 아래의 Manual 참고 

  • ESP32 Memory Tyep (Attribuite 의 Section)
IRAM 
IRAM_ATTR".iram1" 시작영역, (Interrupt Handler 주로사용)
이외 IRAM을 DATA 로 정의해서 사용하는 부분도 상위 링크에서 확인가능 

DRAM
DRAM_ATTR".dram1" 시작영역, Data 저장용으로 SRAM 

DMA
DMA_ATTR:  ".dram1" 4byte align의 DRAM_ATTR이용, User는 MALLOC_CAP_DMA 이용

External BSS영역 
EXT_RAM_ATTR ".ext_ram.bss"로 시작영역, SPIRAM의 BSS 영역지정

RTC 관련영역들 
RTC_DATA_ATTR : ".rtc.data"  시작영역
RTC_RODATA_ATTR : ".rtc.rodata" 시작영역 
RTC_SLOW_ATTR : ".rtc.force_slow" 시작영역
RTC_FAST_ATTR : ".rtc.force_fast" 시작영역

NOINIT 영역들 
__NOINIT_ATTR :  ".noinit" 시작영역, SRAM 내부의 noinit Section
RTC_NOINIT_ATTR".rtc_noinit" 시작영역, RTC를 위한 noinit Section 

noinit Section은 보통 Loader가 이영역을 건들지 않기 때문에 유용하게 사용가능하다.
예를들면, System Reset하여도 초기화가 되지 않아 보관하고자 하는 정보를 저장가능  

  • system.map 기반으로 쉽게파악 
상위영역을 간단히 찾는 방법은 본인이 build한 system.map에서 쉽게 찾을 수 있으며, 상위영역은 prefix이며 조건에따라 naming이 되어진다. 

  • attribuite 의 section 과 Linker script 
Linker Script는 초기부팅하고 전체구성이므로, 보통 중요한 것들은 ".iram0" 과 ".dram0 기반으로 Assembly와 함께 거의 다 구현되어있다. 
그리고, 각 영역들을 찾아보며 구성이 어떻게되어있는지 쉽게 파악가능하다. 
아래는 현재 가장 많이 사용하는 EXT_RAM_ATTR와 Linker Script 연결구성이며,본인도 다 찾기가 귀찮다. 
  
  • ESP32 IDFv4.2 attribuite 의 Section 확인 
상위 attribute들의 각 Section들을 ESP32 IDF 소스에서 직접 확인하며, 각 버전마다 다를 수 있으므로 반드시 아래에서 확인 


  • ESP32 각 영역 의 예로 IRAM (세부내용은 아래링크 참고)
IRAM(Instruction RAM) Manual 
  1. iram0_0_seg 영역 
  2. 0x4008000 - 0x400A0000 : 0x2000
  3. PRO/APP CPU를 위해서 64K MMU로 사용 


1.2. ESP32 의 Linker Script 기본구조파악    

  • A. ESP32 Core  전체구성확인
ESP32 Core의 기본구성파일로 Cmake/Makefile/Linker Scirpt 들 비롯하여 가장 필수적인 파일들이 존재하므로 구성파악 

  • B. ESP32 Cmake/Makefile 확인
예전에는 순수 Makefile기반으로 했는데, Cmake라서 보기도 편하지만, 다만 component 개념이 있어 좀 그렇다.  
아래의 Cmake/Makefile/Component.mk 에서 Linker Script가 어떤 조건일때 각각 연결되는지만 대충파악하면 된다. 

ESP32 Linker Script 파일들 확인  
상위 Cmake/Makefile/Component.mk와 같이 어떻게 연결되는지를 확인 

ESP32의 Core 기본 Linker Script 
ESP32의 기본 Core Linker Script로 Memory Segment가 기본구성되어있으므로 반드시 이해 
상위 System.map에서 언급된 Segment 중심으로 Linker Script 보도록 하자. 


1.3. Linker Script 의 Memory

우선적으로 Linker Script의 Memory 에서 각 Segment의 주소와 길이 및 기본역할들을 파악하자. 
  • MEMORY Command의  Segment 확인 
가장먼저 확인해야 할 것이 Segment 기반으로 주소/길이 와 R/W/X 확인 될 것 같다.
그리고, 이 안에 무엇을 넣을지는 각 Section 에서 정의된 부분을 찾아 확인하면된다. 
  1. iram0_0_seg(RX) : PRO CPU위한 공간이며 MMU로 이용 
  2. iram_0_2_seg(RX) : SPI Flash를 Mapping하기 위해서 사용
  3. dram0_0_seg(RW): 공유되어진 DRAM이며 BT사용할 경우 변경  
  4. drom0_0_seg(R): DRAM으로 Flash Mapping 
  5. rtc_iram_seg(RWX): RTC FAST용 IRAM
  6. rtc_data_seg(RW): RTC FAST 용 DRAM
  7. rtc_slow_seg(RW): RTC SLOW 용 ULP용으로 사용 
  8. extern_ram_seg(RWX): External Memory로 Text /data  ESP기준으로 보면 I/DRAM
R: Read / X: Execute /W:Write 

  • Segment alias

  • Linker Script Manual 
MEMORY Command (Segment)  


1.4. Linker Script 의 Segment  

상위에서 파악된 Segment가 실제로 어떻게 구성이 되는지를 파악하자. 
아래와 같이 각 Section을 파악하고 이와 연결하는 구성을 파악하자. 
더불어 ESP32에서 제공하는 mapping의 의미도 알아보자 

  • iram0_0_seg 의 Section 구성의 예  
iram0_0_seg의 구성을 간단히 보는 것이며, 이외 segment들은 생략 

.iram0.vectors: ISR 구성확인 

.iram0.text : Code 영역

  • Linker Script Manual 
SECTION Manual 

  • ESP32 Linker Script Manual
ESP32의 Linker Script Manul로 내부에서 제공되는 별도의 내용이 있으므로 반드시 확인

  • ESP32에서 사용하는 mapping
Linker Script Template 기능이라고하며, 미리 이 Template를 만들고 나서 쉽게 연결시키는 구조이다. 
ESP32의 Compiler도 GCC기반인데, GCC에서도 이기능을 지원을 해주는 것 같다.

Linker Script Template
Default scheme 정보확인 



2. build/system.map 분석  

Linker Script 결과를 가장 쉽게 확인하는 방법은 역시 map파일로 보면되며, 실제 어떻게 연결되었는지 자세히 알아볼수 있다. 
각 Segment 뿐만 아니라 Section들도 각 다 확인가능하며, Symbol Table도 다 확인가능하다.
 
  • map파일 분석 
$ cat build/*.map  // Memory Map 구성확인 
....

Memory Configuration    //Linker Script에서 각 Segment 확인 (Embedded Memory:SRAM) 

Name             Origin             Length             Attributes
iram0_0_seg      0x0000000040080000 0x0000000000020000 xr    // 131072  Instruction        SRAM0 MMU (Table 117) (Table 2)
iram0_2_seg      0x00000000400d0020 0x000000000032ffe0 xr    // 3.3M    Instruction        External Memory (Table 1) 
dram0_0_seg      0x000000003ffb0000 0x000000000002c200 rw    // 180736  Data               SRAM2 MMU (Table 117)(상위 SRAM1/2의 Data Segment) 와 dram heap
drom0_0_seg      0x000000003f400020 0x00000000003fffe0 r     // 4M      Data               External Memory (Table 1)
rtc_iram_seg     0x00000000400c0000 0x0000000000002000 xrw   // 8K      Instruction        Embedded Memory (Table 1)  RTC FAST MPU  (Table 117)
rtc_data_seg     0x000000003ff80000 0x0000000000002000 rw    // 8K      Data               Embedded Memory (Table 1)  RTC FAST MPU  (Table 117)
rtc_slow_seg     0x0000000050000000 0x0000000000001000 rw    // 4K      Data/Instruction   Embedded Memory (Table 1)  RTC SLOW MPU  (Table 117)
extern_ram_seg   0x000000003f800000 0x0000000000400000 xrw   // 4M      Data               External Memory (Table 1)
*default*        0x0000000000000000 0xffffffffffffffff
.....
_heap_start                                       esp-idf/soc/libsoc.a(soc_memory_layout.c.obj)
_init                                             c:/users/jhlee/.espressif/tools/xtensa-esp32-elf/esp-2020r3-8.4.0/xtensa-esp32-elf/bin/../lib/gcc/xtensa-esp32-elf/8.4.0/esp32-psram/no-rtti/crti.o
_init_start                                       esp-idf/esp32/libesp32.a(cpu_start.c.obj)

....
//SRAM1/2의 dram0_0_seg 의 heap Start 과 End 
.dram0.heap_start
                0x000000003ffbfbe0        0x0
                0x000000003ffbfbe0                . = ALIGN (0x8)
                0x000000003ffbfbe0                _heap_start = ABSOLUTE (.)
                0x0000000000000001                ASSERT (((_iram_end - ORIGIN (iram0_0_seg)) <= LENGTH (iram0_0_seg)), IRAM0 segment data does not fit.)
                0x0000000000000001                ASSERT (((_heap_start - ORIGIN (dram0_0_seg)) <= LENGTH (dram0_0_seg)), DRAM segment data does not fit.)
                0x000000003ff40000                PROVIDE (UART0 = 0x3ff40000)
                0x000000003ff42000                PROVIDE (SPI1 = 0x3ff42000)
                0x000000003ff43000                PROVIDE (SPI0 = 0x3ff43000)
.......
                0x000000003ff96530                __mb_cur_max = 0x3ff96530
                0x000000003ff96458                __sf_fake_stderr = 0x3ff96458
                0x000000003ff96498                __sf_fake_stdin = 0x3ff96498
                0x000000003ff96478                __sf_fake_stdout = 0x3ff96478
                0x000000003ff96540                __wctomb = 0x3ff96540
                0x0000000040001778                close = 0x40001778
                0x000000004000178c                open = 0x4000178c
                0x00000000400017dc                read = 0x400017dc
                0x00000000400017f4                sbrk = 0x400017f4
                0x0000000040001808                times = 0x40001808
                0x000000004000181c                write = 0x4000181c
                0x000000003ffbfbe0                _static_data_end = _bss_end
                0x0000000040000000                _heap_end = 0x40000000
                0x000000003ff80000                _data_seg_org = ORIGIN (rtc_data_seg)
... 

상위 시작주소는 이미 ESP32 기본 Linker Script에 의해 정해져있고, Lens도 정해져있다.
그러므로, 각각의 좌측 Segment Name을 클릭해보면 알수 있다. 
주의: 동적으로 변한다면 sdkconfig의 각 CONFIG 의해 변경. 

esptool.py 의 Memory Map 

  • buuld 후 idy.py로 size 분석 
$ idf.py size   // *.elf 생성 후 *.map 기반으로 SIZE 분석 
.....
Total sizes:
 DRAM .data size:   24808 bytes
 DRAM .bss  size:   48160 bytes
Used static DRAM:   72968 bytes ( 107768 available, 40.4% used)      // 72968+107768  = 180736 -> 0x2C200 (dram0_0_seg)
Used static IRAM:  115369 bytes (  15703 available, 88.0% used)      // 115369+15703  = 131072 -> 0x20000 (iram0_0_seg)
      Flash code:  869907 bytes
    Flash rodata:  185032 bytes
Total image size:~1195116 bytes (.bin may be padded larger)

$ idf.py size-files      // 상위보다 좀 더 자세히 각 Build된 파일 별로 볼 수 있음 
$ idf.py size-components // ESP32의 각 Components 별로 볼 수 있음 


3. 소스에서 사용되어지는 Memory Map

ESP32의 Memory Map 정의 파일로 Linker Script 와 같이 사용되지 않는 것으로 보이며,
ESP32 IDF 소스에서 직접 Address Map 주소가  필요할때 이용하는 것으로 보인다.

실제 프로그램에서는 아래 주소를 사용을 한다는 말이며, 각 부분이 어떻게 사용되는지는 ESP32 IDF를 전체 분석해야하나, 생략한다. 
Virtual Addresss 관련부분 추후 찾기  

/* Overall memory map */
#define SOC_DROM_LOW            0x3F400000         //V Addr0
#define SOC_DROM_HIGH           0x3F800000         //V Addr0
#define SOC_DRAM_LOW            0x3FFAE000         //V Addr0
#define SOC_DRAM_HIGH           0x40000000         //V Addr0
#define SOC_IROM_LOW            0x400D0000         //V Addr1
#define SOC_IROM_HIGH           0x40400000
#define SOC_IROM_MASK_LOW       0x40000000
#define SOC_IROM_MASK_HIGH      0x40064F00


//DRAM (Data RAM)          bss, init, data, heap 할당
//IRAM (Instruction RAM)   text 할당 

#define SOC_CACHE_PRO_LOW       0x40070000     // SRAM0
#define SOC_CACHE_PRO_HIGH      0x40078000     // SRAM0   
#define SOC_CACHE_APP_LOW       0x40078000     // SRAM0  
#define SOC_CACHE_APP_HIGH      0x40080000     // SRAM0 iram0_0_seg     

#define SOC_IRAM_LOW            0x40080000     // SRAM0 iram0_0_seg  MMU, pagesize기반으로 mapping (세부사항, Datasheet) 
#define SOC_IRAM_HIGH           0x400A0000     // SRAM0 iram0_0_seg  MMU, pagesize기반으로 mapping (세부사항, Datasheet)    

#define SOC_RTC_IRAM_LOW        0x400C0000
#define SOC_RTC_IRAM_HIGH       0x400C2000
#define SOC_RTC_DRAM_LOW        0x3FF80000
#define SOC_RTC_DRAM_HIGH       0x3FF82000
#define SOC_RTC_DATA_LOW        0x50000000
#define SOC_RTC_DATA_HIGH       0x50002000
#define SOC_EXTRAM_DATA_LOW     0x3F800000     //V AddrRAM
#define SOC_EXTRAM_DATA_HIGH    0x3FC00000


//First and last words of the D/IRAM region, for both the DRAM address as well as the IRAM alias.
#define SOC_DIRAM_IRAM_LOW      0x400A0000     // SRAM1
#define SOC_DIRAM_IRAM_HIGH     0x400C0000     // SRAM1   
#define SOC_DIRAM_DRAM_LOW      0x3FFE0000     // SRAM1,2    
#define SOC_DIRAM_DRAM_HIGH     0x40000000     // SRAM1,2


// Region of memory accessible via DMA. See esp_ptr_dma_capable().
#define SOC_DMA_LOW  0x3FFAE000
#define SOC_DMA_HIGH 0x40000000

// Region of memory that is byte-accessible. See esp_ptr_byte_accessible().
#define SOC_BYTE_ACCESSIBLE_LOW     0x3FF90000
#define SOC_BYTE_ACCESSIBLE_HIGH    0x40000000

//Region of memory that is internal, as in on the same silicon die as the ESP32 CPUs
//(excluding RTC data region, that's checked separately.) See esp_ptr_internal().
#define SOC_MEM_INTERNAL_LOW        0x3FF90000
#define SOC_MEM_INTERNAL_HIGH       0x400C2000

// Start (highest address) of ROM boot stack, only relevant during early boot
#define SOC_ROM_STACK_START         0x3ffe3f20

ESP32의 soc.h Memory Map  


  • 상위 값을 간단히 정리 
SOC_CACHE_PRO   :  0x40078000 - 0x40070000  = 0x8000   // SRAM0
SOC_CACHE_APP   :  0x40080000 - 0x40078000  = 0x8000   // SRAM0

SOC_IRAM        :  0x400A0000 - 0x40080000  = 0x20000  // SRAM0
SOC_DIRAM_IRAM  :  0x400C0000 - 0x400A0000  = 0x20000  // SRAM1
SOC_DIRAM_DRAM  :  0x40000000 - 0x3FFE0000  = 0x20000  // SRAM1-> SRAM2


4. ESP32 Memory 최적화방법 

ESP32 Manual에서 자세히 설명을 해주고 있으며, Memory 최적화들은 아래의 링크들로 확인하자. 

  • ESP32 IRAM 최적화방법 
WIFI /BT를 동시에 사용하거나, IRAM 전용으로 선언으로 사용시 이를 최적화해야함

  • ESP32 SRAM의 Heap 최적화방법 
이미 상위 그림에서 있듯이 BT 혹은 다른 CONFIG 설정들을 통해 최적화가 가능하며, Heap을 디버깅도 가능하다.  

  • ESP32 SRAM의 IRAM 문제 및 메모리최적화 
iram0 이 사이즈 문제가 되는 경우들이지만, Linker Script에서 수정하는 것은 아닌것 같다. 

  • ESP32의 RAM 사용법 
RAM을 적게 사용하려면, Dynamic으로 사용하고 각 TaskCreat를 할때 Stack은 Heap에서 오기때문에, 적은양의 Stack을 정하라는 이야기이다.  
그리고 사용하지 않은 CONFIG들은 설정을 하지말자 

  • SRAM의 DRAM의 제약사항
각 할당되어지는 Max 사이즈가 있으므로 확인