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
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
if MINIMAL_LIBC

View file

@ -13,6 +13,7 @@
#include <linker/linker-defs.h>
#include <sys/util.h>
#include <sys/errno_private.h>
#include <sys/heap_listener.h>
#include <sys/libc-hooks.h>
#include <syscall_handler.h>
#include <app_memory/app_memdomain.h>
@ -291,6 +292,10 @@ void *_sbrk(intptr_t count)
if ((heap_sz + count) < MAX_HEAP_SIZE) {
heap_sz += count;
ret = ptr;
#ifdef CONFIG_NEWLIB_LIBC_HEAP_LISTENER
heap_listener_notify_resize(HEAP_ID_LIBC, ptr, (char *)ptr + count);
#endif
} else {
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_HEAP_LISTENER heap_listener.c)
zephyr_library_include_directories(
${ZEPHYR_BASE}/kernel/include
${ZEPHYR_BASE}/arch/${ARCH}/include

View file

@ -51,6 +51,12 @@ config SYS_HEAP_RUNTIME_STATS
help
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
prompt "Supported heap sizes"
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