# FC01 — FreeRTOS `heap_4`: od kursora do wolnych bloków

## Decyzja dydaktyczna

To jest jedna 30-minutowa lekcja i bezpośrednia kontynuacja projektu
K&R 5.4 z karty C07. Karta nie uczy pięciu implementacji heap jako pięciu
równorzędnych tematów. `heap_1`, `heap_2`, `heap_3` i `heap_5` zajmują łącznie
maksymalnie minutę kontekstu. Cała właściwa praca dotyczy `heap_4`.

Task 1 i Task 2 są krótkimi modelami prowadzonymi przez nauczyciela. Uczeń
najpierw zapisuje predykcję, ale nie buduje i nie debugguje tych programów
osobno podczas lekcji. Task 3, linkujący upstream `heap_4.c`, jest jedynym
centralnym przebiegiem ucznia.

## Stan wejściowy z K&R 5.4

Uczeń przychodzi z działającym kodem:

```text
allocbuf[64]
allocp
allocator_reset()
alloc_local(5) -> offset 0
alloc_local(7) -> offset 5
allocp         -> offset 12
alloc_local(0) oraz OOM -> NULL bez przesunięcia kursora
```

Nie powtarzamy sposobu działania tego kodu. Zaczynamy od jego granicy:

> Jak zwolnić obszar 5 B spod offsetu 0, zachować późniejszy obszar 7 B spod
> offsetu 5 i ponownie wykorzystać powstałą dziurę?

W projekcie nie istnieje operacja zwolnienia pojedynczego obszaru. Nawet proste
cofnięcie kursora unieważniłoby wszystkie późniejsze przydziały. Brakuje
tożsamości bloku, jego rozmiaru, zbioru dziur i mechanizmu ich łączenia.

## Cel w jednym zdaniu

Po 30 minutach uczeń potrafi wyjaśnić i pokazać w debuggerze, jak `heap_4`
dodaje do liniowej areny nagłówki, wybór first-fit, podział bloku, listę wolnych
bloków oraz scalanie sąsiadów, a następnie odróżnić bieżącą ilość wolnej
pamięci, największy wolny blok i minimum-ever.

## Most K&R 5.4 -> `heap_4`

| K&R 5.4 — stan projektu | Ograniczenie | `heap_4` — nowy mechanizm |
| --- | --- | --- |
| `allocbuf[64]` i jeden `allocp` | istnieje tylko wolny ogon areny | `ucHeap[]` i bloki opisane nagłówkami |
| wynik to surowy adres | allocator nie zna później rozmiaru obszaru | `BlockLink_t` przed payloadem przechowuje rozmiar |
| kolejne przydziały tylko przesuwają kursor | nie można użyć dziury | lista wolnych bloków i wybór first-fit |
| brak zwalniania pojedynczego obszaru | reset odzyskuje wszystko naraz | `vPortFree()` odzyskuje wskazany blok |
| brak relacji między dziurami | sąsiednie dziury pozostają rozdzielone | porządek adresowy i coalescing w obie strony |
| tylko sukces albo `NULL` | brak informacji o fragmentacji | free, largest, liczba bloków i minimum-ever |

W realnym `heap_4.c` najwyższy bit `xBlockSize` oznacza blok przydzielony.
Uproszczone modele Task 1–2 pokazują geometrię i listę, ale nie odtwarzają tego
bitu. To jawna granica modelu.

## Przygotowanie przed lekcją

Budowanie, flashowanie i wykrywanie debug probe nie wchodzą do budżetu 30 minut.
Przed wejściem uczniów:

1. zbuduj Task 3 dla `pico2_w` / RP2350 RISC-V;
2. wgraj zweryfikowany obraz i otwórz sesję na tym samym ELF;
3. ustaw pierwszy breakpoint na `heap4_reset_checkpoint`;
4. przygotuj widok `ucHeap`, statystyk E01--E08,
   `xFreeBytesRemaining` i `xMinimumEverFreeBytesRemaining`;
5. przygotuj Hazard3 jako awaryjny zamiennik — podczas lekcji użyj jednej
   platformy, nigdy obu.

`heap_4` jest implementacją wybraną i linkowaną przez projekt. Nie jest
sprzętowym heapem ani peryferium RP2350.

## Przebieg 30 minut

| Czas | Tryb | Działanie i dowód |
| --- | --- | --- |
| 0–3 min | problem | Przypomnij wyłącznie offsety `0 -> 5 -> 12`. Uczeń próbuje zwolnić pierwszy obszar bez utraty drugiego i nazywa brakującą operację. |
| 3–4 min | kontekst | Jedno zdanie: FreeRTOS dostarcza wymienne implementacje tego samego API; ta karta i oficjalne przykłady Pico 2 W linkują `heap_4`. |
| 4–9 min | Task 1 — demo | Do żądania dodaj nagłówek, wyrównaj rozmiar, wybierz pierwszy pasujący blok i podziel go. Uczeń zapisuje `wanted` i `remainder` przed odsłonięciem wyniku. |
| 9–16 min | Task 2 — demo | Dla `A:48 | B:48 | C:48` pokaż listę `A -> C`, wstaw `B` po adresie i sprawdź scalenie w prawo oraz w lewo: `2 bloki / 96 B / largest 48 B -> 1 blok / 144 B / largest 144 B`. |
| 16–27 min | Task 3 — RUN | Na przygotowanym celu przejdź przez osiem checkpointów prawdziwego `heap_4.c`: reset, trzy alokacje, free A, free C, free B i kontrolowany OOM. Uczeń wypełnia tabelę relacji. |
| 27–30 min | wyjście | Uczeń wymienia trzy dodatki ponad K&R (`header`, `free list`, `coalescing`) i wyjaśnia: suma wolnych bajtów może być większa od największego bloku, a minimum-ever nie rośnie po `free`. |

## Task 1 — model nagłówka i splitu

Task 1 nie jest osobnym laboratorium. Na RV32 nagłówek modelu ma 8 B, więc
żądanie 13 B daje `wanted=24 B` i `remainder=232 B` z areny 256 B. Na AMD64
odpowiednie wartości to 16 B, 32 B i 224 B. Różnica ABI jest tylko pomocą w
zrozumieniu `sizeof` i wyrównania; podczas głównego przebiegu obowiązuje RP2350.

Predykcja ucznia:

```text
free list: [16 B] -> [80 B] -> [160 B]
request + header po wyrównaniu: 24 B
first-fit wybiera: __________
po split: allocated ______ B, remainder ______ B
```

Wykonywalny Task 1 dowodzi obecnie geometrii splitu jednego bloku. Samo
przeszukanie dwóch kandydatów jest elementem demonstracji i realnego
`pvPortMalloc()`, dlatego nie opisujemy Task 1 jako pełnego testu first-fit.

## Task 2 — model listy adresowej i coalescingu

```text
pamięć:    [ A:48 FREE ][ B:48 USED ][ C:48 FREE ]
free list: A --------------------------------> C

vPortFree(B)
  1. wstaw B pomiędzy A i C według adresu;
  2. jeśli end(A) == addr(B), połącz A+B;
  3. jeśli end(AB) == addr(C), połącz AB+C;

wynik:     [ ABC:144 FREE ]
free list: A -> END
```

Uczeń przed odsłonięciem rysuje listę i wpisuje `2 -> 1`, `96 -> 144` oraz
`48 -> 144` odpowiednio dla liczby bloków, sumy i largest.

## Task 3 — centralny przebieg upstream `heap_4.c`

```text
RESET/INIT
  -> heap4_reset_checkpoint           # E01: 4080 B
  -> ALLOC A(24)
  -> heap4_alloc_a_checkpoint         # E02: 4032 B
  -> ALLOC B(40)
  -> heap4_alloc_b_checkpoint         # E03: 3968 B
  -> ALLOC C(16)
  -> heap4_alloc_c_checkpoint         # E04: 3936 B
  -> FREE A
  -> heap4_free_a_checkpoint          # E05: 3984 B, 2 bloki
  -> FREE C
  -> heap4_fragmented_checkpoint      # E06: 4016 B, 2 bloki
  -> FREE B
  -> heap4_coalesced_checkpoint       # E07: 4080 B, 1 blok
  -> OOM request
  -> task03_debug_checkpoint          # E08: NULL, hook=1, pass=1
```

Na Hazard3 `sizeof(BlockLink_t)=8`, ale `portBYTE_ALIGNMENT=16`, dlatego
`xHeapStructSize=16`. Główny replay ma stałe, testowane wartości:

| Obserwacja | Oczekiwany wynik RV32/Hazard3 |
| --- | --- |
| E01--E04 | `4080 -> 4032 -> 3968 -> 3936`, zużycie `48/64/32` |
| E05--E06 | `3984 -> 4016`, nadal 2 bloki i `largest < total_free` |
| E07 | `final_free == initial_free == 4080`, `free_blocks == 1` |
| low-water mark | `minimum_ever == 3936` i nie rośnie po free |
| OOM | wynik `NULL`, hook `1`, brak uszkodzenia listy |

Minimalny dowód w GDB:

```gdb
p g_fragmented_stats
p g_final_stats
p g_a_consumed
p g_b_consumed
p g_c_consumed
p xFreeBytesRemaining
p xMinimumEverFreeBytesRemaining
p xStart
p pxEnd
x/96bx ucHeap
```

Szczegółowe mapowanie wszystkich diagramów do kodu, replay i dowodów zawiera
[`hazard3-iteration-plan.md`](hazard3-iteration-plan.md).

## Kryterium zaliczenia

Uczeń zalicza lekcję, jeżeli:

- wskaże, dlaczego jeden kursor nie wystarcza do zwolnienia obszaru ze środka;
- rozróżni nagłówek allocatora od payloadu i stosu `sp`;
- przewidzi przejście wolnej listy `2 -> 1` po zwolnieniu B;
- wyjaśni różnicę `total free` kontra `largest free block`;
- wyjaśni, dlaczego minimum-ever nie wraca po `free`;
- pokaże `aligned=1 oom=1 hooks=1 asserts=0 pass=1` w Task 3.

## Poza lekcją

Jako rozszerzenie lub pracę własną pozostają: pełne czytanie `heap_4.c`,
uruchomienie Task 1–2 na AMD64 i Hazard3, samodzielny deploy RP2350 oraz
porównanie z `heap_1`, `heap_2`, `heap_3` i `heap_5`.
