fix(lwip): Allocate signals/mboxes once to reduce heap frag

This commit is contained in:
David Cermak
2026-04-22 21:05:54 +08:00
committed by David Čermák
parent 7c1fd3fbbf
commit 1019cb7c19
3 changed files with 89 additions and 50 deletions
+4 -4
View File
@@ -207,8 +207,8 @@ static void netif_unset_garp_flag(struct netif *netif)
#endif // CONFIG_LWIP_GARP_TMR_INTERVAL #endif // CONFIG_LWIP_GARP_TMR_INTERVAL
#if !LWIP_TCPIP_CORE_LOCKING #if !LWIP_TCPIP_CORE_LOCKING
static sys_sem_t api_sync_sem = NULL; static sys_sem_t api_sync_sem = {0};
static sys_sem_t api_lock_sem = NULL; static sys_sem_t api_lock_sem = {0};
#endif #endif
/** /**
@@ -588,14 +588,14 @@ esp_err_t esp_netif_init(void)
} }
#if !LWIP_TCPIP_CORE_LOCKING #if !LWIP_TCPIP_CORE_LOCKING
if (!api_sync_sem) { if (!sys_sem_valid(&api_sync_sem)) {
if (ERR_OK != sys_sem_new(&api_sync_sem, 0)) { if (ERR_OK != sys_sem_new(&api_sync_sem, 0)) {
ESP_LOGE(TAG, "esp netif api sync sem init fail"); ESP_LOGE(TAG, "esp netif api sync sem init fail");
return ESP_FAIL; return ESP_FAIL;
} }
} }
if (!api_lock_sem) { if (!sys_sem_valid(&api_lock_sem)) {
if (ERR_OK != sys_sem_new(&api_lock_sem, 1)) { if (ERR_OK != sys_sem_new(&api_lock_sem, 1)) {
ESP_LOGE(TAG, "esp netif api lock sem init fail"); ESP_LOGE(TAG, "esp netif api lock sem init fail");
return ESP_FAIL; return ESP_FAIL;
@@ -3,7 +3,7 @@
* *
* SPDX-License-Identifier: BSD-3-Clause * SPDX-License-Identifier: BSD-3-Clause
* *
* SPDX-FileContributor: 2018-2024 Espressif Systems (Shanghai) CO LTD * SPDX-FileContributor: 2018-2025 Espressif Systems (Shanghai) CO LTD
*/ */
#ifndef __SYS_ARCH_H__ #ifndef __SYS_ARCH_H__
#define __SYS_ARCH_H__ #define __SYS_ARCH_H__
@@ -18,12 +18,20 @@ extern "C" {
#endif #endif
typedef SemaphoreHandle_t sys_sem_t; typedef StaticSemaphore_t sys_sem_t;
typedef SemaphoreHandle_t sys_mutex_t; typedef SemaphoreHandle_t sys_mutex_t;
typedef TaskHandle_t sys_thread_t; typedef TaskHandle_t sys_thread_t;
/**
* @brief Mailbox structure using static allocation to prevent heap fragmentation
*
* This structure uses FreeRTOS static queue allocation instead of dynamic allocation.
* The buffer[] field is a flexible array member that points to the queue storage
* allocated immediately after this structure.
*/
typedef struct sys_mbox_s { typedef struct sys_mbox_s {
QueueHandle_t os_mbox; StaticQueue_t os_mbox; /**< FreeRTOS static queue structure */
uint8_t buffer[]; /**< Flexible array member for queue storage */
}* sys_mbox_t; }* sys_mbox_t;
/** This is returned by _fromisr() sys functions to tell the outermost function /** This is returned by _fromisr() sys functions to tell the outermost function
@@ -45,9 +53,20 @@ void sys_delay_ms(uint32_t ms);
#define sys_mbox_valid(mbox) (*(mbox) != NULL) #define sys_mbox_valid(mbox) (*(mbox) != NULL)
#define sys_mbox_set_invalid(mbox) (*(mbox) = NULL) #define sys_mbox_set_invalid(mbox) (*(mbox) = NULL)
#define sys_sem_valid_val(sema) ((sema) != NULL) /**
#define sys_sem_valid(sema) (((sema) != NULL) && sys_sem_valid_val(*(sema))) * @brief Check if a static semaphore is valid (initialized)
#define sys_sem_set_invalid(sema) ((*(sema)) = NULL) *
* For static semaphores, we check if the structure is zero-initialized.
* A zero-initialized StaticSemaphore_t indicates an uninitialized semaphore.
*/
#define sys_sem_valid(sema) ((memcmp((sema), &(StaticSemaphore_t){0}, sizeof(StaticSemaphore_t)) == 0) ? pdFALSE : pdTRUE)
/**
* @brief Mark a static semaphore as invalid (uninitialized)
*
* Sets the entire StaticSemaphore_t structure to zero, indicating it's uninitialized.
*/
#define sys_sem_set_invalid(sema) (memset((sema), 0, sizeof(StaticSemaphore_t)))
void sys_delay_ms(uint32_t ms); void sys_delay_ms(uint32_t ms);
sys_sem_t* sys_thread_sem_init(void); sys_sem_t* sys_thread_sem_init(void);
+60 -40
View File
@@ -22,6 +22,8 @@
#include "arch/vfs_lwip.h" #include "arch/vfs_lwip.h"
#include "esp_log.h" #include "esp_log.h"
#include "esp_compiler.h" #include "esp_compiler.h"
#include "esp_heap_caps.h"
#include <string.h>
static const char* TAG = "lwip_arch"; static const char* TAG = "lwip_arch";
@@ -96,11 +98,14 @@ sys_mutex_free(sys_mutex_t *pxMutex)
#endif /* !LWIP_COMPAT_MUTEX */ #endif /* !LWIP_COMPAT_MUTEX */
/** /**
* @brief Creates a new semaphore * @brief Creates a new semaphore using static allocation
* *
* @param sem pointer of the semaphore * Uses FreeRTOS static semaphore allocation to prevent heap fragmentation.
* @param count initial state of the semaphore * The semaphore structure is pre-allocated and passed to this function.
* @return err_t *
* @param sem pointer to the pre-allocated semaphore structure
* @param count initial state of the semaphore (0 or 1)
* @return err_t ERR_OK on success, ERR_MEM on failure
*/ */
err_t err_t
sys_sem_new(sys_sem_t *sem, u8_t count) sys_sem_new(sys_sem_t *sem, u8_t count)
@@ -108,14 +113,14 @@ sys_sem_new(sys_sem_t *sem, u8_t count)
LWIP_ASSERT("initial_count invalid (neither 0 nor 1)", LWIP_ASSERT("initial_count invalid (neither 0 nor 1)",
(count == 0) || (count == 1)); (count == 0) || (count == 1));
*sem = xSemaphoreCreateBinary(); QueueHandle_t res = xSemaphoreCreateBinaryStatic(sem);
if (*sem == NULL) { if ((sys_sem_t *)res != sem) {
LWIP_DEBUGF(SYS_DEBUG, ("sys_sem_new: out of mem\r\n")); LWIP_DEBUGF(SYS_DEBUG, ("sys_sem_new: out of mem\r\n"));
return ERR_MEM; return ERR_MEM;
} }
if (count == 1) { if (count == 1) {
BaseType_t ret = xSemaphoreGive(*sem); BaseType_t ret = xSemaphoreGive(sem);
LWIP_ASSERT("sys_sem_new: initial give failed", ret == pdTRUE); LWIP_ASSERT("sys_sem_new: initial give failed", ret == pdTRUE);
(void)ret; (void)ret;
} }
@@ -131,7 +136,7 @@ sys_sem_new(sys_sem_t *sem, u8_t count)
void void
sys_sem_signal(sys_sem_t *sem) sys_sem_signal(sys_sem_t *sem)
{ {
BaseType_t ret = xSemaphoreGive(*sem); BaseType_t ret = xSemaphoreGive(sem);
/* queue full is OK, this is a signal only... */ /* queue full is OK, this is a signal only... */
LWIP_ASSERT("sys_sem_signal: sane return value", LWIP_ASSERT("sys_sem_signal: sane return value",
(ret == pdTRUE) || (ret == errQUEUE_FULL)); (ret == pdTRUE) || (ret == errQUEUE_FULL));
@@ -144,7 +149,7 @@ int
sys_sem_signal_isr(sys_sem_t *sem) sys_sem_signal_isr(sys_sem_t *sem)
{ {
BaseType_t woken = pdFALSE; BaseType_t woken = pdFALSE;
xSemaphoreGiveFromISR(*sem, &woken); xSemaphoreGiveFromISR(sem, &woken);
return woken == pdTRUE; return woken == pdTRUE;
} }
@@ -162,7 +167,7 @@ sys_arch_sem_wait(sys_sem_t *sem, u32_t timeout)
if (!timeout) { if (!timeout) {
/* wait infinite */ /* wait infinite */
ret = xSemaphoreTake(*sem, portMAX_DELAY); ret = xSemaphoreTake((QueueHandle_t)sem, portMAX_DELAY);
LWIP_ASSERT("taking semaphore failed", ret == pdTRUE); LWIP_ASSERT("taking semaphore failed", ret == pdTRUE);
} else { } else {
/* Round up the number of ticks. /* Round up the number of ticks.
@@ -172,7 +177,7 @@ sys_arch_sem_wait(sys_sem_t *sem, u32_t timeout)
* 1 tick before triggering a timeout. Thus, we need to pass 2 ticks as a timeout * 1 tick before triggering a timeout. Thus, we need to pass 2 ticks as a timeout
* to `xSemaphoreTake`. */ * to `xSemaphoreTake`. */
TickType_t timeout_ticks = ((timeout + portTICK_PERIOD_MS - 1) / portTICK_PERIOD_MS) + 1; TickType_t timeout_ticks = ((timeout + portTICK_PERIOD_MS - 1) / portTICK_PERIOD_MS) + 1;
ret = xSemaphoreTake(*sem, timeout_ticks); ret = xSemaphoreTake((QueueHandle_t)sem, timeout_ticks);
if (ret == errQUEUE_EMPTY) { if (ret == errQUEUE_EMPTY) {
/* timed out */ /* timed out */
return SYS_ARCH_TIMEOUT; return SYS_ARCH_TIMEOUT;
@@ -191,35 +196,39 @@ sys_arch_sem_wait(sys_sem_t *sem, u32_t timeout)
void void
sys_sem_free(sys_sem_t *sem) sys_sem_free(sys_sem_t *sem)
{ {
vSemaphoreDelete(*sem); vSemaphoreDelete((QueueHandle_t)sem);
*sem = NULL; memset(sem, 0, sizeof(*sem));
} }
/** /**
* @brief Create an empty mailbox. * @brief Create an empty mailbox using static allocation
* *
* @param mbox pointer of the mailbox * Uses FreeRTOS static queue allocation to prevent heap fragmentation.
* @param size size of the mailbox * Allocates memory for the mailbox structure plus the queue storage buffer.
* The buffer[] flexible array member points to the queue storage area.
*
* @param mbox pointer to the mailbox pointer (will be allocated)
* @param size size of the mailbox (number of messages)
* @return ERR_OK on success, ERR_MEM when out of memory * @return ERR_OK on success, ERR_MEM when out of memory
*/ */
err_t err_t
sys_mbox_new(sys_mbox_t *mbox, int size) sys_mbox_new(sys_mbox_t *mbox, int size)
{ {
*mbox = mem_malloc(sizeof(struct sys_mbox_s)); *mbox = (sys_mbox_t)heap_caps_malloc(sizeof(struct sys_mbox_s) + (size * sizeof(void *)), ( MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT ));
if (*mbox == NULL){ if (*mbox == NULL){
LWIP_DEBUGF(SYS_DEBUG, ("fail to new *mbox\n")); LWIP_DEBUGF(SYS_DEBUG, ("fail to new *mbox\n"));
return ERR_MEM; return ERR_MEM;
} }
(*mbox)->os_mbox = xQueueCreate(size, sizeof(void *)); QueueHandle_t res = xQueueCreateStatic(size, sizeof(void *), (*mbox)->buffer, &(*mbox)->os_mbox);
if ((*mbox)->os_mbox == NULL) { if ((QueueHandle_t)&(*mbox)->os_mbox != res) {
LWIP_DEBUGF(SYS_DEBUG, ("fail to new (*mbox)->os_mbox\n")); LWIP_DEBUGF(SYS_DEBUG, ("fail to new (*mbox)->os_mbox\n"));
mem_free(*mbox); heap_caps_free(*mbox);
return ERR_MEM; return ERR_MEM;
} }
LWIP_DEBUGF(SYS_DEBUG, ("new *mbox ok mbox=%p os_mbox=%p\n", *mbox, (*mbox)->os_mbox)); LWIP_DEBUGF(SYS_DEBUG, ("new *mbox ok mbox=%p os_mbox=%p\n", *mbox, (QueueHandle_t)&(*mbox)->os_mbox));
return ERR_OK; return ERR_OK;
} }
@@ -232,7 +241,7 @@ sys_mbox_new(sys_mbox_t *mbox, int size)
void void
sys_mbox_post(sys_mbox_t *mbox, void *msg) sys_mbox_post(sys_mbox_t *mbox, void *msg)
{ {
BaseType_t ret = xQueueSendToBack((*mbox)->os_mbox, &msg, portMAX_DELAY); BaseType_t ret = xQueueSendToBack((QueueHandle_t)&(*mbox)->os_mbox, &msg, portMAX_DELAY);
LWIP_ASSERT("mbox post failed", ret == pdTRUE); LWIP_ASSERT("mbox post failed", ret == pdTRUE);
(void)ret; (void)ret;
} }
@@ -249,10 +258,10 @@ sys_mbox_trypost(sys_mbox_t *mbox, void *msg)
{ {
err_t xReturn; err_t xReturn;
if (xQueueSend((*mbox)->os_mbox, &msg, 0) == pdTRUE) { if (xQueueSend((QueueHandle_t)&(*mbox)->os_mbox, &msg, 0) == pdTRUE) {
xReturn = ERR_OK; xReturn = ERR_OK;
} else { } else {
LWIP_DEBUGF(SYS_DEBUG, ("trypost mbox=%p fail\n", (*mbox)->os_mbox)); LWIP_DEBUGF(SYS_DEBUG, ("trypost mbox=%p fail\n", (QueueHandle_t)&(*mbox)->os_mbox));
xReturn = ERR_MEM; xReturn = ERR_MEM;
} }
@@ -274,7 +283,7 @@ sys_mbox_trypost_fromisr(sys_mbox_t *mbox, void *msg)
BaseType_t ret; BaseType_t ret;
BaseType_t xHigherPriorityTaskWoken = pdFALSE; BaseType_t xHigherPriorityTaskWoken = pdFALSE;
ret = xQueueSendFromISR((*mbox)->os_mbox, &msg, &xHigherPriorityTaskWoken); ret = xQueueSendFromISR((QueueHandle_t)&(*mbox)->os_mbox, &msg, &xHigherPriorityTaskWoken);
if (ret == pdTRUE) { if (ret == pdTRUE) {
if (xHigherPriorityTaskWoken == pdTRUE) { if (xHigherPriorityTaskWoken == pdTRUE) {
return ERR_NEED_SCHED; return ERR_NEED_SCHED;
@@ -306,11 +315,11 @@ sys_arch_mbox_fetch(sys_mbox_t *mbox, void **msg, u32_t timeout)
if (timeout == 0) { if (timeout == 0) {
/* wait infinite */ /* wait infinite */
ret = xQueueReceive((*mbox)->os_mbox, &(*msg), portMAX_DELAY); ret = xQueueReceive((QueueHandle_t)&(*mbox)->os_mbox, &(*msg), portMAX_DELAY);
LWIP_ASSERT("mbox fetch failed", ret == pdTRUE); LWIP_ASSERT("mbox fetch failed", ret == pdTRUE);
} else { } else {
TickType_t timeout_ticks = timeout / portTICK_PERIOD_MS; TickType_t timeout_ticks = timeout / portTICK_PERIOD_MS;
ret = xQueueReceive((*mbox)->os_mbox, &(*msg), timeout_ticks); ret = xQueueReceive((QueueHandle_t)&(*mbox)->os_mbox, &(*msg), timeout_ticks);
if (ret == errQUEUE_EMPTY) { if (ret == errQUEUE_EMPTY) {
/* timed out */ /* timed out */
*msg = NULL; *msg = NULL;
@@ -338,7 +347,7 @@ sys_arch_mbox_tryfetch(sys_mbox_t *mbox, void **msg)
if (msg == NULL) { if (msg == NULL) {
msg = &msg_dummy; msg = &msg_dummy;
} }
ret = xQueueReceive((*mbox)->os_mbox, &(*msg), 0); ret = xQueueReceive((QueueHandle_t)&(*mbox)->os_mbox, &(*msg), 0);
if (ret == errQUEUE_EMPTY) { if (ret == errQUEUE_EMPTY) {
*msg = NULL; *msg = NULL;
return SYS_MBOX_EMPTY; return SYS_MBOX_EMPTY;
@@ -359,11 +368,11 @@ sys_mbox_free(sys_mbox_t *mbox)
if ((NULL == mbox) || (NULL == *mbox)) { if ((NULL == mbox) || (NULL == *mbox)) {
return; return;
} }
UBaseType_t msgs_waiting = uxQueueMessagesWaiting((*mbox)->os_mbox); UBaseType_t msgs_waiting = uxQueueMessagesWaiting((QueueHandle_t)&(*mbox)->os_mbox);
LWIP_ASSERT("mbox quence not empty", msgs_waiting == 0); LWIP_ASSERT("mbox quence not empty", msgs_waiting == 0);
vQueueDelete((*mbox)->os_mbox); vQueueDelete((QueueHandle_t)&(*mbox)->os_mbox);
mem_free(*mbox); heap_caps_free(*mbox);
*mbox = NULL; *mbox = NULL;
(void)msgs_waiting; (void)msgs_waiting;
@@ -490,31 +499,42 @@ sys_thread_sem_free(void* data) // destructor for TLS semaphore
{ {
sys_sem_t *sem = (sys_sem_t*)(data); sys_sem_t *sem = (sys_sem_t*)(data);
if (sem && *sem){ if (sem){
LWIP_DEBUGF(SYS_DEBUG, ("sem del, sem=%p\n", *sem)); LWIP_DEBUGF(SYS_DEBUG, ("sem del, sem=%p\n", sem));
vSemaphoreDelete(*sem); vSemaphoreDelete((QueueHandle_t)sem);
} }
if (sem) { if (sem) {
LWIP_DEBUGF(SYS_DEBUG, ("sem pointer del, sem_p=%p\n", sem)); LWIP_DEBUGF(SYS_DEBUG, ("sem pointer del, sem_p=%p\n", sem));
mem_free(sem); heap_caps_free(sem);
} }
} }
/**
* @brief Initialize thread-local semaphore using static allocation
*
* Creates a thread-local semaphore using static allocation to prevent heap fragmentation.
* Uses heap_caps_malloc with internal memory capabilities for better performance.
*
* @return pointer to the allocated semaphore structure, or NULL on failure
*/
sys_sem_t* sys_sem_t*
sys_thread_sem_init(void) sys_thread_sem_init(void)
{ {
sys_sem_t *sem = (sys_sem_t*)mem_malloc(sizeof(sys_sem_t*)); // Always allocate semaphores from internal memory
sys_sem_t *sem = (sys_sem_t*)heap_caps_malloc(sizeof(StaticSemaphore_t), ( MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT ));
if (!sem){ if (!sem){
ESP_LOGE(TAG, "thread_sem_init: out of memory"); ESP_LOGE(TAG, "thread_sem_init: out of memory");
return 0; return 0;
} }
*sem = xSemaphoreCreateBinary(); // If successful, create a binary semaphore the pointer to the semaphore is returned
if (!(*sem)){ QueueHandle_t res = xSemaphoreCreateBinaryStatic(sem);
mem_free(sem);
ESP_LOGE(TAG, "thread_sem_init: out of memory"); if (res != (QueueHandle_t)sem){
heap_caps_free(sem);
ESP_LOGE(TAG, "thread_sem_init: unable to create semaphore %p != %p", res, sem);
return 0; return 0;
} }