Skip to content

incorrect free heap size calculation in v4.3 (with TLSF allocator) (IDFGH-6628) #8270

@tomoyuki-nakabayashi

Description

@tomoyuki-nakabayashi

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);
    }

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions