I am currently working on a framework which uses the FreeRTOS kernel under the hood.
The framework itself creates some tasks and the users of the framework can add other tasks on top of it.
I need to combine the user tasks’ priorities with the framework ones. To do so, I first assign the priorities as given by the user and change them after having computed the combined priorities. The whole process happens before the scheduler has been started, but this often causes the application to crash because calling vTaskPrioritySet might call taskYIELD_IF_USING_PREEMPTION without checking if the scheduler is running.
So my question is, is it possible somehow to set the priority of a task after creation and before the scheduler is started? And, if not, what is the reason behind it?
If your framework is intended to expose FreeRTOS and allow the user full access to FreeRTOS, then are you sure you want your framework to hide its manipulation of the user’s task priorities? Hiding it might cause some user confusion and perhaps even some errors. What if instead you provided a macro like frameworkTRANSLATE_USER_TASK_PRIORITY( userPriority ) for transparency? The user could use it in their calls to xTaskCreate() before the kernel starts. They could also use it their own calls to vTaskPrioritySet().
More directly to your question, probably nobody imagined there could be a use case for changing a task’s priority after it was created at a user-specified priority and before the kernel starts.
thank you for your quick reply.
FreeRTOS is encapsulated in an abstraction layer and therefore not exposed to the user, who is also not allowed to manipulate priorities or creating tasks after the scheduler is started.
The problem is that I need to know all the user tasks and their priorities in order to combine them and reassign them before the scheduler starts. Therefore a function/macro to translate the user priority would not help, as each further task instantiation might require a change of the already assigned priorities.
I played with the vTaskPrioritySet implementation a bit and it seems that changing
if( xYieldRequired != pdFALSE )
{
/* The running task priority is set down. Request the task to yield. */
taskYIELD_TASK_CORE_IF_USING_PREEMPTION( pxTCB );
}
into
if( (xYieldRequired != pdFALSE) && (xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED) )
{
/* The running task priority is set down. Request the task to yield. */
taskYIELD_TASK_CORE_IF_USING_PREEMPTION( pxTCB );
}
fixes the issue.
My plan for now is to use this in a separate branch. I wonder if it is worth opening a PR for this on the official repo.
However, I worry the concept might be too error prone. The concept being changing a task’s priority after it is created with a user-specified priority but before the kernel has started.
FreeRTOS requires compile-time configuration of the total number of priorities to support. Thus any priority-manipulation strategy either (1) fails to maintain relative priorities among all user tasks or (2) restricts which priorities are available for user tasks. Case 1 is undesirable because it can break user applications. Case 2 is acceptable but probably negates the need for manipulating user task priorities. If your framework must limit the priorities available to user tasks, then it might as well provide specific, static limits/controls that enforce the framework’s rules.
I agree that something seems confusing about the requirements, as the only restrictions I can think of are that there are some “system” tasks that need to be higher in priority than all “user” task, and perhaps lower or between classed of user priorities. All that can be handled by a static priority number mapping.
Unless the system isn’t being given actual priority levels, but only relative priorities between tasks, which would seem to error prone, and subject to not being able to fit the requirements within the compile time fixed number.
I would say a fix to the issue might be worthwhile, but probably should be a test aroung the full section of:
if( xYieldRequired != pdFALSE )
{
/* The running task priority is set down. Request the task to yield. */
taskYIELD_TASK_CORE_IF_USING_PREEMPTION( pxTCB );
}
else
{
#if ( configNUMBER_OF_CORES > 1 )
if( xYieldForTask != pdFALSE )
{
/* The priority of the task is being raised. If a running
* task has priority lower than this task, it should yield
* for this task. */
taskYIELD_ANY_CORE_IF_USING_PREEMPTION( pxTCB );
}
else
#endif /* if ( configNUMBER_OF_CORES > 1 ) */
{
mtCOVERAGE_TEST_MARKER();
}
}
and the question becomes, not only should it not trigger a yield before the scheduler is started, should having the scheduler suspended delay the yield until the scheduler is resumed?
Hi guys, thanks for the valuable feedback.
I must admit that my use-case is pretty unusual, so let me try to clarify.
I develop the framework without knowing how the user code looks like. The user can decide how many tasks he/she creates and some of them come from the framework, so that the total number is known at compile-time. As @richard-damon already guessed, the user assigns priorities which are relative to (higher, lower or in between) those of the framework. Therefore I need to recompute all of them at run-time to maintain the relative priorities among user tasks, combine them with the framework’s priorities, and finally reassign them before starting the scheduler.
Beside my use-case being a bit complex, I think that a check is worthwhile to avoid unexpected crashes in case the function is called before starting the scheduler, also because there is no mention of it in the API reference.
Regarding the question about the scheduler suspension, I think the yield should be delayed since the priority configuration changed after a call to vTaskpriorityset and it does not make sense to trigger a task switch while the scheduler is suspended.
As others already suggested, you can consider several other approaches -
Place more restrictions on what priorities user tasks can have.
Place a restriction that no user task can have maximum priority (i.e. configMAX_PRIORITIES - 1) and then create one task with max priority in the framework which can be used for priority re-assignments. If you are using FreeRTOS timers, you can create the timer task at the highest priority and use configUSE_DAEMON_TASK_STARTUP_HOOK for priority re-assignments - Hook Functions - FreeRTOS™.
It seems it is still a fact that your system has a fixed number of priorities available, since that is a built in limitation of FreeRTOS, as the number of priorities is a compile time constant.
You can then make sure you define that number to be high enough to provide the number you want to allow for your “user” tasks, and then add some FIXED priorities for your framework. Each of the “gaps” between your framework priority levels will have a fixed number of priorities in it, which you provide a mapping from those user levels to the FreeRTOS levels, which will be a fixed transformation. The one thing this doesn’t provide is the ability for the user to specify priorities not just as a number, but as greater than or less than some other already created task, but that method leads to a real possibility that the user requests too many priority levels, so isn’t really a good method to combine with FreeRTOS which has a fixed number of priority levels.
In addition to what @richard-damon wrote, it is also worth noting that more priority levels also yield larger footprint and longer task selection time as more lists need to be allocated and traversed.
But the cost of an empty list isn’t enormous, and if you use the optimized selection (which requires having no more than 32 levels) you don’t actually walk the lists very often.
Given he is adding an abstraction layer with his framework, the cost of less than about a kilo-byte for 32 levels may be reasonable. (If he even needs to go that high).
I already considered to have a relative high number of priorities and reserve fixed priority ranges for the user. But, as @RAc mentioned, I’d like to avoid the additional memory consumption.
The second solution from @aggarg would also be suitable. I thought about something similar but I was not aware of the hook functions. I’ll definitely have a look at it.
In general, I think there are many ways to get around not being able to use vTaskPrioritySet before the scheduler. I will settle for one of them, which sort of answers my first question.
Regarding my second question (why vTaskPrioritySet cannot be used before starting the scheduler), I agree with Jeff that probably nobody foresaw this use-case when implementing vTaskPrioritySet. After all, the priority is a task property set by the user on creation, so the user should be able to set it again even if the scheduler is not running. vTaskPrioritySet already does a great job doing it and it is only missing a scheduler state check, so I still think that having that check would be the cleanest solution in the long run and would improve the API.