\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(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(context); Task& base = *static_cast(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}