Question bank — FreeRTOS — task creation, priorities, xTaskCreate
Anchor the picture first — the four task states
Before the traps, pin the mental model. A task is always in exactly one of four states, and every trap on this page is really a question about which state a task is in and why.
The next figure shows when memory from vTaskDelete(NULL) is actually reclaimed — it is not freed the instant you call it.
Local glossary — every symbol used on this page
Because the parent note assumed these, here they are in plain words, so nothing below is unexplained.
Two ways to create — dynamic vs static
The parent note only showed xTaskCreate, which carves the stack and TCB out of the heap. There is a second door.
True or false — justify
A task in the Blocked state still consumes a slice of CPU each tick.
Setting a task's priority to configMAX_PRIORITIES gives it the highest possible urgency.
0 .. configMAX_PRIORITIES - 1, so the top is configMAX_PRIORITIES - 1. Passing the max value itself is an off-by-one bug that may be silently clamped.Two tasks at equal priority will run in strict alternation forever.
configUSE_TIME_SLICING = 1. They then round-robin one tick each, but a task that blocks early hands its remaining slice to the other, so it is not clockwork alternation.With configUSE_TIME_SLICING = 0, two equal-priority CPU-bound tasks still take turns each tick.
taskYIELD). A peer at the same priority can then starve indefinitely — the complementary edge case people forget.usStackDepth = 256 reserves 256 bytes of stack.
If xTaskCreate returns pdPASS, the task has already run at least once.
pdPASS only means the TCB and stack were allocated and the task was placed on the Ready list. The kernel separates allocation from the first context switch because the running context (or the not-yet-started scheduler) may be higher priority, so the new task must wait to be picked — allocation success says nothing about being scheduled.The idle task always runs at tskIDLE_PRIORITY = 0 and can never be starved.
Calling vTaskStartScheduler() returns once all tasks finish.
vTaskStartScheduler() normally never returns; it only returns if there is not enough heap to create the idle/timer task.A higher-priority task will always preempt a lower one the instant it becomes Ready.
configUSE_PREEMPTION = 1) and the CPU is not inside a critical section (interrupts masked). Inside taskENTER_CRITICAL()/taskEXIT_CRITICAL() the switch is deferred until the critical section ends, even at prio difference.Spot the error
void vTask(void *p){ doWork(); } // what breaks?::: The task function returns after doWork(). A returning task falls off the end into undefined code and corrupts the scheduler. Wrap the body in for(;;) or end with vTaskDelete(NULL).
xTaskCreate(vHigh, "hi", 128, NULL, 1, NULL);
xTaskCreate(vLow, "lo", 128, NULL, 3, NULL); // vLow does heavy math, never blocks::: The names lie: vLow was given priority 3, higher than vHigh at 1. Since bigger = more urgent and vLow never blocks, it starves vHigh completely. The intended urgency is inverted.
void vCritical(void *p){ for(;;){ readSensor(); } } // top priority, must be responsive::: No blocking call, so this top-priority loop never yields — every lower task, including idle, starves. Responsiveness comes from blocking until the next event, not from spinning. Add vTaskDelay or block on a queue/semaphore.
void isr_handler(void){ xTaskCreate(vLog, "log", 128, NULL, 2, NULL); }::: xTaskCreate is not ISR-safe and must not be called from an interrupt. Pre-create the task at startup and, from the ISR, use a ...FromISR API to signal it — see ISR-safe APIs (FromISR) and deferred interrupt handling.
xTaskCreate(vBlink, "L", 128, NULL, 1, NULL);
vTaskDelete(h); // h is uninitialised — we passed NULL for the handle::: The handle was never captured (pxCreatedTask = NULL), so h is garbage. To delete or suspend a task later you must pass &h and store the real handle.
xTaskCreate(vTask, "T", 4096, NULL, 1, &h); // "only 4 KB, plenty of heap"::: 4096 is in words, so on Cortex-M this reserves per task — four times the assumed size. On a small MCU this may return errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY.
taskENTER_CRITICAL();
vTaskDelay(pdMS_TO_TICKS(10)); // "just a short pause inside the critical section"
taskEXIT_CRITICAL();::: Blocking calls like vTaskDelay are illegal inside a critical section — you cannot yield the CPU while interrupts are masked. This deadlocks or asserts. Do timing outside the critical region.
Why questions
Why does FreeRTOS express stack size in words instead of bytes?
Why is "bigger number = more urgent" a trap even for experienced programmers?
nice, several classic RTOSes) treat smaller numbers as more important, so muscle memory inverts the meaning. FreeRTOS simply runs the numerically largest ready priority.Why must a real-time high-priority task spend most of its life Blocked?
Why store the TaskHandle_t output even if you don't need it right now?
Why does pdMS_TO_TICKS(500) exist instead of just writing the tick count?
configTICK_RATE_HZ) varies per project. The macro converts milliseconds to ticks — — so the code reads in real time and stays portable when the tick rate changes.Why does a critical section defer preemption even when configUSE_PREEMPTION = 1?
taskENTER_CRITICAL() masks interrupts (up to a threshold), and preemption is driven by the tick interrupt and other IRQs. With those masked, the scheduler literally cannot fire until you exit — so critical sections must be kept as short as possible.Why does each task need its own stack rather than sharing one?
Why can equal-priority tasks fairly share the CPU but unequal ones cannot?
configUSE_TIME_SLICING = 1). A lower-priority task is simply never chosen while any higher one is Ready, so "fairness" only exists within a priority level.Edge cases
What happens if every task blocks simultaneously (nothing is Ready)?
How does configUSE_TICKLESS_IDLE = 1 change when the idle task and low-power sleep behave?
What if you create a task before calling vTaskStartScheduler()?
main() begin executing only after vTaskStartScheduler().What if two tasks at different priorities both become Ready at the exact same tick?
What happens if usStackDepth is set too small for the task's actual usage?
configCHECK_FOR_STACK_OVERFLOW to catch it via a hook instead of a mystery crash.What does vTaskDelete(NULL) do, and when is its memory actually freed?
NULL to delete itself. Its TCB/stack are not freed immediately — a running task cannot free its own stack — so the kernel just marks it and the idle task reclaims that memory on its next turn (see the timeline figure). Another reason idle must not starve.Can a task change its own priority at runtime, and what's the risk?
vTaskPrioritySet. The risk is starving others by raising itself, or creating priority-inversion scenarios if it interacts with shared resources — see Priority Inversion and Priority Inheritance.If you built with configSUPPORT_DYNAMIC_ALLOCATION = 0, what breaks?
xTaskCreate won't compile/link — there is no heap to carve from. You must use xTaskCreateStatic with your own stack and TCB storage (configSUPPORT_STATIC_ALLOCATION = 1).Recall One-line self-test
Bigger number = more urgent, stack is in words, tasks never return, high-priority tasks must block, and xTaskCreate is not ISR-safe. If you can justify all five, you own this topic.