Hi!
After my last post and pr I have continued to work with the tracing API and found a few more details that I would like to address. Please let me know if you would be interested in PRs.
As a little background: I am in the process of re-working the practical exercises of the embedded systems course for my university, and I am in the process of building a simple tracer, so students can visualize the operation of FreeRTOS. I plan to open-source the tracer when completed. The course will likely also be published when in a presentable state.
Idle/Timer Service task identification
Problem
When started, the kernel creates a timer service task and idle task for every core, using the normal task creation API. This causes the normal traceTASK_CREATE
hooks to be called. There is, however, currently no way for the tracer to identify these specific tasks as the system tasks. Their name is configurable and not guaranteed to be unique so it cannot reliably be used to identify them.
A tracer could require the user to manually identify them using xTaskGetIdletTaskHandle
and xTimerGetTimerDaemonTaskHandle
, but this is error-prone and annoying because it has to be done in some thread after the kernel has started.
Implementation Idea Option 1:
It would be nicest to have the âtypeâ of task (normal/timer/idle) reported when the traceTASK_CREATE
macro is called. An âextendedâ version of this macro could be included that
Unfortunately this would be rather difficult to implement: The âspecialâ tasks are created using the public task creation functions, so no extra information can be passed along. There is also no clear way to identify if a given task is a timer or idle task at the location of the traceTASK_CREATE
macro as far as I can tell. I guess it would be possible to introduce a static/global variable to track this, but that is also messy.
Implementation Idea Option 2:
Alternatively, after the task is created, new trace macros could be introduced that identify a given task as the timer service or idle task:
#define traceTASK_IS_IDLE_TASK( pxNewTCB, xCoreID )
#define traceTASK_IS_TIMER_TASK( pxNewTCB )
With this style it could also be useful to introduce a macro that identifies the timer cmd queue:
#define traceQUEUE_IS_TIMER_QUEUE( xTimerQueue )
This change would be completely backwards compatible and not require any refactoring.
Notfiy does not expose xClearCountOnExit
Problem
This problem is very similar to the discussion in the previous post about queue send tracing. The traceTASK_NOTIFY_TAKE
macro does not expose the value of xClearCountOnExit
, so a tracer cannot determine the state of the task notification value after they have been taken - especially because it is updated after the macro is called.
Implementation Idea
As was done for traceQUEUE_SEND
, I think it makes sense to introduce a traceTASK_NOTIFY_TAKE_EXT
hook that exposes the current value of ulNotifiedValue[ uxIndexToWaitOn ]
and the value of xClearCountOnExit
:
#ifndef traceTASK_NOTIFY_TAKE_EXT
#define traceTASK_NOTIFY_TAKE_EXT( uxIndexToWaitOn, ulNotifiedValue, xClearCountOnExit ) traceTASK_NOTIFY_TAKE( uxIndexToWaitOn )
#endif
// in ulTaskGenericNotifyTake, line 7682:
taskENTER_CRITICAL();
{
ulReturn = pxCurrentTCB->ulNotifiedValue[ uxIndexToWaitOn ];
traceTASK_NOTIFY_TAKE_EXT( uxIndexToWaitOn, ulReturn, xClearCountOnExit );
if( ulReturn != 0U )
{
if( xClearCountOnExit != pdFALSE )
{
...
TaskNumber vs TCBNumber API confusion
Problem
The TCB currently contains two tracing numbers:
typedef struct tskTaskControlBlock
{
// --- snip ---
#if ( configUSE_TRACE_FACILITY == 1 )
UBaseType_t uxTCBNumber; /**< Stores a number that increments each time a TCB is created. It allows debuggers to determine when a task has been deleted and then recreated. */
UBaseType_t uxTaskNumber; /**< Stores a number specifically for use by third party trace code. */
#endif
// --- snip ---
} tskTCB;
The comments here are accurate: The uxTaskNumber
member is not used by the kernel and can be set/retrieved by the user with the following two methods:
UBaseType_t uxTaskGetTaskNumber( TaskHandle_t xTask );
void vTaskSetTaskNumber( TaskHandle_t xTask, const UBaseType_t uxHandle );
The uxTCBNumber
member is initialized on task creation to a unique ID which is created by incrementing a global counter that is (confusingly) called uxTaskNumber
.
The public API confusion starts with vTaskGetInfo
, which in the returned TaskStatus_t
struct provides a field called xTaskNumber
:
typedef struct xTASK_STATUS
{
// --- snip ---
UBaseType_t xTaskNumber; /* A number unique to the task. */
// --- snip ---
} TaskStatus_t;
While the comment makes sense, the name is rather confusing because this field is used to return the pxTCB->uxTCBNumber
member:
pxTaskStatus->xTaskNumber = pxTCB->uxTCBNumber;
This is rather confusing:
- Call vTaskSetTaskNumber(mytask, 42);
- uxTaskGetNumber
will return 42.
- The xTaskNumber
field of a task status struct, when queried, will return
some other number.
Also it is rather inefficient for tracers that currently the only API to access the value of uxTCBNumber
outside of tasks.c
is through vTaskGetInfo
, requiring all status struct members to be fetched.
Implementation Idea: Provide a method to get uxTCBNumber
directly
I suggest adding a dedicated uxTaskGetTCBNumber
function to provide access to
the uxTCBNumber
TCB member:
#if ( configUSE_TRACE_FACILITY == 1 )
UBaseType_t uxTaskGetTCBNumber( TaskHandle_t xTask )
{
UBaseType_t uxReturn;
TCB_t const * pxTCB;
traceENTER_uxTaskGetTCBNumber( xTask );
if( xTask != NULL )
{
pxTCB = xTask;
uxReturn = pxTCB->uxTCBNumber;
}
else
{
uxReturn = 0U;
}
traceRETURN_uxTaskGetTCBNumber( uxReturn );
return uxReturn;
}
#endif /* configUSE_TRACE_FACILITY */
This allows tracer code to access the unique task TCB ID in other places except trace hooks inside tasks.c
.
Implementation Idea: Cleanup the task status struct.
I think it makes sense for vTaskGetInfo
to return both the TCB number and task number and be less confusing, but would look for your input on how this should be done best.
I would assume changing the behavior of vTaskGetInfo
to return TCB->uxTaskNumber
in xTaskNumber
and introducing a new xTCBNumber
field is a breaking change and not desired? Maybe updating the documentation of the xTaskNumber
info struct member to be very clear that this is a different task number than the one that can be set/gotten using the tracing functions, and introducing a second variable called xUserTaskNumber
is acceptable? It is still rather confusing that wayâŚ
Implementation Idea: Rename the global uxTaskNumber
counter to uxTCBNumber
This is absolutely not necessary, so again I am looking for your feedback if renaming the counter that is used to assign TCB numbers from uxTaskNumber
to uxTCBNumber
is acceptable. Some of its documentation references that it is used by kernel-aware debuggers, so presumably this would break things?
Queue Set identification
Problem
Queue Sets use an extra queue to track the state of all member queues. At the moment, this queue is created with queue type queueQUEUE_TYPE_SET
, but this has he same value as queueQUEUE_TYPE_BASE
:
#define queueQUEUE_TYPE_BASE ( ( uint8_t ) 0U )
#define queueQUEUE_TYPE_SET ( ( uint8_t ) 0U )
#define queueQUEUE_TYPE_MUTEX ( ( uint8_t ) 1U )
#define queueQUEUE_TYPE_COUNTING_SEMAPHORE ( ( uint8_t ) 2U )
#define queueQUEUE_TYPE_BINARY_SEMAPHORE ( ( uint8_t ) 3U )
#define queueQUEUE_TYPE_RECURSIVE_MUTEX ( ( uint8_t ) 4U )
This makes it impossible for tracers to properly identify queue sets as queue sets.
Proposed Solution
As far as I can tell, the queueQUEUE_TYPE
macros and their inclusion in the queue struct are only present for tracing, and changing queueQUEUE_TYPE_SET
to be of value 5U
does not break anything in the kernel and the CMock
test suite continues to pass.
I am unsure, however, if this is an acceptable breaking change, and if this could only be implemented with some sort of backwards compatibility in mind. Possible the queue set type could be 0x80
, and all calls to âlegacyâ tracing hooks could mask the queue type with 0x7F
? Or is this kind of breaking change acceptable?
It would also be possible to introduce a new separate hook to identify queues as queue sets traceQUEUE_IS_QUEUE_SET
but that seems rather messy considering that there already is a mechanism for tracing the type of a queue.
Adding an equivalent of uxTCBNumber
to other FreeRTOS constructs
Problem
Currently, only tasks contain both a uxTaskNumber
that is set by the tracer and a uxTCBNumber
that is automatically uniquely assigned by the kernel.
The uxTCBNumber
is rather useful for tracers, as it provides a unique ID that a given task can be identified with.
All other constructs (queues/semaphores/mutex/queue set, stream/message buffers, timers, event groups) only feature a number that has to be manually set by the tracer.
Proposed Solution
Add a unique ID to queues, stream buffers, timers, and event groups that mimics that functions as the uxTCBNumber
does for tasks: A global counter is incremented to generate new IDs for each new construct.
A new function (gated being configUSE_TRACE_FACILITY
) would be added for each of the above to allow the ID to be queried.
As to what this field should be named, I am open to suggestions. id
? Stick with the same style as before and use MCBNumber
for queues?
I would use separate counters for queues, stream buffers, timers, and event groups but share the timer for each âsubtypeâ - In other words, Queues, semaphores, mutex, etc would share a counter and never have the same IDs. This seems to be the most logical behavior, as, sticking with the example of queues, most trace events are shared between all queue types. If Semaphores and queues had separate ID counters, each one of those shared tracing hooks would have to use the queue type and queue ID to uniquely identify the queue.
Having to assigning these unique IDs in the tracer is not a huge problem, but including it into the kernel seems sensible because it creates parity between all âobjectsâ that a tracer may track. Furthermore, it opens up the ânumberâ struct member for the tracer to store other metadata. One could imagine enabling/disabling tracing on a per-queue basis, or assigning queues to different categories for example.
The only downside I see is a minimal increase in memory usage (1 UBaseType_t
per construct, if configUSE_TRACE_FACILITY == 1
).
Sorry for the long message and trouble I am causing. I put this all together into a long post because I did not want to spam the form with multiple posts.
Please let me know what you think!
All the best,
Philipp