\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}}\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} 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} & 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(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} 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}