\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,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}{11} \newcommand{\CardCount}{16} \newcommand{\CardSlug}{mutex} \newcommand{\CardVersion}{v00.01} \newcommand{\DocumentUUID}{34d7ad61-6b03-4922-8e58-b8b98316e5f7} \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{K11 · FreeRTOS C++ · mutex}}\rhead{\small L10 · owner + inheritance} \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{Mutex}, \texttt{LockGuard} i inheritance}\par \vspace{.25em}{\large owner, trzy early returns i zmiana LOW 1--3--1}\par \end{center} \noindent\begin{tabularx}{\textwidth}{@{}p{1.65cm}Xp{1.75cm}X@{}} \toprule Karta & K11 / \CardCount & Czas & 30 minut \\ Platforma & Hazard3 / RV32I & Taski & LOW/MEDIUM/HIGH \\ Priorytety & 1 / 2 / 3 & Storage & static, heap delta 0 \\ Wersja & \CardVersion & UUID karty & \texttt{34d7ad61-...} \\ \bottomrule \end{tabularx} \section*{Dlaczego mutex nie jest binary semaphore?} \noindent\begin{tabularx}{\textwidth}{@{}p{4.3cm}p{4cm}X@{}} \toprule Właściwość & Mutex & Binary semaphore \\ \midrule owner & tak & nie \\ give & tylko owner & sygnał od producenta \\ priority inheritance & tak & nie \\ ISR give & nie & wariant FromISR istnieje \\ \bottomrule \end{tabularx} \noindent\fcolorbox{accent}{accentlight}{\begin{minipage}{.94\textwidth} \textbf{Kontrakt K11.} HIGH blokuje się na mutexie LOW. Kernel tymczasowo podnosi LOW do priorytetu HIGH, aby MEDIUM nie przedłużał inwersji. Destruktor guarda zwalnia dokładnie raz; ordinary mutex nie jest rekurencyjny ani ISR-safe. \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 & semantics & owner, give i różnica od semaphore \\ 5--10 & guard & trzy return, jeden unlock na ścieżkę \\ 10--15 & setup & LOW bierze; HIGH próbuje; MEDIUM ready \\ 15--20 & inherit & HIGH blocked; LOW 1--3 \\ 20--25 & handoff & LOW przed MEDIUM; potem HIGH owner \\ 25--30 & restore & LOW wraca do 1; counter=11 \\ \bottomrule \end{tabularx} \section*{Predykcja} Kto wykona się po zablokowaniu HIGH: LOW czy MEDIUM? \blank{3cm}. Jaki będzie bieżący priorytet LOW? \blank{2cm}. \newpage \section{LockGuard i granice ownership} \begin{lstlisting} { freertos::LockGuard guard{mutex, timeout}; if (!guard.owns_lock()) return failed; // protected work; every return crosses guard destructor } \end{lstlisting} \noindent\begin{tabularx}{\textwidth}{@{}p{1.5cm}p{3cm}p{3cm}X@{}} \toprule Path & Take & Give po return & Wynik \\ \midrule 0 & success & \blank{2cm} & \texttt{10} \\ 1 & success & \blank{2cm} & \texttt{20} \\ 2 & success & \blank{2cm} & \texttt{30} \\ \bottomrule \end{tabularx} \section*{Ordinary oznacza non-recursive} Ten sam task próbuje wziąć już posiadany mutex z timeout 0. Wynik: \blank{3cm}. Czy nieudany \texttt{LockGuard} może wywołać give w destruktorze? \blank{2cm}. \section{Kontrolowany scenariusz inwersji} \begin{center} \texttt{LOW(1) take -- HIGH(3) attempt/block -- LOW inherits 3}\\[.25em] \texttt{LOW release -- HIGH take/give -- MEDIUM(2) run -- LOW back at 1} \end{center} \noindent\begin{tabularx}{\textwidth}{@{}p{1.25cm}p{3.2cm}p{3.7cm}X@{}} \toprule Nr & Aktor & Zdarzenie & Odczyt \\ \midrule 1 & LOW & take, owner=LOW & \blank{2.4cm} \\ 2 & HIGH & attempt, state=Blocked & \blank{2.4cm} \\ 3 & LOW & priority inherited, release & \blank{2.4cm} \\ 4 & HIGH & take, owner=HIGH & \blank{2.4cm} \\ 5 & MEDIUM & pierwszy run & \blank{2.4cm} \\ 6 & LOW & priority restored & \blank{2.4cm} \\ \bottomrule \end{tabularx} Dlaczego MEDIUM nie może znaleźć się między krokiem 2 i release LOW?\\[.5em] \blank{16cm} \newpage \section{Dowód priority inheritance} \noindent\begin{tabularx}{\textwidth}{@{}p{5.4cm}p{3.2cm}X@{}} \toprule Pomiar & Predykcja & Odczyt \\ \midrule LOW base priority & 1 & \blank{2.5cm} \\ HIGH priority & 3 & \blank{2.5cm} \\ HIGH state podczas wait & Blocked & \blank{2.5cm} \\ LOW current podczas wait & 3 & \blank{2.5cm} \\ MEDIUM ran before release & 0 & \blank{2.5cm} \\ LOW po release & 1 & \blank{2.5cm} \\ \bottomrule \end{tabularx} \section{Owner i chroniony stan} \noindent\begin{tabularx}{\textwidth}{@{}p{5cm}p{3.7cm}X@{}} \toprule Relacja & Predykcja & Odczyt \\ \midrule holder po LOW take & LOW handle & \blank{2.5cm} \\ holder po handoff & HIGH handle & \blank{2.5cm} \\ counter po LOW & 1 & \blank{2.5cm} \\ counter po HIGH & 11 & \blank{2.5cm} \\ mutex handle & caller control & \blank{2.5cm} \\ heap delta create/destroy & 0 / 0 & \blank{2.5cm} \\ \bottomrule \end{tabularx} \section*{Co inheritance rozwiązuje, a czego nie?} Rozwiązuje ograniczoną inwersję powodowaną przez ownera mutexa. Nie skraca długiej sekcji, nie naprawia deadlocku, nie nadaje porządku wielu mutexom i nie pozwala zwolnić mutexa z innego taska. Zaproponuj stały porządek przejmowania dwóch mutexów A/B:\\[.5em] \blank{16cm}\\[1em] Co stanie się z inheritance, gdy LOW trzyma kilka mutexów?\\[.5em] \blank{16cm} \section*{Kontrakt czasu} Timeout w konstruktorze guarda jest jawny. \texttt{portMAX\_DELAY} w tym eksperymencie jest świadomym oczekiwaniem HIGH, nie ukrytym defaultem API. \newpage \section{Hazard3/GDB i zaliczenie} \begin{lstlisting}[language=bash] make check riscv64-unknown-elf-gdb build/task01_mutex/prog.elf b mutex_debug_checkpoint \end{lstlisting} \begin{lstlisting} p g_low_base_priority p g_low_inherited_priority p g_low_priority_after_release p g_high_state_seen_by_low p g_medium_ran_before_release p/x g_owner_low p/x g_owner_high p g_sequence_low_take p g_sequence_high_attempt p g_sequence_low_release p g_sequence_high_take p g_sequence_medium_run p g_shared_counter p g_mutex_pass \end{lstlisting} \section*{Zaliczenie} \begin{itemize} \item $\square$ odróżniam mutex owner/inheritance od binary semaphore; \item $\square$ pokazuję give guarda na trzech early returns; \item $\square$ dowodzę HIGH=Blocked i LOW 1--3--1; \item $\square$ MEDIUM nie wykonuje się przed release LOW; \item $\square$ owner równa się właściwemu handle LOW/HIGH; \item $\square$ event order i chroniony counter=11 kończą się PASS. \end{itemize} \section*{Wyjście} Dlaczego binary semaphore nie naprawi tej inwersji?\\[.5em]\blank{16cm}\\[1em] Dlaczego ordinary mutex nie wolno brać drugi raz?\\[.5em]\blank{16cm} \vfill \noindent\textbf{Następna karta K12:} binary/counting semaphore oraz jednoodbiorcze task notifications z porównaniem storage i semantyki. \end{document}