7.4 KiB
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:
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:
- zbuduj Task 3 dla
pico2_w/ RP2350 RISC-V; - wgraj zweryfikowany obraz i otwórz sesję na tym samym ELF;
- ustaw pierwszy breakpoint na
heap4_fragmented_checkpoint; - przygotuj widok
ucHeap,g_fragmented_stats,g_final_stats,xFreeBytesRemainingixMinimumEverFreeBytesRemaining; - 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 |
| 16–27 min | Task 3 — RUN | Na przygotowanym Pico 2 W przejdź przez prawdziwy heap_4.c: trzy alokacje, free A i C, checkpoint z dwoma blokami, free B, checkpoint z jednym blokiem, OOM=NULL i jeden malloc-failed hook. 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:
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
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(B) == addr(C), połącz B+C;
3. jeśli end(A) == addr(BC), połącz A+BC;
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
RESET/INIT
-> ALLOC A(24)
-> ALLOC B(40)
-> ALLOC C(16)
-> FREE A
-> FREE C
-> heap4_fragmented_checkpoint # 2 free blocks
-> FREE B
-> OOM request
-> task03_debug_checkpoint # 1 free block, pass=1
Uczeń zapisuje tylko relacje, ponieważ dokładne bajty zależą od ABI i konfiguracji:
| Obserwacja | Oczekiwany związek |
|---|---|
| po trzech alokacjach | after_allocations < initial_free |
| stan pofragmentowany | free_blocks == 2, largest < total_free |
po free(B) |
final_free == initial_free, free_blocks == 1 |
| low-water mark | minimum_ever <= after_allocations i nie rośnie po free |
| OOM | wynik NULL, hook 1, brak uszkodzenia listy |
Minimalny dowód w GDB:
p g_fragmented_stats
p g_final_stats
p xFreeBytesRemaining
p xMinimumEverFreeBytesRemaining
p xStart
p pxEnd
x/96bx ucHeap
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 -> 1po zwolnieniu B; - wyjaśni różnicę
total freekontralargest free block; - wyjaśni, dlaczego minimum-ever nie wraca po
free; - pokaże
aligned=1 oom=1 hooks=1 asserts=0 pass=1w 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.