275 lines
9.5 KiB
TeX
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}
|