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:
Damian Krolik 2021-12-16 15:30:49 +01:00 committed by Christopher Friedt
commit 3aedda9852
10 changed files with 270 additions and 0 deletions

108
include/sys/heap_listener.h Normal file
View 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 */

View file

@ -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

View file

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

View file

@ -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

View file

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

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

View file

@ -0,0 +1,4 @@
CONFIG_ZTEST=y
CONFIG_NEWLIB_LIBC=y
CONFIG_NEWLIB_LIBC_NANO=n
CONFIG_NEWLIB_LIBC_HEAP_LISTENER=y

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

View file

@ -0,0 +1,4 @@
tests:
libraries.libc.newlib.heap_listener:
tags: clib newlib
filter: TOOLCHAIN_HAS_NEWLIB == 1