3.2 KiB
K07 — TaskHandle_t, explicit start and static trampoline
Position
- Series: FreeRTOS C++
- Lesson: L06, card K07
- Duration: 30 minutes
- One C++ owner object plus one raw verifier task
- Dynamic FreeRTOS TCB/stack from real
heap_4.c
K02 introduced CRTP in a larger first-task experiment. K07 revisits the task boundary after the memory/resource cards and demands a precise identity proof: object, TCB/handle and task stack are separate things with separate lifetimes.
Outcome
After 30 minutes the student can annotate every argument of xTaskCreate,
explain why construction and start are separate, trace void* back to a typed
member body, and prove stable object address without virtual dispatch.
Lesson plan — 30 minutes
| Time | Mode | Evidence |
|---|---|---|
| 0–5 | predict addresses | object vs handle/TCB vs task stack |
| 5–10 | explicit start | constructor heap delta 0; start creates kernel objects |
| 10–16 | C boundary | annotate entry, name, words, context, priority, handle-out |
| 16–21 | trampoline | raw context equals object; static cast; private run() |
| 21–26 | lifecycle | ready/running/completed, handle cleared, self-delete |
| 26–30 | GDB/exit | stack local inside bounds; no move/vtable/destructor delete |
Stable identity contract
object_before == object_after_start
== xTaskCreate context
== trampoline context
== this in run
== completed object
handle_after_start == current handle == TCB address
handle_after_start != C++ object address
local variable address lies in task stack bounds
Why explicit start
The derived object is not complete while its base constructor runs. Starting
there could let the scheduler enter run() against an incompletely constructed
object. start() is a separate state transition invoked only after the complete
owner exists. Duplicate start is rejected without another xTaskCreate call.
Wrapper contract
- constructor stores name, stack words and priority only;
start()movesconstructed -> starting -> readyand calls the C API;- static trampoline sets
running, invokesDerived::run(), then recordscompleted, clears the handle and deletes the current kernel task; - copy and move are deleted because the context address must not change;
- no destructor force-deletes a live task.
Acceptance
- host test proves no constructor create call, one explicit create, stable
context, duplicate rejection and
start_failedbehavior; - ABI contains the static trampoline but no vtable/RTTI/hosted runtime;
- Hazard3 shows object outside
ucHeap, TCB inucHeap, stack-local inside reported task stack bounds; - counter result is 500500, wrapper ends completed with null handle;
- all identity equalities and checkpoint 12 PASS hold.
Main traps
TaskHandle_tis not the C++ object pointer; it identifies the TCB.- Stack depth is in
StackType_twords, not necessarily bytes. - Moving a started wrapper invalidates the kernel's stored context.
- Starting in a constructor exposes a partial object.
- Hiding forced deletion in a destructor does not unwind arbitrary task-local C++ objects and creates unsafe asynchronous lifetime coupling.