284 lines
14 KiB
Markdown
284 lines
14 KiB
Markdown
# FC02 Plan — First Task, TCB and Task Stack
|
||
|
||
Status: implemented and validated locally. The card exists at
|
||
`series/freertos/lab-rv32i-freertos-c-first-task`, is visible in the
|
||
FreeRTOS C catalog and remains outside the deploy manifest until its own
|
||
repository is committed and published.
|
||
|
||
The paired experiment and common E01–E12 vocabulary are defined in
|
||
[`freertos-first-task-a1-a8.md`](freertos-first-task-a1-a8.md).
|
||
|
||
## Goal
|
||
|
||
The student creates a FreeRTOS task through the raw C API, follows
|
||
`pvParameters` into a typed context, separates that context from the TCB and
|
||
task stack, observes equal-priority time slicing, and proves deterministic
|
||
completion in Hazard3/GDB.
|
||
|
||
The card is the C baseline for K02. It must not imitate a C++ wrapper in C.
|
||
|
||
## Scope
|
||
|
||
Included:
|
||
|
||
- `TaskFunction_t` and `void *pvParameters`;
|
||
- `xTaskCreate`, `TaskHandle_t`, scheduler start and self-delete;
|
||
- two equal-priority CPU-bound workers and one lower-priority supervisor;
|
||
- static application contexts, dynamic TCB/task-stack allocation in `heap_4`;
|
||
- TCB, stack, PC/SP, callback ABI and task-state observations;
|
||
- the same sum and scheduling invariants as K02.
|
||
|
||
Excluded:
|
||
|
||
- C++ classes, templates, RAII or virtual dispatch;
|
||
- queues, mutexes, notifications, timers, ISR APIs and peripherals;
|
||
- proof that the idle task has already reclaimed deleted tasks;
|
||
- report/PDF/Teams generation and the later book/matura lesson payload.
|
||
|
||
## Implemented program shape
|
||
|
||
```c
|
||
typedef enum
|
||
{
|
||
TRACE_PREPARED,
|
||
TRACE_STARTING,
|
||
TRACE_READY,
|
||
TRACE_RUNNING,
|
||
TRACE_COMPLETED,
|
||
TRACE_CREATE_FAILED
|
||
} WorkerTraceState;
|
||
|
||
typedef struct
|
||
{
|
||
uint32_t id;
|
||
volatile uint32_t result;
|
||
volatile uint32_t iterations;
|
||
volatile WorkerTraceState trace_state;
|
||
TaskHandle_t handle;
|
||
TickType_t start_tick;
|
||
TickType_t end_tick;
|
||
} WorkerContext;
|
||
```
|
||
|
||
Source responsibilities:
|
||
|
||
```text
|
||
src/tasks/task01_first_task.c
|
||
├── two static WorkerContext instances
|
||
├── trace/checkpoint state
|
||
├── create_worker(WorkerContext*, ...)
|
||
├── sum_task_entry(void *pvParameters)
|
||
├── supervisor_task_entry(void *pvParameters)
|
||
├── task01_debug_checkpoint(event, subject)
|
||
└── main()
|
||
```
|
||
|
||
`trace_state` is an application diagnostic. Its name deliberately avoids
|
||
pretending that it is `eTaskState`.
|
||
|
||
## Applicable viewpoint profile
|
||
|
||
| View | Status | FC02 purpose |
|
||
|---|---|---|
|
||
| A1 CONTEXT | enabled | raw application → C API/kernel/port → Hazard3 |
|
||
| A2 STRUCTURE | enabled | contexts, handles, entry functions and storage domains |
|
||
| A3 DISPATCH | enabled | function pointer + erased parameter → typed C body |
|
||
| A4 APPLICATION | unavailable | the raw task module already is the concrete application; no generic application layer exists |
|
||
| A5 FLOW | enabled | E01–E12 creation and execution scenario |
|
||
| A6 STATE | enabled | static context/diagnostic state versus kernel state |
|
||
| A7 RUNTIME | enabled | context, TCB, stack, registers, PC and ticks |
|
||
| A8 PATTERNS | unavailable | a standalone pattern view would be decorative; idioms are labelled in A2/A3 and compared in K02 A8 |
|
||
|
||
Unavailable A4/A8 tabs remain readable in the viewer and print index with the
|
||
reasons above.
|
||
|
||
## Canonical tree
|
||
|
||
```text
|
||
Series · FreeRTOS C
|
||
└── Card FC02 · First Task, TCB and Task Stack
|
||
└── Task01 · Raw C task and typed context
|
||
├── Block · A1 CONTEXT
|
||
│ └── Phase · SYSTEM BOUNDARY
|
||
│ ├── Step 01 · application module requests tasks [CODE]
|
||
│ ├── Step 02 · FreeRTOS C API owns scheduling [CODE]
|
||
│ └── Step 03 · kernel/port execute on Hazard3 [CODE]
|
||
├── Block · A2 STRUCTURE
|
||
│ └── Phase · C DATA / FUNCTIONS
|
||
│ ├── Step 01 · WorkerContext is application state [CODE]
|
||
│ ├── Step 02 · TaskHandle_t is an opaque kernel handle [CODE]
|
||
│ ├── Step 03 · two static worker contexts [CODE]
|
||
│ ├── Step 04 · sum_task_entry has TaskFunction_t shape [CODE]
|
||
│ ├── Step 05 · SupervisorContext observes both workers [CODE]
|
||
│ └── Step 06 · common sum/scheduling invariants [CODE]
|
||
├── Block · A3 DISPATCH · C FUNCTION POINTER
|
||
│ └── Phase · CALLBACK / CONTEXT
|
||
│ ├── Step 01 · TaskFunction_t erases the context type [CODE]
|
||
│ ├── Step 02 · xTaskCreate receives entry and context [RUN E03]
|
||
│ ├── Step 03 · pvParameters arrives in RV32 a0 at entry [RUN E06]
|
||
│ ├── Step 04 · cast recovers WorkerContext* [RUN E06]
|
||
│ ├── Step 05 · entry executes the typed calculation [RUN E07]
|
||
│ └── Step 06 · wrong context cast is not rejected by C [CODE]
|
||
├── Block · A5 FLOW
|
||
│ ├── Phase · CREATE / START
|
||
│ │ ├── Step 01 · static contexts prepared [RUN E01]
|
||
│ │ ├── Step 02 · first worker create request [RUN E02]
|
||
│ │ ├── Step 03 · xTaskCreate(entry, &workerA) [RUN E03]
|
||
│ │ ├── Step 04 · handle != null; diagnostic ready [RUN E04]
|
||
│ │ └── Step 05 · vTaskStartScheduler() [RUN E05]
|
||
│ └── Phase · DISPATCH / FINISH
|
||
│ ├── Step 06 · sum_task_entry(&workerA) [RUN E06]
|
||
│ ├── Step 07 · diagnostic running; calculation loop [RUN E07]
|
||
│ ├── Step 08 · tick/time slice A -> B -> A [RUN E08]
|
||
│ ├── Step 09 · result 200010000 published [RUN E09]
|
||
│ ├── Step 10 · diagnostic completed; public handle null [RUN E10]
|
||
│ ├── Step 11 · vTaskDelete(NULL) [RUN E11]
|
||
│ └── Step 12 · supervisor verifies PASS [RUN E12]
|
||
├── Block · A6 STATE
|
||
│ ├── Phase · CONTEXT / DIAGNOSTIC
|
||
│ │ ├── Step 01 · static WorkerContext lifetime [CODE]
|
||
│ │ ├── Step 02 · PREPARED -> STARTING -> READY [RUN E01–E04]
|
||
│ │ └── Step 03 · RUNNING -> COMPLETED [RUN E07–E10]
|
||
│ └── Phase · KERNEL
|
||
│ ├── Step 04 · Ready -> Running through scheduler [RUN E04–E07]
|
||
│ ├── Step 05 · equal-priority time slicing [RUN E08]
|
||
│ └── Step 06 · Deleted does not destroy static context [RUN E10–E11]
|
||
├── Block · A7 RUNTIME
|
||
│ ├── Phase · MEMORY
|
||
│ │ ├── Step 01 · context address is not a TCB address [RUN E04/E06]
|
||
│ │ ├── Step 02 · TCB and task stack come from heap_4 [RUN E04/E07]
|
||
│ │ └── Step 03 · SP lies inside worker stack interval [RUN E06/E07]
|
||
│ └── Phase · CPU / SCHEDULER
|
||
│ ├── Step 04 · pvParameters in a0 only at callback entry [RUN E06]
|
||
│ ├── Step 05 · PC, tick and switch trace [RUN E08]
|
||
│ └── Step 06 · result, source and checkpoint identity [RUN E12]
|
||
└── Exercise · Timestamped raw-C task trace
|
||
```
|
||
|
||
## Checkpoint table
|
||
|
||
FC02 snapshots are local to the FC02 binary even though their `event_id`
|
||
matches K02.
|
||
|
||
| Event | Planned snapshot | Stop condition | Required verification |
|
||
|---|---|---|---|
|
||
| E01 | `task01.contexts` | point 1, worker A | trace prepared, handle null, result zero |
|
||
| E02 | `task01.create-request` | point 2, worker A, previous point 1 | first launch request only |
|
||
| E03 | `task01.create-call` | point 3, worker A | trace starting; callback/context arguments correct |
|
||
| E04 | `task01.ready` | point 4, worker A | trace ready; handle non-null |
|
||
| E05 | `task01.scheduler` | point 5 | all three tasks created; startup stack active |
|
||
| E06 | `task01.entry` | point 6, worker A | `pvParameters == &workerA`; callback-entry `a0` matches |
|
||
| E07 | `task01.work` | point 7, worker A | typed context recovered; trace running |
|
||
| E08 | `task01.timeslice` | point 8, worker A only | A/B/A trace; >=2 switches and elapsed ticks |
|
||
| E09 | `task01.result` | point 9, worker A | result `200010000`, iterations `20000` |
|
||
| E10 | `task01.completed` | point 10, worker A | trace completed; public handle null |
|
||
| E11 | `task01.delete` | point 11, worker A | published state persists before non-returning delete |
|
||
| E12 | `task01.pass` | point 12, supervisor | two results, contexts, handles, stacks and scheduler invariants pass |
|
||
|
||
The checkpoint sink is `noinline` and `used`, writes volatile point/subject
|
||
state, and ends with a compiler memory barrier. E08 is emitted once by worker
|
||
A after the trace has proved `A -> B -> A`.
|
||
|
||
## A3 dispatch evidence
|
||
|
||
The exact boundary is:
|
||
|
||
```text
|
||
xTaskCreate(sum_task_entry, ..., &workerA, ..., &workerA.handle)
|
||
│ │
|
||
│ TaskFunction_t │ void* context
|
||
▼ ▼
|
||
sum_task_entry(void *pvParameters)
|
||
│ callback-entry a0 == &workerA
|
||
▼
|
||
WorkerContext *worker = (WorkerContext *)pvParameters
|
||
│
|
||
▼
|
||
typed calculation and published result
|
||
```
|
||
|
||
There is one C callback dispatch. The cast does not cause a second dynamic
|
||
dispatch. At callback entry, source + disassembly + `a0` prove the ABI. After
|
||
ordinary instructions execute, the card stops claiming that `a0` retains
|
||
`pvParameters`.
|
||
|
||
A compile-only negative exhibit demonstrates that C can accept a wrong
|
||
`void*`-to-structure cast that the K02 C++ contract rejects. The unsafe fixture
|
||
is never linked into or executed by the teaching program.
|
||
|
||
## Per-view debugging strategies
|
||
|
||
| View/anchor | Mode | Layout/action | Expected evidence |
|
||
|---|---|---|---|
|
||
| A1 dependency boundary | CODE | include/link/config excerpts | application calls public API; kernel/port/target boundary |
|
||
| A2 context layout | CODE | source excerpt + `ptype`/DWARF or bounded layout output | static context fields and no hidden C++ object model |
|
||
| A2 handle/storage | CODE | declaration + memory-region map | handle is opaque; context is static; TCB/stack are dynamic |
|
||
| A3 entry/context | RUN | source + entry disassembly + `a0` + context memory | callback ABI and exact worker identity |
|
||
| A3 wrong-cast exhibit | CODE | deterministic compile output and source blob | C compiler does not encode the desired context type |
|
||
| A5 E01–E12 | RUN | per-event minimal source/asm/register/stack/memory set | ordered scenario and non-empty assertions |
|
||
| A6 diagnostic state | RUN | context fields beside public `eTaskGetState`/`vTaskGetInfo` result | correlated domains remain explicitly distinct |
|
||
| A7 TCB/stack | RUN | context address, public handle, TCB address, heap range, SP interval | context != TCB; SP belongs to the selected task stack |
|
||
| A7 scheduling | RUN | PC, tick, trace buffer and current-task observation | computation crosses real ticks and task changes |
|
||
|
||
Application code uses public task APIs. `pxCurrentTCB` may appear only in a
|
||
clearly labelled debugger-internal observation, never in a C assertion or
|
||
source dependency.
|
||
|
||
## State model
|
||
|
||
```text
|
||
static context lifetime: initialized -------------------------- program end
|
||
diagnostic trace state: PREPARED -> STARTING -> READY -> RUNNING -> COMPLETED
|
||
kernel task state: Ready <-> Running <-> Blocked; Deleted
|
||
```
|
||
|
||
The lanes can be correlated by an event and handle, but they are not equal.
|
||
Clearing the public handle before `vTaskDelete(NULL)` is an application
|
||
diagnostic choice. It does not prove immediate heap reclamation.
|
||
|
||
## Page and spread plan
|
||
|
||
1. goal/scope, paired invariants and viewpoint index;
|
||
2. compact A1 plus main A2 C structure/storage map;
|
||
3. A3 callback/parameter dispatch;
|
||
4. A5 CREATE/START (E01–E05);
|
||
5. A5 DISPATCH/FINISH (E06–E12), joined with sheet 4 in the viewer;
|
||
6. A6 diagnostic and kernel state lanes;
|
||
7. A7 runtime map plus exercise/rubric.
|
||
|
||
The A5 split occurs at the scheduler boundary. Both assets share a
|
||
`spread_group`, stable anchor numbering and common participant alignment.
|
||
|
||
## Acceptance contract
|
||
|
||
- freestanding C11 build, same pinned kernel/port/config as K02;
|
||
- no `_Z*`, `.init_array`, `_GLOBAL__sub_I`, `_Unwind*`, `__cxa_*` or hosted
|
||
C++ runtime symbols; positive C/FreeRTOS/checkpoint symbols must exist;
|
||
- two independent results equal `200010000`;
|
||
- two workers each execute `20000` loop iterations;
|
||
- work crosses at least two ticks and proves at least two task changes with an
|
||
`A -> B -> A` subsequence;
|
||
- callback-entry `a0`, `pvParameters` and worker A address match;
|
||
- static context, TCB and task stack are shown as distinct objects/domains;
|
||
- all twelve RUN checkpoints have assertions and replay from clean RAM;
|
||
- every CODE step has a deterministic source/artifact query and no snapshot;
|
||
- all enabled/disabled views, anchors and full tree keys validate;
|
||
- plain cursor movement never runs or resets the simulator.
|
||
|
||
## Validation result
|
||
|
||
1. The raw C workload and A1/A2/A3/A5/A6/A7 views are implemented.
|
||
2. Kernel/config/workload parity is pinned in
|
||
`config/paired-experiment.json` and passes `make check-pair`.
|
||
3. C-only ABI, context contract and host workload checks pass.
|
||
4. Hazard3 finishes with `PASS`; two results equal `200010000`, the trace
|
||
contains `A -> B -> A`, and three clean simulator processes produce
|
||
identical evidence.
|
||
5. HTML and TeX render with A1/A6 portrait, A2/A3/A7 landscape and A5 as a
|
||
two-page landscape spread. No PDF was generated.
|
||
6. The catalog marks FC02 as existing. The deploy manifest is unchanged
|
||
until a published card repository exists.
|
||
|
||
HTML and TeX may be rendered during validation. PDF generation and commit
|
||
remain outside this pass.
|