Files
lab-rv32i-freertos-heap4/doc/main.tex
T

315 lines
11 KiB
TeX

\documentclass[10pt]{article}
\usepackage[T1]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage[polish]{babel}
\usepackage[a4paper,margin=1.55cm]{geometry}
\usepackage{array,tabularx,booktabs}
\usepackage{amsmath,amssymb}
\usepackage{xcolor,listings}
\usepackage{hyperref,fancyhdr,lastpage}
\usepackage{enumitem}
\definecolor{accent}{HTML}{16324A}
\definecolor{accentlight}{HTML}{EEF3F7}
\definecolor{rulegray}{HTML}{D7DEE5}
\definecolor{success}{HTML}{236B45}
\hypersetup{colorlinks=true,linkcolor=accent,urlcolor=blue}
\IfFileExists{build-meta.tex}{\input{build-meta.tex}}{\newcommand{\BuildCommit}{local}}
\newcommand{\PublisherDomain}{mpabi}
\newcommand{\DocumentAuthor}{M. Pabiszczak}
\newcommand{\DocumentYear}{2026}
\newcommand{\CardArea}{inf}
\newcommand{\CardSeries}{rv32i-freertos}
\newcommand{\CardNumber}{01}
\newcommand{\CardCount}{15}
\newcommand{\CardSlug}{heap4}
\newcommand{\CardVersion}{v0.3}
\newcommand{\DocumentKey}{\PublisherDomain/\CardArea/\CardSeries/\CardNumber/\CardSlug/\CardVersion}
\newcommand{\DocumentUUID}{4654524c-73f2-46df-83a3-d448ba6d6c5a}
\lstset{
language=C,basicstyle=\ttfamily\scriptsize,columns=fullflexible,
keepspaces=true,frame=single,breaklines=true,showstringspaces=false,
numbers=none,backgroundcolor=\color{accentlight},rulecolor=\color{rulegray}
}
\pagestyle{fancy}
\fancyhf{}
\lhead{\textbf{FC01 · FreeRTOS \texttt{heap\_4}}}
\rhead{\small 30 minut · Pico 2 W / RP2350}
\lfoot{\scriptsize commit \BuildCommit}
\cfoot{\scriptsize \thepage/\pageref{LastPage}}
\rfoot{\scriptsize V11.3.0 / \CardVersion}
\setlength{\headheight}{14pt}
\setlength{\footskip}{19pt}
\setlist[itemize]{nosep,leftmargin=1.45em}
\setlist[enumerate]{nosep,leftmargin=1.65em}
\newcommand{\checkline}{\(\square\)\;}
\newcommand{\blank}[1]{\rule{#1}{.2pt}}
\begin{document}
\sloppy
\begin{center}
{\LARGE\bfseries FreeRTOS \texttt{heap\_4}: od kursora do wolnych bloków}\par
\vspace{.25em}
{\large bezpośrednia kontynuacja projektu K\&R 5.4}\par
\end{center}
\noindent\begin{tabularx}{\textwidth}{@{}p{1.45cm}Xp{1.4cm}X@{}}
\toprule
Karta & FC01 / \CardCount & Czas & 30 minut \\
Płytka & Raspberry Pi Pico 2 W & Układ & RP2350, rdzeń RISC-V \\
Allocator & FreeRTOS-Kernel V11.3.0 & Źródło & upstream \texttt{heap\_4.c} \\
Wersja & \CardVersion & Data & 19.07.2026 \\
\bottomrule
\end{tabularx}
\section*{Punkt startowy — tego już nie powtarzamy}
Z poprzedniej karty działa projekt:
\begin{lstlisting}
allocbuf[64]; allocp = allocbuf;
alloc_local(5) -> offset 0
alloc_local(7) -> offset 5
allocp -> offset 12
alloc_local(0) i OOM -> NULL; kursor pozostaje na 12
\end{lstlisting}
\noindent\fcolorbox{accent}{accentlight}{%
\begin{minipage}{.94\textwidth}
\textbf{Pytanie projektowe.} Jak zwolnić obszar 5 B spod offsetu 0, zachować
późniejszy obszar 7 B spod offsetu 5 i ponownie wykorzystać powstałą dziurę?
W naszym projekcie nie istnieje \texttt{free}; reset odzyskuje całą arenę.
Samo cofnięcie kursora zniszczyłoby również późniejsze przydziały.
\end{minipage}}
\section*{Cel lekcji}
Potrafię wskazać trzy elementy dodane przez \texttt{heap\_4} do alokatora
liniowego --- \textbf{nagłówek bloku, listę wolnych bloków i scalanie} --- oraz
potwierdzić ich działanie na prawdziwym \texttt{heap\_4.c}.
\section*{Plan 30 minut}
\begin{tabularx}{\textwidth}{@{}p{1.55cm}p{3.0cm}X@{}}
\toprule
Czas & Tryb & Wynik \\
\midrule
0--3 & problem K\&R & nazwać, dlaczego jeden kursor nie zwolni obszaru ze środka \\
3--4 & kontekst & FreeRTOS linkuje jedną implementację; tutaj pracujemy tylko z \texttt{heap\_4} \\
4--9 & Demo 1 & przewidzieć nagłówek, first-fit i split \\
9--16 & Demo 2 & przewidzieć listę adresową i scalenie \texttt{2 -> 1} \\
16--27 & Task 3 RUN & osiem checkpointów E01--E08 prawdziwego \texttt{heap\_4.c} \\
27--30 & wyjście & odróżnić total free, largest free i minimum-ever \\
\bottomrule
\end{tabularx}
\vfill
\noindent\textbf{Jedna minuta kontekstu:} \texttt{heap\_1} przypomina rosnący
kursor bez \texttt{free}; \texttt{heap\_2} nie scala; \texttt{heap\_3} używa
alokatora biblioteki C; \texttt{heap\_5} rozszerza mechanikę \texttt{heap\_4}
na wiele regionów. Nie są to tematy tej lekcji.
\newpage
\section{Most: od K\&R 5.4 do \texttt{heap\_4}}
\begin{tabularx}{\textwidth}{@{}p{3.5cm}p{4.8cm}X@{}}
\toprule
K\&R 5.4 — co mamy & Ograniczenie & Co dodaje \texttt{heap\_4} \\
\midrule
\texttt{allocbuf[64]}, \texttt{allocp} & istnieje tylko wolny ogon areny & arena podzielona na opisane bloki \\
wynik to surowy adres & później nie znamy rozmiaru obszaru & nagłówek \texttt{BlockLink\_t} przed payloadem \\
przydział przesuwa kursor & nie wykorzystamy dziury & lista wolnych bloków i first-fit \\
reset całej areny & brak zwolnienia jednego obszaru & \texttt{vPortFree()} wskazanego bloku \\
brak relacji między dziurami & sąsiednie dziury są rozdzielone & porządek adresowy i coalescing \\
sukces albo \texttt{NULL} & brak miary fragmentacji & free, largest, block count, minimum-ever \\
\bottomrule
\end{tabularx}
\subsection*{Nagłówek realnego bloku}
\begin{lstlisting}
typedef struct A_BLOCK_LINK {
struct A_BLOCK_LINK *pxNextFreeBlock;
size_t xBlockSize; /* MSB oznacza blok przydzielony */
} BlockLink_t;
[ BlockLink_t | payload zwracany przez pvPortMalloc() ]
^ metadata ^ adres widziany przez aplikacje
\end{lstlisting}
\textbf{Granica modelu:} Task 1--2 używają uproszczonego nagłówka
\texttt{\{next,size\}}. Bit allocated i pełne zabezpieczenia oglądamy dopiero
w upstream \texttt{heap\_4.c}.
\section{Demo 1 — pierwszy pasujący blok i split (5 minut)}
Na RP2350/RV32 nagłówek modelu ma 8 B. Dla żądania 13 B:
\[
\texttt{wanted}=\operatorname{align8}(8+13)=24\ \mathrm{B}
\]
\begin{lstlisting}
free list przed: [ block 16 B ] -> [ block 256 B ] -> END
request po naglowku i align8: 24 B
\end{lstlisting}
\textbf{Predykcja — wpisz przed odsłonięciem:}
\begin{enumerate}
\item Pierwszy pasujący blok ma \blank{1.5cm} B.
\item Blok przydzielony ma \blank{1.5cm} B, a remainder \blank{1.5cm} B.
\item Równość zachowania areny: \blank{2cm} + \blank{2cm} = 256 B.
\end{enumerate}
\noindent\textbf{Sprawdzenie po predykcji:}
\texttt{16 B} nie mieści żądania, więc first-fit wybiera \texttt{256 B}; split
daje \texttt{24 B + 232 B}. Realny \texttt{heap\_4} dzieli blok tylko wtedy,
gdy reszta jest większa od minimalnego rozmiaru bloku.
\noindent\textbf{Nie uruchamiamy osobnego debugowania Task 1.} Model służy do
policzenia geometrii; centralny RUN jest w Task 3.
\newpage
\section{Demo 2 — lista adresowa i coalescing (7 minut)}
\begin{center}
\texttt{[ A:48 FREE ][ B:48 USED ][ C:48 FREE ]}
\end{center}
\begin{lstlisting}
pamiec: A -------- B -------- C
free list: A ------------------> C -> END
\end{lstlisting}
\textbf{Predykcja:} po \texttt{vPortFree(B)} narysuj listę i uzupełnij:
\begin{tabular}{@{}lccc@{}}
\toprule
Stan & liczba wolnych bloków & suma wolna & największy blok \\
\midrule
przed zwolnieniem B & \blank{1.2cm} & \blank{1.2cm} & \blank{1.2cm} \\
po zwolnieniu B & \blank{1.2cm} & \blank{1.2cm} & \blank{1.2cm} \\
\bottomrule
\end{tabular}
\subsection*{Odsłoń po zapisaniu predykcji}
\begin{enumerate}
\item Wstaw B między A i C według adresu.
\item \texttt{end(B) == address(C)}: połącz B z C.
\item \texttt{end(A) == address(BC)}: połącz A z BC.
\end{enumerate}
\begin{lstlisting}
przed: A:48 -> C:48 -> END blocks=2 total=96 largest=48
po: A:144 -> END blocks=1 total=144 largest=144
\end{lstlisting}
Porządek po adresie sprawia, że fizycznych sąsiadów można sprawdzić podczas
jednego wstawienia. Uproszczony Task 2 scala prawą, a następnie lewą stronę.
Właściwy \texttt{prvInsertBlockIntoFreeList()} z FreeRTOS V11.3.0 wykonuje
te same dwa testy w kolejności: poprzedni/lewy, następnie kolejny/prawy blok.
\section{Task 3 — jedyny centralny RUN (11 minut)}
Obraz i sesja muszą być przygotowane przed lekcją. Nie kompilujemy ani nie
flashujemy w czasie tych 30 minut.
\begin{lstlisting}[language=bash]
# wykonane przed lekcja:
stemctl debug rp2350 freertos heap4 3 --device /dev/bus/usb/BBB/DDD
\end{lstlisting}
\begin{lstlisting}
RESET/INIT
-> heap4_reset_checkpoint # E01: free=4080
-> heap4_alloc_a_checkpoint # E02: free=4032, used=48
-> heap4_alloc_b_checkpoint # E03: free=3968, used=64
-> heap4_alloc_c_checkpoint # E04: free=3936, used=32
-> heap4_free_a_checkpoint # E05: free=3984, blocks=2
-> heap4_fragmented_checkpoint # E06: free=4016, blocks=2
-> heap4_coalesced_checkpoint # E07: free=4080, blocks=1
-> task03_debug_checkpoint # E08: OOM, hook, PASS
\end{lstlisting}
\textbf{E01--E04:} sprawdź, że zużycie obejmuje wyrównany nagłówek całego
bloku, a nie tylko payload. \textbf{E05--E06:} porównaj sumę wolną z largest.
Przed przejściem z E06 do E07 ustaw tymczasowy breakpoint na
\texttt{prvInsertBlockIntoFreeList()} i sprawdź oba testy sąsiedztwa.
\textbf{E07--E08:} potwierdź odzyskanie areny, minimum-ever, OOM i hook.
\newpage
\section{Dowód na Pico 2 W / RP2350}
\textbf{Ważne:} \texttt{heap\_4} jest plikiem wybranym i linkowanym przez
projekt. Nie jest sprzętowym heapem ani peryferium RP2350. Pico 2 W dostarcza
realną pamięć SRAM, w której obserwujemy ten sam algorytm.
\subsection*{Minimalny widok GDB}
\begin{lstlisting}
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/160bx ucHeap
\end{lstlisting}
\subsection*{Tabela obserwacji}
\begin{tabularx}{\textwidth}{@{}p{4.2cm}p{3.0cm}X@{}}
\toprule
Wielkość & Odczyt & Warunek \\
\midrule
\texttt{initial\_free} & \blank{2.3cm} & punkt odniesienia \\
\texttt{after\_allocations} & \blank{2.3cm} & mniejsze niż initial \\
E06: free blocks & \blank{2.3cm} & równe 2 \\
E06: total / largest & \blank{2.3cm} & largest mniejsze niż total \\
E07: final free & \blank{2.3cm} & równe initial \\
E07: free blocks & \blank{2.3cm} & równe 1 \\
minimum-ever & \blank{2.3cm} & nie rośnie po free \\
OOM / hook / asserts / pass & \blank{2.3cm} & \texttt{1 / 1 / 0 / 1} \\
\bottomrule
\end{tabularx}
\section*{Wyjście — trzy minuty}
\begin{enumerate}
\item Dlaczego jeden \texttt{allocp} nie wystarcza do zwolnienia obszaru
spod offsetu 0 przy zachowaniu obszaru spod offsetu 5?\\[.35em]
\blank{.96\linewidth}
\item Jak \texttt{vPortFree()} znajduje rozmiar payloadu, skoro dostaje tylko
jego adres?\\[.35em]
\blank{.96\linewidth}
\item Dlaczego 96 wolnych bajtów w dwóch blokach nie pozwala przydzielić
jednego bloku 80 B?\\[.35em]
\blank{.96\linewidth}
\end{enumerate}
\subsection*{Zaliczenie}
\begin{itemize}
\item \checkline wymieniam: header, free list, coalescing;
\item \checkline przewiduję \texttt{2 bloki -> 1 blok};
\item \checkline odróżniam payload, metadata allocatora i stos \texttt{sp};
\item \checkline odróżniam current free, largest free i minimum-ever;
\item \checkline Task 3 kończy się
\texttt{aligned=1 oom=1 hooks=1 asserts=0 pass=1}.
\end{itemize}
\vfill
\noindent\textbf{Poza lekcją:} pełne czytanie upstream \texttt{heap\_4.c},
osobne uruchomienie Task 1--2 na AMD64/Hazard3, samodzielny deploy RP2350 oraz
porównanie z \texttt{heap\_1}, \texttt{heap\_2}, \texttt{heap\_3} i
\texttt{heap\_5}.
\end{document}