Files

275 lines
9.5 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,enumitem}
\definecolor{accent}{HTML}{16324A}
\definecolor{accentlight}{HTML}{EEF3F7}
\definecolor{rulegray}{HTML}{D7DEE5}
\hypersetup{colorlinks=true,linkcolor=accent,urlcolor=blue}
\IfFileExists{build-meta.tex}{\input{build-meta.tex}}{\newcommand{\BuildCommit}{local}}
\newcommand{\PublisherDomain}{mpabi}
\newcommand{\CardArea}{inf}
\newcommand{\CardSeries}{freertos-cpp}
\newcommand{\CardNumber}{06}
\newcommand{\CardCount}{16}
\newcommand{\CardSlug}{allocator-resource}
\newcommand{\CardVersion}{v00.01}
\newcommand{\DocumentUUID}{1af48a2e-8564-4e18-8115-b2c0208169d6}
\newcommand{\blank}[1]{\rule{#1}{.2pt}}
\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{K06 · FreeRTOS C++ · resource}}
\rhead{\small L05 · arena i typed allocator}
\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}
\begin{document}
\sloppy
\begin{center}
{\LARGE\bfseries \texttt{MemoryResource} i \texttt{FreeRtosAllocator<T>}}\par
\vspace{.25em}
{\large wybór storage w runtime bez vtable i bez przepełnienia}\par
\end{center}
\noindent\begin{tabularx}{\textwidth}{@{}p{1.65cm}Xp{1.55cm}X@{}}
\toprule
Karta & K06 / \CardCount & Czas & 30 minut \\
Platforma & Hazard3 / RV32I & Język & freestanding C++17 \\
Zasoby & heap + arena 256 B & Dispatch & context + 2 funkcje \\
Wersja & \CardVersion & UUID karty & \texttt{1af48a2e-...} \\
\bottomrule
\end{tabularx}
\section*{Jedna operacja typowana, dwie polityki}
Ten sam \texttt{FreeRtosAllocator<uint32\_t>} rezerwuje 16 elementów z:
\begin{itemize}
\item \texttt{HeapResource}: \texttt{pvPortMalloc/vPortFree}, pojedyncze free;
\item \texttt{StaticArenaResource}: bufor w \texttt{.bss}, liniowy offset,
pojedyncze deallocate nic nie robi, odzyskanie przez \texttt{reset()}.
\end{itemize}
\noindent\fcolorbox{accent}{accentlight}{%
\begin{minipage}{.94\textwidth}
\textbf{Kontrakt K06.} Przed \texttt{count*sizeof(T)} sprawdzamy dzielenie.
Porażka nie zmienia dostępnej pamięci ani offsetu. Widok zasobu nie posiada
kontekstu; provider musi żyć dłużej niż allocator.
\end{minipage}}
\section*{Plan 30 minut}
\noindent\begin{tabularx}{\textwidth}{@{}p{1.35cm}p{3.1cm}X@{}}
\toprule
Czas & Tryb & Dowód \\
\midrule
0--5 & kontrakt & trzy słowa: context, allocate, deallocate \\
5--10 & arytmetyka & guard przed mnożeniem count i sizeof(T) \\
10--16 & alokacja & 16 uint32 z heapu i 16 z areny \\
16--21 & adresy & ucHeap kontra globalny bufor .bss, alignment \\
21--26 & failure & overflow i capacity nie zmieniają storage \\
26--30 & release & heap free kontra arena deallocate/reset \\
\bottomrule
\end{tabularx}
\section*{Predykcja}
Rozmiar 16 elementów \texttt{uint32\_t}: \blank{2cm} B. Po arena-deallocate
\texttt{used}=\blank{2cm}; po reset \texttt{used}=\blank{2cm}.
\newpage
\section{Resource to wartość, nie hierarchia klas}
\begin{lstlisting}
class MemoryResource final {
void* context_;
void* (*allocate_)(void*, size_t, size_t) noexcept;
void (*deallocate_)(void*, void*, size_t, size_t) noexcept;
};
\end{lstlisting}
\begin{center}
\texttt{allocator -> MemoryResource -> function(context, bytes, alignment)}
\end{center}
\noindent\begin{tabularx}{\textwidth}{@{}p{4.0cm}p{4.0cm}X@{}}
\toprule
Element & Właściciel & Co sprawdzamy \\
\midrule
\texttt{MemoryResource} & nikt; non-owning view & 3 wskaźniki, bez vtable \\
\texttt{HeapResource} & liczniki providera & alignment $\le$ gwarancja portu \\
\texttt{StaticArenaResource} & bufor przekazany przez caller & base, capacity, used \\
\texttt{FreeRtosAllocator<T>} & nikt; kopiuje view & count, overflow, alignof(T) \\
\bottomrule
\end{tabularx}
Wpisz odczyty z checkpointu 1:
\noindent\begin{tabularx}{\textwidth}{@{}p{5.5cm}X@{}}
\toprule
Pole & Adres \\
\midrule
heap context & \blank{7cm} \\
arena context & \blank{7cm} \\
heap allocate function & \blank{7cm} \\
arena allocate function & \blank{7cm} \\
\bottomrule
\end{tabularx}
Funkcje allocate powinny być różne, ale kod wywołujący typed allocator jest
ten sam. Koszt dispatch to jawne pośrednie wywołanie, nie wirtualny obiekt.
\section{Guard mnożenia przed mnożeniem}
Uzupełnij:
\begin{lstlisting}
T* try_allocate(size_t count) const noexcept {
if (count == 0 || count > ________________________) {
return nullptr;
}
const size_t bytes = _____________________________;
return static_cast<T*>(resource.allocate_bytes(
bytes, ___________________));
}
\end{lstlisting}
\textbf{Dlaczego test po mnożeniu jest błędny?} Gdy wynik zawinie się do małej
liczby, allocator może zwrócić za mały, pozornie poprawny blok. W tej karcie
overflow nie wywołuje nawet funkcji providera.
\subsection*{Dwie klasy porażki}
\begin{enumerate}
\item \textbf{Arithmetic rejection}: count nie daje się bezpiecznie
przeliczyć; context nie jest dotykany.
\item \textbf{Capacity rejection}: liczba bajtów jest poprawna, ale provider
nie ma miejsca; zwraca \texttt{nullptr} i nie przesuwa storage.
\end{enumerate}
\newpage
\section{Ten sam bufor logiczny, dwa obszary adresowe}
\begin{center}
\texttt{HeapResource -> [16 * uint32] inside ucHeap}\\[.5em]
\texttt{ArenaResource -> [16 * uint32] inside g\_static\_arena\_storage}
\end{center}
\noindent\begin{tabularx}{\textwidth}{@{}p{4.7cm}p{3.4cm}X@{}}
\toprule
Dowód & Heap & Arena \\
\midrule
adres & \blank{3cm} & \blank{3cm} \\
zakres pamięci & \blank{3cm} & \blank{3cm} \\
\texttt{address \% alignof(uint32\_t)} & \blank{3cm} & \blank{3cm} \\
liczba elementów / bajtów & \blank{3cm} & \blank{3cm} \\
suma wpisanych wartości & \blank{3cm} & \blank{3cm} \\
\bottomrule
\end{tabularx}
\section*{Porażka ma zostawić stan storage bez zmian}
\begin{tabularx}{\textwidth}{@{}p{5.2cm}p{4.0cm}X@{}}
\toprule
Próba & Stan przed & Stan po \\
\midrule
overflow count dla heap allocator & free=\blank{2cm} & free=\blank{2cm} \\
overflow count dla arena allocator & used=\blank{2cm} & used=\blank{2cm} \\
za duży poprawny byte request heap & free=\blank{2cm} & free=\blank{2cm} \\
za duży poprawny byte request arena & used=\blank{2cm} & used=\blank{2cm} \\
\bottomrule
\end{tabularx}
Przy overflow provider failure count też się nie zmienia, bo wywołanie zostaje
odrzucone wcześniej. Przy capacity failure może wzrosnąć licznik nieudanych
\emph{prób}, ale ilość zajętej pamięci pozostaje taka sama.
\section*{Dwie polityki zwalniania}
\begin{center}
\begin{tabular}{c|c|c|c}
& po allocate & po deallocate & po reset \\
\hline
heap current used & 64+metadata & 0 względem baseline & nie dotyczy \\
arena used & 64 & \textbf{64} & \textbf{0}
\end{tabular}
\end{center}
Dlaczego arena nie może zwolnić „środkowego” bloku przez samo cofnięcie
offsetu? \blank{13cm}\\[1em]\blank{16cm}
Reset jest operacją zbiorczą. Wolno go wykonać dopiero, gdy żaden żywy obiekt
nie używa adresów z areny.
\newpage
\section{Hazard3/GDB: siedem checkpointów}
\begin{lstlisting}[language=bash]
make check
riscv64-unknown-elf-gdb build/task01_allocator_resource/prog.elf
b allocator_resource_debug_checkpoint
\end{lstlisting}
\noindent\begin{tabularx}{\textwidth}{@{}p{1cm}p{4.0cm}X@{}}
\toprule
STOP & Stan & Wymagany dowód \\
\midrule
1 & dwa resource views & context i function pointers zapisane \\
2 & dwa bufory żywe & heap w ucHeap, arena w .bss, alignment i sumy \\
3 & overflow rejected & oba nullptr; provider/storage bez zmian \\
4 & capacity rejected & oba nullptr; available/used bez zmian \\
5 & heap deallocated & heap current free wraca do baseline \\
6 & arena deallocated & arena used nadal 64 \\
7 & arena reset & arena used 0; liczniki i pass=1 \\
\bottomrule
\end{tabularx}
\subsection*{Odczyty}
\begin{lstlisting}
p/x g_heap_address
p/x g_arena_address
p/x g_arena_storage_begin
p/x g_arena_storage_end
p g_heap_after_capacity_failure
p g_arena_used_after_allocation
p g_arena_used_after_deallocate
p g_arena_used_after_reset
p g_heap_successful_resource_allocations
p g_heap_failed_resource_allocations
p g_arena_successful_resource_allocations
p g_arena_failed_resource_allocations
p g_allocator_resource_pass
\end{lstlisting}
\section*{Zaliczenie}
\begin{itemize}
\item $\square$ rysuję 3-słowowy resource i nie nazywam go vtable;
\item $\square$ stosuję \texttt{count > SIZE\_MAX/sizeof(T)} przed mnożeniem;
\item $\square$ klasyfikuję heap address i arena address po zakresach;
\item $\square$ pokazuję niezmieniony storage po obu rodzajach failure;
\item $\square$ odróżniam heap free, arena deallocate i arena reset;
\item $\square$ wiem, że view nie posiada providera ani bufora.
\end{itemize}
\section*{Wyjście}
Dlaczego \texttt{reset()} nie może być ukryty w destruktorze dowolnego
allocatora? \blank{12cm}\\[1em]
Co musiałoby żyć dłużej: \texttt{FreeRtosAllocator<T>} czy jego provider?
\\[.4em]\blank{8cm}
\vfill
\noindent\textbf{Następna karta K07:} posiadający obiekt taska, jawne
\texttt{start()}, stabilny kontekst i statyczny trampoline C $\rightarrow$ C++.
\end{document}