Files

245 lines
9.3 KiB
TeX

\documentclass[10pt]{article}
\usepackage[T1]{fontenc}\usepackage[utf8]{inputenc}\usepackage[polish]{babel}
\usepackage[a4paper,margin=1.55cm]{geometry}
\usepackage{array,tabularx,booktabs,amsmath,amssymb,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}{07}
\newcommand{\CardCount}{16}
\newcommand{\CardSlug}{task-wrapper}
\newcommand{\CardVersion}{v00.01}
\newcommand{\DocumentUUID}{9bffead0-4341-4350-806d-e975b1a3a08f}
\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{K07 · FreeRTOS C++ · Task}}\rhead{\small L06 · start i trampoline}
\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{TaskHandle\_t}, jawny \texttt{start()} i trampoline}\par
\vspace{.25em}{\large jeden stabilny obiekt przez granicę C $\rightarrow$ C++}\par
\end{center}
\noindent\begin{tabularx}{\textwidth}{@{}p{1.65cm}Xp{1.55cm}X@{}}
\toprule
Karta & K07 / \CardCount & Czas & 30 minut \\
Platforma & Hazard3 / RV32I & Język & freestanding C++17 \\
Storage & dynamiczny TCB + stack & Dispatch & static CRTP trampoline \\
Wersja & \CardVersion & UUID karty & \texttt{9bffead0-...} \\
\bottomrule
\end{tabularx}
\section*{Trzy adresy, trzy role}
\begin{center}
\texttt{C++ object} $\ne$ \texttt{TaskHandle\_t/TCB} $\ne$
\texttt{task stack storage}
\end{center}
Konstruktor zapisuje tylko konfigurację. Dopiero \texttt{start()} woła
\texttt{xTaskCreate}, a kernel zapamiętuje adres kompletnego obiektu jako
\texttt{void*}. Statyczny trampoline odzyskuje typ i uruchamia prywatne
\texttt{run()} bez vtable.
\noindent\fcolorbox{accent}{accentlight}{\begin{minipage}{.94\textwidth}
\textbf{Inwariant K07.} Adres obiektu od startu do końca taska jest stały.
Wrapper nie jest kopiowalny ani przenoszalny. Handle identyfikuje TCB, a nie
obiekt aplikacyjny. Destruktor nie wykonuje wymuszonego delete taska.
\end{minipage}}
\section*{Plan 30 minut}
\noindent\begin{tabularx}{\textwidth}{@{}p{1.35cm}p{3cm}X@{}}
\toprule
Czas & Tryb & Dowód \\
\midrule
0--5 & adresy & object, TCB/handle i stack to różne rzeczy \\
5--10 & explicit start & konstruktor heap delta 0; start tworzy kernel storage \\
10--16 & C API & sześć argumentów xTaskCreate \\
16--21 & trampoline & raw context = object = this in run \\
21--26 & lifecycle & ready, running, completed, null handle, self-delete \\
26--30 & GDB/exit & local na task stack; bez move/vtable/destructor-delete \\
\bottomrule
\end{tabularx}
\section*{Predykcja}
Który adres znajdzie się w \texttt{TaskHandle\_t}: obiekt czy TCB?
\blank{4cm}\quad Czy konstruktor może uruchomić task? \blank{4cm}
\newpage
\section{Dlaczego start nie należy do konstruktora}
W czasie konstruktora bazowego część derived nie jest jeszcze kompletna.
Scheduler mógłby wywołać \texttt{run()} natychmiast po create. Dlatego:
\begin{lstlisting}
CounterTask worker; // tylko konfiguracja, heap bez zmian
bool ok = worker.start(); // dopiero teraz xTaskCreate
\end{lstlisting}
\noindent\begin{tabularx}{\textwidth}{@{}p{4.4cm}X@{}}
\toprule
Stan wrappera & Znaczenie \\
\midrule
\texttt{constructed} & kompletny obiekt, brak taska kernela \\
\texttt{starting} & wejście do jawnej operacji start \\
\texttt{ready} & context i konfiguracja przekazywane do kernela \\
\texttt{running} & trampoline odzyskał typed object \\
\texttt{completed} & member body wrócił, handle wyzerowany \\
\texttt{start\_failed} & create nie utworzył taska; handle null \\
\bottomrule
\end{tabularx}
Drugie \texttt{start()} musi zwrócić \blank{2cm}, wykonać
\blank{2cm} nowych \texttt{xTaskCreate} i pozostawić handle
\blank{5cm}.
\section{Rozpisz sześć argumentów \texttt{xTaskCreate}}
\begin{lstlisting}
xTaskCreate(&Task::trampoline,
name_, stack_words_,
static_cast<Derived*>(this),
priority_, &handle_);
\end{lstlisting}
\noindent\begin{tabularx}{\textwidth}{@{}p{5.5cm}X@{}}
\toprule
Argument & Co niesie / jednostka \\
\midrule
\texttt{\&Task::trampoline} & \blank{10cm} \\
\texttt{name\_} & \blank{10cm} \\
\texttt{stack\_words\_} & \blank{10cm} \\
typed context: \texttt{Derived*} z \texttt{this} & \blank{9cm} \\
\texttt{priority\_} & \blank{10cm} \\
\texttt{\&handle\_} & \blank{10cm} \\
\bottomrule
\end{tabularx}
\textbf{Pułapka jednostki:} stack depth jest liczbą \texttt{StackType\_t}, nie
domyślnie bajtów. Dla RV32I 256 słów to \blank{2cm} B.
\newpage
\section{Trampoline odzyskuje typ, nie tworzy obiektu}
\begin{lstlisting}
static void trampoline(void* context) {
Derived* derived = static_cast<Derived*>(context);
Task& base = *static_cast<Task*>(derived);
base.state_ = TaskState::running;
derived->run();
base.state_ = TaskState::completed;
base.handle_ = nullptr;
vTaskDelete(nullptr);
}
\end{lstlisting}
Nie ma \texttt{new}, lookupu ani vtable. Context jest adresem istniejącego,
kompletnego obiektu. Prywatny \texttt{run()} jest dostępny dzięki jawnej relacji
\texttt{friend} z konkretną instancją CRTP.
\section*{Trzy domeny adresów}
\noindent\begin{tabularx}{\textwidth}{@{}XXX@{}}
\toprule
Tożsamość obiektu C++ & Tożsamość TCB & Task stack \\
\midrule
before start: \blank{3.1cm} & handle after start: \blank{3cm} & low: \blank{3cm} \\
after start: \blank{3.1cm} & current handle: \blank{3cm} & high: \blank{3cm} \\
trampoline context: \blank{3.1cm} & status TCB: \blank{3cm} & local: \blank{3cm} \\
\texttt{this} in run: \blank{3.1cm} & & watermark: \blank{3cm} \\
completed object: \blank{3.1cm} & & \\
\bottomrule
\end{tabularx}
\begin{center}
\texttt{object identities equal}\qquad
\texttt{handle == current == TCB}\qquad
\texttt{low <= local <= high}
\end{center}
\section*{Maszyna stanów wrappera}
\begin{center}
\texttt{constructed -> starting -> ready -> running -> completed}\\[.5em]
\texttt{constructed -> starting -> ready -> start\_failed} (gdy create fail)
\end{center}
Dlaczego stan \texttt{ready} jest ustawiany przed wejściem do
\texttt{xTaskCreate}? Przy aktywnym schedulerze nowy task może wykonać się, a
nawet zakończyć, zanim wywołanie create zwróci do startera. Kod po powrocie nie
może nadpisać \texttt{completed} ponownie stanem \texttt{ready}.
\section*{Predykcja duplikatu}
Drugie \texttt{start()} na tym samym obiekcie powinno:
\begin{itemize}
\item zwrócić \blank{2cm};
\item wywołać \texttt{xTaskCreate} jeszcze raz? \blank{2cm};
\item zmienić handle? \blank{2cm};
\item pozostawić stan: \blank{3cm}.
\end{itemize}
\section*{Granica destruktora}
Destruktor obiektu na innym stosie nie może bezpiecznie „sprzątnąć” dowolnego
żywego taska: wymuszone \texttt{vTaskDelete(handle)} nie odwija jego ramek C++.
K07 kończy body normalnie i dopiero trampoline usuwa bieżący task.
\newpage
\section{Hazard3/GDB i kontrakt zaliczenia}
\begin{lstlisting}[language=bash]
make check
riscv64-unknown-elf-gdb build/task01_task_wrapper/prog.elf
b explicit_task_debug_checkpoint
\end{lstlisting}
\begin{lstlisting}
p g_checkpoint_trace
p/x g_object_before_start
p/x g_trampoline_context
p/x g_run_this
p/x g_handle_after_start
p/x g_tcb_address
p/x g_stack_low
p/x g_stack_high
p/x g_stack_local
p g_counter_result
p g_task_wrapper_pass
\end{lstlisting}
\noindent\begin{tabularx}{\textwidth}{@{}p{1cm}p{3.8cm}X@{}}
\toprule
STOP & Stan & Obowiązkowy dowód \\
\midrule
1 & constructed & heap konstrukcji bez zmian, handle null \\
2--3 & start & starting/ready, context wskazuje pełny obiekt \\
4 & create returned & handle wskazuje TCB w ucHeap \\
5 & before scheduler & object address nadal ten sam \\
6--7 & trampoline & context odzyskany, state running \\
8 & private run & handle/current/TCB równe; local w stack; wynik 500500 \\
10 & completion & state completed; handle null; self-delete \\
11--12 & verifier & wszystkie relacje i pass=1 \\
\bottomrule
\end{tabularx}
\section*{Zaliczenie}
\begin{itemize}
\item $\square$ odróżniam object, handle/TCB i task stack;
\item $\square$ pokazuję zerowy heap delta konstruktora i jawny create;
\item $\square$ opisuję sześć argumentów \texttt{xTaskCreate};
\item $\square$ śledzę ten sam context przez trampoline do \texttt{run()};
\item $\square$ pokazuję local w stack bounds oraz wynik 500500;
\item $\square$ uzasadniam brak copy/move, vtable i delete w destruktorze.
\end{itemize}
\section*{Wyjście}
Dlaczego handle nie może być użyty jako adres obiektu aplikacyjnego?\\[.4em]
\blank{12cm}\\[1em]
Co chroni jawny \texttt{start()}?\\[.4em]\blank{11cm}
\vfill
\noindent\textbf{Następna karta K08:} dynamiczny i statyczny TCB/stack,
słowa kontra bajty, jawne zakończenie i czas odzyskania pamięci.
\end{document}