-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Description
Environment
- Development Kit: ESP32-DevKitC
- IDF version: v4.3.2
- Build System: idf.py
- Compiler version: xtensa-esp32-elf-gcc (crosstool-NG esp-2021r2) 8.4.0
- Operating System: Linux
- Using an IDE?: No
- Power Supply: USB
Problem Description
esp_get_minimum_free_heap_size()
seems to return incorrect free heap size.
This is a meaningless code, but I try to repeatedly allocate 4-byte memory block with malloc()
while esp_get_minimum_free_heap_size()
returns enough free heap size.
Then, I found malloc()
returns NULL
even if esp_get_minimum_free_heap_size()
says tens of KB free heap there.
Don't see the same problem in ESP-IDF v4.2.
Expected Behavior
malloc()
failed if esp_get_minimum_free_heap_size()
return few bytes of free heap remaining.
Actual Behavior
malloc()
failed even if esp_get_minimum_free_heap_size()
says tens of KB free heap there.
Code to reproduce this issue
Just execute this code, you can find malloc()
returns NULL even if esp_get_minimum_free_heap_size()
says about 73 KB of free heap there.
#include <esp_log.h>
#include <esp_system.h>
#include <stdlib.h>
void app_main(void)
{
ESP_LOGI("app_main", "start free heap size test");
while (malloc(4) != NULL) {}
ESP_LOGI("app_main", "out of memory: %d bytes remaining\n", esp_get_minimum_free_heap_size());
}
Debug Logs
I (0) cpu_start: App cpu up.
I (209) cpu_start: Pro cpu start user code
I (209) cpu_start: cpu freq: 160000000
I (210) cpu_start: Application information:
I (214) cpu_start: Project name: hello-world
I (219) cpu_start: App version: v4.3.2-dirty
I (225) cpu_start: Compile time: Jan 21 2022 11:35:42
I (231) cpu_start: ELF file SHA256: 0c042c5b9d7005d0...
I (237) cpu_start: ESP-IDF: v4.3.2
I (242) heap_init: Initializing. RAM available for dynamic allocation:
I (249) heap_init: At 3FFAE6E0 len 00001920 (6 KiB): DRAM
I (255) heap_init: At 3FFB31F8 len 0002CE08 (179 KiB): DRAM
I (262) heap_init: At 3FFE0440 len 00003AE0 (14 KiB): D/IRAM
I (268) heap_init: At 3FFE4350 len 0001BCB0 (111 KiB): D/IRAM
I (274) heap_init: At 4008B298 len 00014D68 (83 KiB): IRAM
I (282) spi_flash: detected chip: generic
I (285) spi_flash: flash io: dio
W (289) spi_flash: Detected size(4096k) larger than the size in the binary image header(2048k). Using the size in the binary image header.
I (303) cpu_start: Starting scheduler on PRO CPU.
I (0) cpu_start: Starting scheduler on APP CPU.
I (313) app_main: start free heap size test
I (513) app_main: out of memory: 73256 bytes remaining
Other items if possible
I investigated the cause, and looks like free heap size calculation lacks of the new memory block header handling in multi_heap.c
.
void *multi_heap_malloc_impl(multi_heap_handle_t heap, size_t size)
{
// snip
void *result = tlsf_malloc(heap->heap_data, size);
if(result) {
heap->free_bytes -= tlsf_block_size(result);
// snip
}
Because tlsf_block_size()
returns the size of the block, excluding the block header, sizeof(block_header_t*)
should be also decreased if tlsf_malloc() split the block.
In the reproduction code case, when I modify the free heap size calculation like below
heap->free_bytes -= tlsf_block_size(result);
+ heap->free_bytes -= sizeof(struct block_header_t*);
code to reproduce the problem works expected.
I (313) app_main: start free heap size test
I (513) app_main: out of memory: 36 bytes remaining
But, this depends on whether tlsh_malloc()
splits the block or not. Related code (components/heap/heap_tlsf.c
) here:
static inline __attribute__((__always_inline__)) void block_trim_free(control_t* control, block_header_t* block, size_t size)
{
tlsf_assert(block_is_free(block) && "block must be free");
if (block_can_split(block, size))
{
block_header_t* remaining_block = block_split(block, size);
block_link_next(block);
block_set_prev_free(remaining_block);
block_insert(control, remaining_block);
}
}
FYI
On ESP-IDF 4.2, multi_heap.c can handle this problem because it knows whether the block is split or not. So, just decrease block data size excluding the block header but, the new header size handled in split_if_necessary()
.
heap->free_bytes -= block_data_size(best_block);
split_if_necessary(heap, best_block, size, prev_free);
heap->free_bytes += block_data_size(new_block);
just adds the size of the new block, as a result, the new block header size is still decreased.
static void split_if_necessary(heap_t *heap, heap_block_t *block, size_t size, heap_block_t *prev_free_block)
{
// snip
} else {
/* Insert a free block between the current and the next one. */
// snip
heap->free_bytes += block_data_size(new_block);
}