feat: add lab-rv32i-freertos-scheduler-states card
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
# K04 — Scheduler states, priorities, time slicing and typed ticks
|
||||
|
||||
## Position in the series
|
||||
|
||||
- Series: FreeRTOS C++
|
||||
- Lesson: L03, card K04
|
||||
- Duration: 30 minutes
|
||||
- Executables: exactly one
|
||||
- Kernel: unchanged FreeRTOS V11.3.0 in C
|
||||
- C++ mode: freestanding C++17, no exceptions, RTTI or hosted `libstdc++`
|
||||
- New C++ surface: `Ticks`, `TickPoint`, non-owning `TaskRef`
|
||||
|
||||
K02 introduced task entry and lifecycle, while K03 established ownership and
|
||||
RAII for a heap buffer. K04 deliberately does not build a large facade. It
|
||||
first exposes the scheduler's raw evidence and adds only types that prevent a
|
||||
duration, a tick point and an owning task object from being confused.
|
||||
|
||||
## Outcome
|
||||
|
||||
After 30 minutes the student can predict and verify `READY`, `RUNNING`,
|
||||
`BLOCKED` and `DELETED`; distinguish a relative delay from a periodic wake
|
||||
reference; explain time slicing for equal priorities; and use the order
|
||||
`BEFORE -> HIGH -> AFTER` as proof of immediate priority preemption.
|
||||
|
||||
## Lesson plan — 30 minutes
|
||||
|
||||
| Time | Mode | Required evidence |
|
||||
| --- | --- | --- |
|
||||
| 0–4 | prediction | fill the first state/priority table without running code |
|
||||
| 4–8 | typed time | distinguish `Ticks` from `TickPoint`; no assumption that tick means ms |
|
||||
| 8–13 | time slicing | obtain `A,B,A` or `B,A,B` at equal priority 2 |
|
||||
| 13–19 | blocked state | show A=`eBlocked`, B=`eRunning`, elapsed exactly 3 ticks |
|
||||
| 19–25 | priority change | show B at priority 3 and marker order `1,2,3` |
|
||||
| 25–30 | interpretation | compare relative/periodic delay, state limits and stale `TaskRef` |
|
||||
|
||||
## Canonical experiment
|
||||
|
||||
```text
|
||||
scheduler starts
|
||||
-> startup probe: A READY, B READY
|
||||
-> equal priority 2: A/B alternate on tick boundaries
|
||||
-> A calls vTaskDelay(3)
|
||||
-> A BLOCKED; B RUNNING
|
||||
-> at start+3 A becomes READY and then RUNNING
|
||||
-> A writes BEFORE=1 and raises B to priority 3
|
||||
-> B preempts, writes HIGH=2, lowers itself to priority 2
|
||||
-> A resumes inside/after vTaskPrioritySet and writes AFTER=3
|
||||
-> workers delete themselves; verifier writes terminal snapshot and PASS
|
||||
```
|
||||
|
||||
The test accepts either initial equal-priority order. It requires the
|
||||
alternating relation `first == third && first != second`.
|
||||
|
||||
## Stable evidence contract
|
||||
|
||||
| Evidence | Required relation |
|
||||
| --- | --- |
|
||||
| `g_alternating_tasks[0..2]` | A,B,A or B,A,B |
|
||||
| delay snapshot | A=`eBlocked`, B=`eRunning` |
|
||||
| delay timestamps | `end - start == 3`, observation lies before end |
|
||||
| priority snapshot | A=`eReady`, B=`eRunning`, B priority 3 |
|
||||
| `g_priority_order` | exactly `1,2,3` |
|
||||
| terminal snapshot | A/B recorded as `eDeleted`, handles no longer queried |
|
||||
| `g_scheduler_states_pass` | 1 |
|
||||
|
||||
## Prediction table
|
||||
|
||||
The student completes state and priority before inspecting the executable:
|
||||
|
||||
| Event | A state / priority | B state / priority | Current task | Why? |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| startup probe | | | | |
|
||||
| A before delay | | | | |
|
||||
| B while A delays | | | | |
|
||||
| A after 3 ticks | | | | |
|
||||
| before raise | | | | |
|
||||
| B at p=3 | | | | |
|
||||
| after API returns | | | | |
|
||||
| verifier | | | | |
|
||||
|
||||
## Typed wrapper boundary
|
||||
|
||||
```cpp
|
||||
freertos::Ticks duration{3};
|
||||
const auto start = freertos::TickPoint::now();
|
||||
vTaskDelay(duration.count());
|
||||
const auto elapsed = freertos::TickPoint::now() - start;
|
||||
|
||||
freertos::TaskRef peer{handle}; // no ownership
|
||||
peer.set_priority(3);
|
||||
```
|
||||
|
||||
`TaskRef` is the size of one native handle and has no virtual dispatch. It
|
||||
must not be used after the task is deleted. K04 uses raw scheduling calls on
|
||||
purpose; a broad `Scheduler` facade is deferred until K09.
|
||||
|
||||
## Misconceptions to surface
|
||||
|
||||
1. Highest priority does not mean “always running”: a high-priority task can
|
||||
be blocked and therefore ineligible.
|
||||
2. `vTaskDelay(3)` does not promise three milliseconds unless the configured
|
||||
tick rate makes that conversion true.
|
||||
3. `vTaskDelay` is relative; repeated work plus relative delays can drift.
|
||||
`xTaskDelayUntil` advances a periodic reference.
|
||||
4. Equal priority does not establish a fixed first task; verify alternation,
|
||||
not a guessed first ID.
|
||||
5. A handle is not ownership. After deletion, `TaskRef` can dangle.
|
||||
|
||||
## Acceptance
|
||||
|
||||
- host test proves typed tick arithmetic, wrap-around and one-handle `TaskRef`;
|
||||
- ELF contains no RTTI, vtable, exception/unwind or hosted C++ dependency;
|
||||
- Hazard3 proves a three-entry alternating trace;
|
||||
- the blocked snapshot and exactly-three-tick delay pass;
|
||||
- priority markers are exactly `1,2,3` and B is priority 3 at marker 2;
|
||||
- all eight snapshots match the intended state transition sequence;
|
||||
- student explains why the startup snapshot is taken after scheduler start.
|
||||
|
||||
Reference in New Issue
Block a user