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
@@ -3,7 +3,7 @@
*
* 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__
#define __SYS_ARCH_H__
@@ -18,12 +18,20 @@ extern "C" {
#endif
typedef SemaphoreHandle_t sys_sem_t;
typedef StaticSemaphore_t sys_sem_t;
typedef SemaphoreHandle_t sys_mutex_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 {
QueueHandle_t os_mbox;
StaticQueue_t os_mbox; /**< FreeRTOS static queue structure */
uint8_t buffer[]; /**< Flexible array member for queue storage */
}* sys_mbox_t;
/** 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_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)))
#define sys_sem_set_invalid(sema) ((*(sema)) = NULL)
/**
* @brief Check if a static semaphore is valid (initialized)
*
* 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);
sys_sem_t* sys_thread_sem_init(void);
+60 -40
View File
@@ -22,6 +22,8 @@
#include "arch/vfs_lwip.h"
#include "esp_log.h"
#include "esp_compiler.h"
#include "esp_heap_caps.h"
#include <string.h>
static const char* TAG = "lwip_arch";
@@ -96,11 +98,14 @@ sys_mutex_free(sys_mutex_t *pxMutex)
#endif /* !LWIP_COMPAT_MUTEX */
/**
* @brief Creates a new semaphore
* @brief Creates a new semaphore using static allocation
*
* @param sem pointer of the semaphore
* @param count initial state of the semaphore
* @return err_t
* Uses FreeRTOS static semaphore allocation to prevent heap fragmentation.
* The semaphore structure is pre-allocated and passed to this function.
*
* @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
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)",
(count == 0) || (count == 1));
*sem = xSemaphoreCreateBinary();
if (*sem == NULL) {
QueueHandle_t res = xSemaphoreCreateBinaryStatic(sem);
if ((sys_sem_t *)res != sem) {
LWIP_DEBUGF(SYS_DEBUG, ("sys_sem_new: out of mem\r\n"));
return ERR_MEM;
}
if (count == 1) {
BaseType_t ret = xSemaphoreGive(*sem);
BaseType_t ret = xSemaphoreGive(sem);
LWIP_ASSERT("sys_sem_new: initial give failed", ret == pdTRUE);
(void)ret;
}
@@ -131,7 +136,7 @@ sys_sem_new(sys_sem_t *sem, u8_t count)
void
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... */
LWIP_ASSERT("sys_sem_signal: sane return value",
(ret == pdTRUE) || (ret == errQUEUE_FULL));
@@ -144,7 +149,7 @@ int
sys_sem_signal_isr(sys_sem_t *sem)
{
BaseType_t woken = pdFALSE;
xSemaphoreGiveFromISR(*sem, &woken);
xSemaphoreGiveFromISR(sem, &woken);
return woken == pdTRUE;
}
@@ -162,7 +167,7 @@ sys_arch_sem_wait(sys_sem_t *sem, u32_t timeout)
if (!timeout) {
/* wait infinite */
ret = xSemaphoreTake(*sem, portMAX_DELAY);
ret = xSemaphoreTake((QueueHandle_t)sem, portMAX_DELAY);
LWIP_ASSERT("taking semaphore failed", ret == pdTRUE);
} else {
/* 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
* to `xSemaphoreTake`. */
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) {
/* timed out */
return SYS_ARCH_TIMEOUT;
@@ -191,35 +196,39 @@ sys_arch_sem_wait(sys_sem_t *sem, u32_t timeout)
void
sys_sem_free(sys_sem_t *sem)
{
vSemaphoreDelete(*sem);
*sem = NULL;
vSemaphoreDelete((QueueHandle_t)sem);
memset(sem, 0, sizeof(*sem));
}
/**
* @brief Create an empty mailbox.
* @brief Create an empty mailbox using static allocation
*
* @param mbox pointer of the mailbox
* @param size size of the mailbox
* Uses FreeRTOS static queue allocation to prevent heap fragmentation.
* 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
*/
err_t
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){
LWIP_DEBUGF(SYS_DEBUG, ("fail to new *mbox\n"));
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"));
mem_free(*mbox);
heap_caps_free(*mbox);
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;
}
@@ -232,7 +241,7 @@ sys_mbox_new(sys_mbox_t *mbox, int size)
void
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);
(void)ret;
}
@@ -249,10 +258,10 @@ sys_mbox_trypost(sys_mbox_t *mbox, void *msg)
{
err_t xReturn;
if (xQueueSend((*mbox)->os_mbox, &msg, 0) == pdTRUE) {
if (xQueueSend((QueueHandle_t)&(*mbox)->os_mbox, &msg, 0) == pdTRUE) {
xReturn = ERR_OK;
} 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;
}
@@ -274,7 +283,7 @@ sys_mbox_trypost_fromisr(sys_mbox_t *mbox, void *msg)
BaseType_t ret;
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
ret = xQueueSendFromISR((*mbox)->os_mbox, &msg, &xHigherPriorityTaskWoken);
ret = xQueueSendFromISR((QueueHandle_t)&(*mbox)->os_mbox, &msg, &xHigherPriorityTaskWoken);
if (ret == pdTRUE) {
if (xHigherPriorityTaskWoken == pdTRUE) {
return ERR_NEED_SCHED;
@@ -306,11 +315,11 @@ sys_arch_mbox_fetch(sys_mbox_t *mbox, void **msg, u32_t timeout)
if (timeout == 0) {
/* 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);
} else {
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) {
/* timed out */
*msg = NULL;
@@ -338,7 +347,7 @@ sys_arch_mbox_tryfetch(sys_mbox_t *mbox, void **msg)
if (msg == NULL) {
msg = &msg_dummy;
}
ret = xQueueReceive((*mbox)->os_mbox, &(*msg), 0);
ret = xQueueReceive((QueueHandle_t)&(*mbox)->os_mbox, &(*msg), 0);
if (ret == errQUEUE_EMPTY) {
*msg = NULL;
return SYS_MBOX_EMPTY;
@@ -359,11 +368,11 @@ sys_mbox_free(sys_mbox_t *mbox)
if ((NULL == mbox) || (NULL == *mbox)) {
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);
vQueueDelete((*mbox)->os_mbox);
mem_free(*mbox);
vQueueDelete((QueueHandle_t)&(*mbox)->os_mbox);
heap_caps_free(*mbox);
*mbox = NULL;
(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);
if (sem && *sem){
LWIP_DEBUGF(SYS_DEBUG, ("sem del, sem=%p\n", *sem));
vSemaphoreDelete(*sem);
if (sem){
LWIP_DEBUGF(SYS_DEBUG, ("sem del, sem=%p\n", sem));
vSemaphoreDelete((QueueHandle_t)sem);
}
if (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_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){
ESP_LOGE(TAG, "thread_sem_init: out of memory");
return 0;
}
*sem = xSemaphoreCreateBinary();
if (!(*sem)){
mem_free(sem);
ESP_LOGE(TAG, "thread_sem_init: out of memory");
// If successful, create a binary semaphore the pointer to the semaphore is returned
QueueHandle_t res = xSemaphoreCreateBinaryStatic(sem);
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;
}