Zephyr Kernel Debugging Tool Object Cores

Overview

Object Cores is a kernel debugging tool provided by Zephyr, used to identify registered objects and perform statistical operations on them. Most RTOS kernel objects have their own lists for collecting and reporting statistics, while Zephyr abstracts Object Cores to provide a unified method for collecting and retrieving information. Various kernel objects in Zephyr allow for the collection and reporting of statistics through Object Cores. Object Cores are abstracted to identify objects and statistical operations, but are not limited to use within the kernel; developers can freely integrate them into other objects in their projects. This article focuses on how to use Object Cores, while another document explains their usage within Zephyr.

Object Cores contain a pointer to a statistical descriptor that defines various operations for interacting with object statistics. Additionally, Object Cores contain a pointer to the “raw” statistics associated with that object. The raw data is the unprocessed data related to the statistics. The queried data may be “raw”, but it may also be processed in some way (e.g., averaged).

Usage

As we learned from the overview, Object Cores are used to track a certain type of object. Here we have prepared an object called mgr as an example, which supports the following interfaces:

  • mgr_open Opens a mgr object
  • mgr_close Closes a mgr object
  • mgr_fetch Executes the fetch action
  • mgr_get_stats_raw Retrieves the raw data of the mgr state
  • mgr_query_stats Queries the processed data
  • mgr_stats_reset Resets the mgr state
  • mgr_stats_enable Enables the collection of mgr state
  • mgr_stats_disable Disables the collection of mgr state

Here we use Object Cores to demonstrate how to track an opened mgr object and how to obtain the state of mgr through Object Cores.

Configuration

To use Object Cores, you need to configure CONFIG_OBJ_CORE=y. If you want to obtain the object state through Object Cores, you also need to configure CONFIG_OBJ_CORE_STATS=y. Object Cores need to be embedded in the mgr object. For convenience in distinguishing subsequent example code, the code embedding Object Cores into mgr will be wrapped with CONFIG_OBJ_CORE.

Data Structure

The mgr object has the following data structure. For mgr, each object requires a struct k_obj_core to track:

struct my_obj_type_raw_info {
    uint32_t total_fetch_time;
    uint32_t fetch_cnt;
};

struct my_obj_type_query_stats {
    uint32_t avg_fetch_time;
};

// Used to manage the <code>mgr object
struct my_test_manage{
    bool used;          // Whether the mgr object is open
    bool statistic;     // Whether to allow mgr to be counted
    struct my_obj_type_raw_info my_raw_data;    // Raw data counted by mgr

    // Insert struct k_obj_core to track this object; recommended member name is obj_core
#ifdef CONFIG_OBJ_CORE
    struct k_obj_core obj_core;
#endif
};

The API of Object Core takes struct k_obj_core as a parameter. When we insert the member name obj_core in the structure, we can access this member through K_OBJ_CORE. For example, accessing struct my_test_manage mgr’s obj_core is K_OBJ_CORE ( &mgr ). Conversely, knowing the pointer to obj_core, we can also obtain the address of mgr, for example:

struct my_test_manage *mgr;
mgr = CONTAINER_OF ( obj_core, struct my_test_manage, obj_core ) ;

Initialization

The example code is as follows:

#ifdef CONFIG_OBJ_CORE

#ifdef CONFIG_OBJ_CORE_STATS
// Implement the callback function for raw state data
static int my_obj_type_stats_raw ( struct k_obj_core *obj_core, void *stats )
{
    memcpy ( stats, obj_core-&gt;stats, sizeof ( struct my_obj_type_raw_info )) ;

    return 0;
}

// Implement the callback function for query data
static int my_obj_type_stats_query ( struct k_obj_core *obj_core, void *stats )
{
    struct my_test_manage *mgr;

    mgr = CONTAINER_OF ( obj_core, struct my_test_manage, obj_core ) ;

    return mgr_query_stats ( mgr, stats ) ;
}

// Implement the callback function for resetting state
static int my_obj_type_stats_reset ( struct k_obj_core *obj_core )
{
    struct my_test_manage *mgr;

    mgr = CONTAINER_OF ( obj_core, struct my_test_manage, obj_core ) ;
    mgr_stats_reset ( mgr ) ;

    return 0;
}

// Implement the callback function for disabling state collection
static int my_obj_type_stats_disable ( struct k_obj_core *obj_core )
{
    struct my_test_manage *mgr;

    mgr = CONTAINER_OF ( obj_core, struct my_test_manage, obj_core ) ;

    mgr_stats_disable ( mgr ) ;

    return 0;
}

// Implement the callback function for enabling state collection
static int my_obj_type_stats_enable ( struct k_obj_core *obj_core )
{
    struct my_test_manage *mgr;

    mgr = CONTAINER_OF ( obj_core, struct my_test_manage, obj_core ) ;

    mgr_stats_enable ( mgr ) ;

    return 0;
}

// Define and initialize <code>struct k_obj_core_stats_desc, specifying the length of the data and the callback functions
static struct k_obj_core_stats_desc my_obj_type_stats_desc = {
    .raw_size = sizeof ( struct my_obj_type_raw_info ) ,        // Specify the length of raw data
    .query_size = sizeof ( struct my_obj_type_query_stats ) ,    // Specify the length of query data
    .raw = my_obj_type_stats_raw,
    .query = my_obj_type_stats_query,
    .reset = my_obj_type_stats_reset,
    .disable = my_obj_type_stats_disable,
    .enable = my_obj_type_stats_enable,
};
#endif /* CONFIG_OBJ_CORE_STATS */

// Define the type of MGR
# define K_OBJ_TYPE_MY_NEW_TYPE  K_OBJ_TYPE_ID_GEN ( "MGRS" )
struct k_obj_type  my_obj_type;

void my_obj_type_init ( void )
{
    // Initialize object core to manage mgr
    z_obj_type_init ( &amp;my_obj_type, K_OBJ_TYPE_MY_NEW_TYPE,
                    offsetof ( struct my_test_manage, obj_core )) ;
#ifdef CONFIG_OBJ_CORE_STATS
    // Initialize object core stats to obtain mgr statistics
    k_obj_type_stats_init ( &amp;my_obj_type, &amp;my_obj_type_stats_desc ) ;
#endif /* CONFIG_OBJ_CORE_STATS */
}

// Call my_obj_type_init during application initialization to complete the initialization of mgr’s object core
SYS_INIT ( my_obj_type_init, APPLICATION, 0 ) ;
#endif /*CONFIG_OBJ_CORE_STATS*/

The entire process above completes the initialization of my_obj_type, which can be used to manage the bound mgr object. The initialization can be viewed in two parts: one is using z_obj_type_init to initialize the Object Core, which will create a linked list to manage the MGR objects. The other is implementing the callback functions required by struct k_obj_core_stats_desc and initializing them through k_obj_type_stats_init.

Binding Objects

In mgr_open and mgr_close, use k_obj_core_init_and_link to link the mgr object to my_obj_type. After that, by operating on my_obj_type, you can access all mgr objects. When the mgr object is closed, use k_obj_core_unlink to unlink it. Similarly, if you want to obtain data from the mgr object through the object core, you need to use k_obj_core_stats_register to register the raw data, and when the mgr object is closed, unregister it.

static void *mgr_open ( void )
{
    for ( uint32_t i=0; i&lt;MY_TEST_MGR_NUM; i++ ) {
        if ( mgrs [i].used == false ) {
            mgrs [i].used = true;
        #ifdef CONFIG_OBJ_CORE
            // Link the object
            k_obj_core_init_and_link ( K_OBJ_CORE ( &amp;mgrs [i] ) , &amp;my_obj_type ) ;
        #ifdef CONFIG_OBJ_CORE_STATS
            // Register raw data for the object
            k_obj_core_stats_register ( K_OBJ_CORE ( &amp;mgrs [i] ) ,
                  &amp;mgrs [i].my_raw_data,
                  sizeof ( mgrs [i].my_raw_data )) ;
        #endif
        #endif
            return &amp;mgrs [i];
        }
    }

    return NULL;
}

static bool mgr_close ( void* mgr )
{
    for ( uint32_t i=0; i&lt;MY_TEST_MGR_NUM; i++ ) {
        if ( &amp;mgrs [i].used == mgr ) {
        #ifdef CONFIG_OBJ_CORE
        #ifdef CONFIG_OBJ_CORE_STATS
            // Unlink
            k_obj_core_stats_deregister ( K_OBJ_CORE ( &amp;mgrs [i] )) ;
        #endif
            // Unregister
            k_obj_core_unlink ( K_OBJ_CORE ( &amp;mgrs [i] )) ;
        #endif
            memset ( &amp;mgrs [i], 0, sizeof ( struct my_test_manage )) ;
            return true;
        }
    }

    return false;
}

Accessing Objects

You can access objects using k_obj_type_walk_unlocked and k_obj_type_walk_locked. The difference between the two is whether the access is locked. First, define the access function:

int walk_op ( struct k_obj_core *obj_core, void *data )
{
    struct my_test_manage *mgr;

    mgr = CONTAINER_OF ( obj_core, struct my_test_manage, obj_core ) ;

    shell_print ( data, "%p fetch time %u cnt %u", mgr, mgr-&gt;my_raw_data.total_fetch_time, mgr-&gt;my_raw_data.fetch_cnt ) ;

    return 0;
}

Then traverse access:

// Find the object to traverse
struct k_obj_type *obj_type;
obj_type = k_obj_type_find ( K_OBJ_TYPE_MY_NEW_TYPE ) ;
// Traverse all MGR objects in an unlocked manner; each object will call walk_op once
k_obj_type_walk_unlocked ( obj_type, walk_op, shell ) ;
// Traverse all MGR objects in a locked manner; each object will call walk_op once
k_obj_type_walk_locked ( obj_type, walk_op, shell ) ;

Statistical Query

When CONFIG_OBJ_CORE_STATS=y is configured, the following APIs for statistical queries are effective:

// Get the raw state data of obj_core into stats, allowed data length is stats_len
int k_obj_core_stats_raw ( struct k_obj_core *obj_core, void *stats, size_t stats_len ) ;
// Get the state data of obj_core into stats, allowed data length is stats_len
int k_obj_core_stats_query ( struct k_obj_core *obj_core, void *stats, size_t stats_len ) ;
// Reset obj_core's state
int k_obj_core_stats_reset ( struct k_obj_core *obj_core ) ;
// Disable statistical state
int k_obj_core_stats_disable ( struct k_obj_core *obj_core ) ;
// Enable statistical state
int k_obj_core_stats_enable ( struct k_obj_core *obj_core ) ;

k_obj_type_stats_init initializes by calling the callback functions required by struct k_obj_core_stats_desc. The correspondence between the callback functions and the APIs for obtaining state is as follows:

  • raw : k_obj_core_stats_raw
  • query : k_obj_core_stats_query
  • reset : k_obj_core_stats_reset
  • disable : k_obj_core_stats_disable
  • enable : k_obj_core_stats_enable

These callbacks are not mandatory to implement; when the callback is assigned NULL, calling the corresponding API will return -ENOTSUP.

Additionally, struct k_obj_core_stats_desc also requires specifying raw_size and query_size. When executing k_obj_core_stats_raw and k_obj_core_stats_query, it will compare the passed parameter stats_len. If they do not match, it will return -EINVAL.

Example code for obtaining state:

int  status;
// Enable statistics
k_obj_core_stats_enable ( K_OBJ_CORE (( struct my_test_manage* ) mgr_handle [i] )) ;

// Get state data
status = k_obj_core_stats_query ( K_OBJ_CORE (( struct my_test_manage* ) mgr_handle [i] ) ,
                            &amp;my_stats, sizeof ( my_stats )) ;

if ( status == 0 ) {
    shell_print ( shell, "avg fetch time %u", my_stats.avg_fetch_time ) ;
}

// Get raw data
status = k_obj_core_stats_raw ( K_OBJ_CORE (( struct my_test_manage* ) mgr_handle [i] ) ,
                            &amp;my_raw, sizeof ( my_raw )) ;

if ( status == 0 ) {
    shell_print ( shell, "total fetch time %u fetch cnt", my_raw.total_fetch_time, my_raw.fetch_cnt ) ;
}

// Reset statistical data
k_obj_core_stats_reset ( K_OBJ_CORE (( struct my_test_manage* ) mgr_handle [i] )) ;

// Disable statistics
k_obj_core_stats_disable ( K_OBJ_CORE (( struct my_test_manage* ) mgr_handle [i] )) ;

References

https://docs.zephyrproject.org/3.5.0/kernel/object_cores/index.html

Leave a Comment