lib: os: add heap event listener
* add generic heap event listener module that can be used for notifying an application of heap-related events * use the listener module in newlib libc hooks * add a unit test Signed-off-by: Damian Krolik <damian.krolik@nordicsemi.no>
This commit is contained in:
parent
16571db029
commit
3aedda9852
10 changed files with 270 additions and 0 deletions
108
include/sys/heap_listener.h
Normal file
108
include/sys/heap_listener.h
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021 Nordic Semiconductor ASA
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef ZEPHYR_INCLUDE_SYS_HEAP_LISTENER_H
|
||||||
|
#define ZEPHYR_INCLUDE_SYS_HEAP_LISTENER_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <sys/slist.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
struct heap_listener {
|
||||||
|
/** Singly linked list node */
|
||||||
|
sys_snode_t node;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identifier of the heap whose events are listened.
|
||||||
|
*
|
||||||
|
* It can be a heap pointer, if the heap is represented as an object,
|
||||||
|
* or 0 in the case of the global libc heap.
|
||||||
|
*/
|
||||||
|
uintptr_t heap_id;
|
||||||
|
|
||||||
|
/** Function called when the listened heap is resized */
|
||||||
|
void (*resize_cb)(void *old_heap_end, void *new_heap_end);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Register heap event listener
|
||||||
|
*
|
||||||
|
* Add the listener to the global list of heap listeners that can be notified by
|
||||||
|
* different heap implementations upon certain events related to the heap usage.
|
||||||
|
*
|
||||||
|
* @param listener Pointer to the heap_listener object
|
||||||
|
*/
|
||||||
|
void heap_listener_register(struct heap_listener *listener);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Unregister heap event listener
|
||||||
|
*
|
||||||
|
* Remove the listener from the global list of heap listeners that can be
|
||||||
|
* notified by different heap implementations upon certain events related to the
|
||||||
|
* heap usage.
|
||||||
|
*
|
||||||
|
* @param listener Pointer to the heap_listener object
|
||||||
|
*/
|
||||||
|
void heap_listener_unregister(struct heap_listener *listener);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Notify listeners of heap resize event
|
||||||
|
*
|
||||||
|
* Notify registered heap event listeners with matching heap identifier that the
|
||||||
|
* heap has been resized.
|
||||||
|
*
|
||||||
|
* @param heap_id Heap identifier
|
||||||
|
* @param old_heap_end Address of the heap end before the change
|
||||||
|
* @param new_heap_end Address of the heap end after the change
|
||||||
|
*/
|
||||||
|
void heap_listener_notify_resize(uintptr_t heap_id, void *old_heap_end, void *new_heap_end);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Construct heap identifier from heap pointer
|
||||||
|
*
|
||||||
|
* Construct a heap identifer from a pointer to the heap object, such as
|
||||||
|
* sys_heap.
|
||||||
|
*
|
||||||
|
* @param heap_pointer Pointer to the heap object
|
||||||
|
*/
|
||||||
|
#define HEAP_ID_FROM_POINTER(heap_pointer) ((uintptr_t)heap_pointer)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Libc heap identifier
|
||||||
|
*
|
||||||
|
* Identifier of the global libc heap.
|
||||||
|
*/
|
||||||
|
#define HEAP_ID_LIBC ((uintptr_t)0)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Define heap event listener object
|
||||||
|
*
|
||||||
|
* Sample usage:
|
||||||
|
* @code
|
||||||
|
* void on_heap_resized(void *old_heap_end, void *new_heap_end)
|
||||||
|
* {
|
||||||
|
* LOG_INF("Libc heap end moved from %p to %p", old_heap_end, new_heap_end);
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* HEAP_LISTENER_DEFINE(my_listener, HEAP_ID_LIBC, on_heap_resized);
|
||||||
|
* @endcode
|
||||||
|
*
|
||||||
|
* @param name Name of the heap event listener object
|
||||||
|
* @param _heap_id Identifier of the heap to be listened
|
||||||
|
* @param _resize_cb Function to be called when the listened heap is resized
|
||||||
|
*/
|
||||||
|
#define HEAP_LISTENER_DEFINE(name, _heap_id, _resize_cb) \
|
||||||
|
struct heap_listener name = { .heap_id = _heap_id, .resize_cb = _resize_cb }
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* ZEPHYR_INCLUDE_SYS_HEAP_LISTENER_H */
|
|
@ -110,6 +110,13 @@ config NEWLIB_LIBC_FLOAT_SCANF
|
||||||
Build with floating point scanf enabled. This will increase the size of
|
Build with floating point scanf enabled. This will increase the size of
|
||||||
the image.
|
the image.
|
||||||
|
|
||||||
|
config NEWLIB_LIBC_HEAP_LISTENER
|
||||||
|
bool "Notify heap listeners of newlib libc heap events"
|
||||||
|
select HEAP_LISTENER
|
||||||
|
help
|
||||||
|
Notify registered heap listeners upon certain events related to the newlib
|
||||||
|
libc heap usage, such as the heap resize.
|
||||||
|
|
||||||
endif # NEWLIB_LIBC
|
endif # NEWLIB_LIBC
|
||||||
|
|
||||||
if MINIMAL_LIBC
|
if MINIMAL_LIBC
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
#include <linker/linker-defs.h>
|
#include <linker/linker-defs.h>
|
||||||
#include <sys/util.h>
|
#include <sys/util.h>
|
||||||
#include <sys/errno_private.h>
|
#include <sys/errno_private.h>
|
||||||
|
#include <sys/heap_listener.h>
|
||||||
#include <sys/libc-hooks.h>
|
#include <sys/libc-hooks.h>
|
||||||
#include <syscall_handler.h>
|
#include <syscall_handler.h>
|
||||||
#include <app_memory/app_memdomain.h>
|
#include <app_memory/app_memdomain.h>
|
||||||
|
@ -291,6 +292,10 @@ void *_sbrk(intptr_t count)
|
||||||
if ((heap_sz + count) < MAX_HEAP_SIZE) {
|
if ((heap_sz + count) < MAX_HEAP_SIZE) {
|
||||||
heap_sz += count;
|
heap_sz += count;
|
||||||
ret = ptr;
|
ret = ptr;
|
||||||
|
|
||||||
|
#ifdef CONFIG_NEWLIB_LIBC_HEAP_LISTENER
|
||||||
|
heap_listener_notify_resize(HEAP_ID_LIBC, ptr, (char *)ptr + count);
|
||||||
|
#endif
|
||||||
} else {
|
} else {
|
||||||
ret = (void *)-1;
|
ret = (void *)-1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,6 +45,8 @@ zephyr_sources_ifdef(CONFIG_REBOOT reboot.c)
|
||||||
|
|
||||||
zephyr_sources_ifdef(CONFIG_SHARED_MULTI_HEAP shared_multi_heap.c)
|
zephyr_sources_ifdef(CONFIG_SHARED_MULTI_HEAP shared_multi_heap.c)
|
||||||
|
|
||||||
|
zephyr_sources_ifdef(CONFIG_HEAP_LISTENER heap_listener.c)
|
||||||
|
|
||||||
zephyr_library_include_directories(
|
zephyr_library_include_directories(
|
||||||
${ZEPHYR_BASE}/kernel/include
|
${ZEPHYR_BASE}/kernel/include
|
||||||
${ZEPHYR_BASE}/arch/${ARCH}/include
|
${ZEPHYR_BASE}/arch/${ARCH}/include
|
||||||
|
|
|
@ -51,6 +51,12 @@ config SYS_HEAP_RUNTIME_STATS
|
||||||
help
|
help
|
||||||
Gather system heap runtime statistics.
|
Gather system heap runtime statistics.
|
||||||
|
|
||||||
|
config HEAP_LISTENER
|
||||||
|
bool "Enable heap listener"
|
||||||
|
help
|
||||||
|
Enable API for registering and notifying listeners of certain
|
||||||
|
events related to a heap usage, such as the heap resize.
|
||||||
|
|
||||||
choice
|
choice
|
||||||
prompt "Supported heap sizes"
|
prompt "Supported heap sizes"
|
||||||
depends on !64BIT
|
depends on !64BIT
|
||||||
|
|
43
lib/os/heap_listener.c
Normal file
43
lib/os/heap_listener.c
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021 Nordic Semiconductor ASA
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <spinlock.h>
|
||||||
|
#include <sys/heap_listener.h>
|
||||||
|
|
||||||
|
static struct k_spinlock heap_listener_lock;
|
||||||
|
static sys_slist_t heap_listener_list = SYS_SLIST_STATIC_INIT(&heap_listener_list);
|
||||||
|
|
||||||
|
void heap_listener_register(struct heap_listener *listener)
|
||||||
|
{
|
||||||
|
k_spinlock_key_t key = k_spin_lock(&heap_listener_lock);
|
||||||
|
|
||||||
|
sys_slist_append(&heap_listener_list, &listener->node);
|
||||||
|
|
||||||
|
k_spin_unlock(&heap_listener_lock, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
void heap_listener_unregister(struct heap_listener *listener)
|
||||||
|
{
|
||||||
|
k_spinlock_key_t key = k_spin_lock(&heap_listener_lock);
|
||||||
|
|
||||||
|
sys_slist_find_and_remove(&heap_listener_list, &listener->node);
|
||||||
|
|
||||||
|
k_spin_unlock(&heap_listener_lock, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
void heap_listener_notify_resize(uintptr_t heap_id, void *old_heap_end, void *new_heap_end)
|
||||||
|
{
|
||||||
|
struct heap_listener *listener;
|
||||||
|
k_spinlock_key_t key = k_spin_lock(&heap_listener_lock);
|
||||||
|
|
||||||
|
SYS_SLIST_FOR_EACH_CONTAINER(&heap_listener_list, listener, node) {
|
||||||
|
if (listener->heap_id == heap_id && listener->resize_cb != NULL) {
|
||||||
|
listener->resize_cb(old_heap_end, new_heap_end);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
k_spin_unlock(&heap_listener_lock, key);
|
||||||
|
}
|
8
tests/lib/newlib/heap_listener/CMakeLists.txt
Normal file
8
tests/lib/newlib/heap_listener/CMakeLists.txt
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
cmake_minimum_required(VERSION 3.20.0)
|
||||||
|
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
|
||||||
|
project(heap_listener)
|
||||||
|
|
||||||
|
FILE(GLOB app_sources src/*.c)
|
||||||
|
target_sources(app PRIVATE ${app_sources})
|
4
tests/lib/newlib/heap_listener/prj.conf
Normal file
4
tests/lib/newlib/heap_listener/prj.conf
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
CONFIG_ZTEST=y
|
||||||
|
CONFIG_NEWLIB_LIBC=y
|
||||||
|
CONFIG_NEWLIB_LIBC_NANO=n
|
||||||
|
CONFIG_NEWLIB_LIBC_HEAP_LISTENER=y
|
83
tests/lib/newlib/heap_listener/src/main.c
Normal file
83
tests/lib/newlib/heap_listener/src/main.c
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021 Nordic Semiconductor ASA.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <sys/heap_listener.h>
|
||||||
|
#include <zephyr.h>
|
||||||
|
#include <ztest.h>
|
||||||
|
|
||||||
|
#include <malloc.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Function used by malloc() to obtain or free memory to the system.
|
||||||
|
* Returns the heap end before applying the change.
|
||||||
|
*/
|
||||||
|
extern void *sbrk(intptr_t count);
|
||||||
|
|
||||||
|
static uintptr_t current_heap_end(void)
|
||||||
|
{
|
||||||
|
return (uintptr_t)sbrk(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ptrdiff_t heap_difference;
|
||||||
|
|
||||||
|
static void heap_resized(void *old_heap_end, void *new_heap_end)
|
||||||
|
{
|
||||||
|
heap_difference += ((char *)new_heap_end - (char *)old_heap_end);
|
||||||
|
}
|
||||||
|
|
||||||
|
static HEAP_LISTENER_DEFINE(listener, HEAP_ID_LIBC, heap_resized);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Test that heap listener is notified when libc heap size changes.
|
||||||
|
*
|
||||||
|
* This test calls the malloc() and free() followed by malloc_trim() functions
|
||||||
|
* and verifies that the heap listener is notified of allocating or returning
|
||||||
|
* memory from the system.
|
||||||
|
*/
|
||||||
|
void test_alloc_and_trim(void)
|
||||||
|
{
|
||||||
|
uintptr_t saved_heap_end;
|
||||||
|
void *ptr;
|
||||||
|
|
||||||
|
TC_PRINT("Allocating memory...\n");
|
||||||
|
|
||||||
|
heap_listener_register(&listener);
|
||||||
|
saved_heap_end = current_heap_end();
|
||||||
|
ptr = malloc(4096);
|
||||||
|
|
||||||
|
TC_PRINT("Total heap size change: %zi\n", heap_difference);
|
||||||
|
|
||||||
|
zassert_true(heap_difference > 0, "Heap increase not detected");
|
||||||
|
zassert_equal(current_heap_end() - saved_heap_end, heap_difference,
|
||||||
|
"Heap increase not detected");
|
||||||
|
|
||||||
|
TC_PRINT("Freeing memory...\n");
|
||||||
|
|
||||||
|
heap_difference = 0;
|
||||||
|
saved_heap_end = current_heap_end();
|
||||||
|
free(ptr);
|
||||||
|
malloc_trim(0);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* malloc_trim() may not free any memory to the system if there is not enough to free.
|
||||||
|
* Therefore, do not require that heap_difference < 0.
|
||||||
|
*/
|
||||||
|
zassert_equal(current_heap_end() - saved_heap_end, heap_difference,
|
||||||
|
"Heap decrease not detected");
|
||||||
|
|
||||||
|
heap_listener_unregister(&listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_main(void)
|
||||||
|
{
|
||||||
|
ztest_test_suite(newlib_libc_heap_listener,
|
||||||
|
ztest_unit_test(test_alloc_and_trim));
|
||||||
|
|
||||||
|
ztest_run_test_suite(newlib_libc_heap_listener);
|
||||||
|
}
|
4
tests/lib/newlib/heap_listener/testcase.yaml
Normal file
4
tests/lib/newlib/heap_listener/testcase.yaml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
tests:
|
||||||
|
libraries.libc.newlib.heap_listener:
|
||||||
|
tags: clib newlib
|
||||||
|
filter: TOOLCHAIN_HAS_NEWLIB == 1
|
Loading…
Add table
Add a link
Reference in a new issue