\documentclass[a4paper,french,12pt]{article}

\title{Programmation Multitâches et Temps Réel}
\author{}
\date{Dernière compilation~: \today{} à \currenttime}

\usepackage{styles}
\usepackage{tikz}
\usetikzlibrary{shapes.multipart}
\usepackage{xfrac}
\usepackage{xcolor,colortbl}

\begin{document}

\maketitle
\tableofcontents

\clearpage

\section{Système d'exploitation temps réel}

    Un système d'exploitation est considéré temps réel si le changement de contexte et le temps de réponse sont inférieurs à 10$\mu{}s$.

    Quelle est la différence entre un OS classique et un OS temps réel~?
    Un OS classique utilise la notion de temps partagé~:

    \begin{itemize}
        \item ordonnancement complexe
        \item priorité peu considérée (partage équitable)
        \item gestion des temps CPU limitée
        \item incertitudes sur les temps
        \item noyau non préemptif
        \item la gestion des I/O peut générer des temps morts
    \end{itemize}

    Un OS temps réel~:

    \begin{itemize}
        \item ordonnancement temps réel
        \item gestion strict de la priorité
        \item perception du temps (donc plus de précision)
        \item noyau préemptif
        \item primitives de synchronisation
        \item respect total ou partiel de la norme POSIX
    \end{itemize}

    \subsection{Contraintes matérielles}

        \begin{itemize}
            \item processeur~: horloges, \ldots
            \item mémoire~: RAM, ROM, EPROM, \ldots
            \item interruption du processeur
            \item I/O~: les bus
        \end{itemize}

    \subsection{Validation d'une application en temps réel}

        \begin{itemize}
            \item Par prototypage, par exemple la méthodologie $A^3$ (adéquation, algorithme, architecture) pour Cycab (véhicule électrique semi-autonome).
            \item Par analyse statistique (projet Heptane de l'IRISA).
            \item Par simulation (simulateur RTFSim).
            \item Par techniques hybrides.
        \end{itemize}

    \subsection{Contraintes temporelles}

        Elles peuvent être strictes~: le dépassement d'une échéance conduit à un état grave.
        Par exemple contrôle d'une unité de production industrielle (centrale nucléaire, usine chimique\ldots).
        Il faut alors respecter strictement toutes les contraintes temporelles.

        Elles peuvent être souples~: le dépassement d'une échéance conduit à un état dégradé mais non grave.

        Elles peuvent aussi être fermes~: la contrainte est sévère mais une faible probabilité de manquer les limites temporelles peut être tolérée.

        En réalité, la majorité des systèmes temps réel sont hybrides.
        Certaines tâches sont à contraintes souples, d'autres ont des contraintes strictes.

    \subsection{Modèles d'arrivées des tâches}

        \paragraph{Modèle périodique}

        \paragraph{Modèle apériodique}

            Une tâche apériodique ne s'exécute~:

            \begin{itemize}
                \item qu'une seule fois (inter-arrivée infinie)
                \item à un instant à priori inconnu par le système
            \end{itemize}

        \paragraph{Modèle sporadique}

            Une tâche sporadique est une tâche~:

            \begin{itemize}
                \item dont on connait l'inter-arrivée minimale séparant deux arrivées successives (abusivement appelée la période)
                \item dont on ne connaît pas l'inter-arrivée maximale
                \item dont les dates d'activation sont à priori inconnues
            \end{itemize}

    \subsection{Échéances}

        \begin{itemize}
            \item L'échéance relative~: c'est une durée.
            \item L'échéance absolue de terminaison au plus tard~: c'est une date.
                Il y a l'échéance de terminaison au plus tôt et l'échéance de démarrage au plus tard.
        \end{itemize}

        Quand on parle juste d'échéance, on parle d'échéance de terminaison au plus tard.

    \subsection{Conception}

        \begin{itemize}
            \item Déterminaison des modules sujets aux temps réel.
            \item Description des contraintes temporelles.
            \item Représentation logique et temporelle du système.
            \item Prédéterminaison des paramètres temporels.
            \item Choix de l'architecture logicielle et matérielle.
            \item Évaluation des performances.
            \item Validation de l'application.
        \end{itemize}

    \subsection{Support langage}

        Un langage de programmation temps réel doit pouvoir~:

        \begin{itemize}
            \item manipuler des tâches
            \item gérer la synchronisation
            \item évaluer le comportement des tâches
        \end{itemize}

        Les points importants considérés par la spécification du langage~:

        \begin{itemize}
            \item ordonnancement des tâches
            \item gestion de la mémoire
            \item partage des ressources et synchronisation
            \item horloges et timers
        \end{itemize}

\section{Ordonnancement}

    Dans un système multi-tâches, de nombreux processus attendent qu'un évènement se produise.
    Ils n'ont pas besoin du processeur, mais doivent pouvoir l'obtenir en priorité.

    \subsection{Types d'ordonnancement}

        \begin{itemize}
            \item Souples
            \item Strictes
                \begin{itemize}
                    \item Dynamique
                    \item Statique
                \end{itemize}
        \end{itemize}

    Le rôle d'un ordonnanceur temps réel est de vérifier la faisabilité du système.
    Il garantit que l'arrivée d'une nouvelle tâche, acceptée par l'ordonnanceur, sera ordonnançable.

    \subsection{Priorités}

        Les ordonnanceurs peuvent être à priorité fixe (chaque tâche conserve sa priorité durant toute sa durée de vie), ou à priorité dynamique (la priorité d'une tâche peut changer pendant son exécution).
        Les ordonnanceurs à priorité fixe sont plus répandus.

        Les priorités peuvent être déterminées selon~: l'échéance relative (D~: durée), la période (T~: échéance) ou l'importance de la tâche (P).

        On peut alors avoir~:

        \begin{itemize}
            \item Rate Monotonic (RM) ---
                La tâche ayant la plus petite période est la plus prioritaire.
            \item Deadline Monotonic (DM) ---
                La tâche ayant la plus petite échéance relative est la plus prioritaire.
            \item Highest Priority First (HPF) ---
                Les priorités sont attribuées aux tâches selon leur importance.
        \end{itemize}

        Exemples d'ordonnanceurs à priorités dynamiques~:

        \begin{itemize}
            \item La tâche peut changer de priorité à chacune de ses activations, selon un des paramètres variables suivants~:
                \begin{itemize}
                    \item D (échéance absolue)
                    \item T (période)
                    \item C (temps d'exécution restant)
                \end{itemize}
            \item Earliest Deadline First (EDF) ---
                La tâche la plus proche de son échéance absolue aura la priorité la plus élevé.
                C'est l'ordonnanceur le plus optimal.
            \item Least Laxity First (LLF) ---
                La tâche la plus prioritaire est celle de moindre laxité (différence entre l'échéance absolue et l'instant de terminaison de la tâche si on l'exécute totalement).
        \end{itemize}

    \subsection{Analyse de faisabilité}

        Calcul de la charge du processeur (U = CPU Utilization), le temps processeur nécessaire à l'exécution des tâches~:

        \begin{align*}
            U = \sum_{i=1}^n \frac{C_i}{T_i} \leq 1
            \quad \text{avec}
            \left\{
            \begin{array}{l}
                C \text{ le coût de la tâche } \\
                T \text{ la période de la tâche } \\
            \end{array}
            \right.
        \end{align*}

        \paragraph{Ordonnanceur à priorités fixes}

            Dans le cas d'un ordonnanceur à priorités fixes (Rate Monotonic), la charge maximale est inférieure à 1.
            Elle dépend du nombre de tâches dans le système~:

            \begin{align*}
                U = \sum_{i=1}^n \frac{C_i}{T_i} \leq U_{RM} \quad \text{avec} \quad U_{RM} = n (2^{\frac{1}{n}} - 1)
            \end{align*}

            Les tests ne prennent pas en considération le coût système.
            Une solution est de déterminer le coût système par des tests, et d'intégrer le coût dans la faisabilité.

            Calcul du temps de réponse d'une tâche (avec une priorité fixe et $D \leq T$)~:

            \begin{align*}
                r_i^{n+1} = C_i + \sum_{j \in hp(i)} \left\lceil\frac{r_i^n}{T_j}\right\rceil C_j \quad \text{avec } r_i^0 = C_i
            \end{align*}

        \paragraph{Ordonnanceur à priorités dynamiques}

            Avec l'exemple du Earliest Deadline First (EDF)~: la priorité change à chaque activation, on n'obtient donc pas forcément le pire temps de réponse dès la première activation.
            Il faut donc un calcul de temps de réponse sur plusieurs activations.
            La durée d'étude doit englober le pire cas.

            \begin{itemize}
                \item Solution 1 ---
                    On prend le plus petit commun multiplicateur (ppcm) des périodes des tâches.
                    Deux tâches avec des périodes égales à 12 et 15 auront une période de test égale à 60.

                    Ainsi, la durée comprend tous les scénari possibles.
                    Par contre, elle peut être très grande.

                \item Solution 2 ---
                    On trouve une période d'étude (cycle d'exécution) réduite mais dans laquelle se trouve le pire cas.
                    Cette période d'étude s'appelle \emph{Busy Period}.
                    Déterminer cette période consiste à déterminer la durée sur laquelle se porte l'analyse.
            \end{itemize}

            Définitions~:

            \begin{itemize}
                \item \emph{Point d'inactivité (idle point)} ---
                    L'instant dans lequel le processeur est inactif (il n'exécute aucune tâche).
                \item \emph{Période active (busy period, BP)} ---
                    Intervalle de temps entre deux instants inactifs et qui ne contient pas d'instant inactif.
            \end{itemize}

            Le taux d'utilisation du processeur est optimal pendant la busy period.
            La fin de la busy period coïncide avec l'idle time.

            Le calcul de la busy period se fait de la manière suivante~:

            \begin{itemize}
                \item on cherche $t$ tel que $t = W(t)$
                \item on commence avec $t = 1$
                \item on s'arrête lorsque deux valeurs successives sont identiques
            \end{itemize}

            \begin{align*}
                W(t) = \sum_{i=1}^n \left\lceil\frac{t}{T_i}\right\rceil C_i
            \end{align*}

            L'ensemble $A$ représentant tous les instants d'activation dans la période active, le calcul du pire temps de réponse d'une tâche nécessite d'examiner plusieurs dates d'activation $a \in A$.
            Le temps de réponse correspond alors à~:

            \begin{align*}
                r_i = \max_{a \in A} (r_i(a))
            \end{align*}

        \paragraph{Temps réel --- ordonnanceur préemptif vs.\ non préemptif}

            La condition nécessaire et suffisante pour un système temps réel est~:
            \begin{equation*}
                \forall i : Z_i \leq D_i
            \end{equation*}

            Pour cela il y a deux manières de calculer la faisabilité, en fonction de si l'ordonnanceur est préemptif ou \textcolor{red}{non}~:
            \begin{equation*}
                Z_i^n = C_i + \sum_{j\in \mathrm{hp}(i)} \left\lceil\frac{r_i^{n-1}}{T_j}\right\rceil \cdot C_j \,\textcolor{red}{+ \max_{k\in\mathrm{lp}(i)}(C_k)}
            \end{equation*}

    \subsection{Optimalité}

        On dit qu'un ordonnanceur est optimal lorsqu'il trouve une solution d'ordonnancement à chaque fois que cette solution existe.
        Lorsqu'il ne trouve pas de solution d'ordonnancement, alors aucun autre algorithme d'ordonnancement ne sera capable d'en trouver une.

        \begin{itemize}
            \item RM --- préemptif, $D_i = T_i$
            \item DM --- préemptif, $D_i \leq T_i$
            \item LLF --- préemptif
            \item EDF --- toujours
        \end{itemize}

    \subsection{Synchronisation}

        On cherche à éliminer les accès concurrents.
        Cela s'appelle l'\emph{exclusion mutuelle}.
        Toutes les instructions manipulant une ou plusieurs ressources critiques forment une section critique.

        La protection se fait par verrou~: sémaphores, mutex, ou variables conditionnelles.

    \subsection{Inversion de priorité}

        Une tâche de haute priorité peut se bloquer sur une section critique par une tâche moins prioritaire.
        Cela laisse le temps à une autre tâches moins prioritaire d'être exécuté avant.

    \subsection{Interblocage}

        Quand deux tâches ont besoin d'accéder simultanément à deux ressources partagées, elles se bloquent mutuellement.

    \subsection{Héritage de priorité}

        Le protocole d'héritage de priorité (Priority Inheritance Protocol, PIP) vise à éviter les blocages.
        Si une tâche de basse priorité détient le verrou sur une ressource demandée par une tâche plus prioritaire, il faut élever la priorité de la première tâche au niveau de la tâche prioritaire jusqu'à ce qu'elle libère la ressource.

        \paragraph{Protocole}

            Lorsqu'une tâche $\tau_i$ demande une ressource $R_k$, on a l'une des situations suivantes~:

            \begin{itemize}
                \item $R_k$ est libre, $\tau_i$ prend la ressource.
                \item $R_k$ n'est pas libre (détenue par une tâche moins prioritaire).
            \end{itemize}

            Le plus mauvais cas de blocage est la somme du blocage direct et du blocage par héritage de priorité.

        \paragraph{PIP --- limites}

            \begin{itemize}

                \item Interblocages (deadlock) ---
                    Le protocole PIP ne permet pas d'éviter le problème d'interblocage.

                \item Chaîne de blocage (blocking chain) ---
                    Une tâche peut être bloquée autant de fois que le nombre de sections critiques qu'elle utilise.

            \end{itemize}

        \paragraph{PCP --- Protocole de priorité plafond}

            Le PCP (Prority Ceiling Protocol) permet d'éviter l'interblocage ainsi que la chaîne de blocage.
            Le protocole est plus simple que PIP mais difficilement implémentable.

            Lorsqu'une tâche demande une ressource, elle se bloque lorsque~:

            \begin{itemize}
                \item La ressource n'est pas libre (détenue par une tâche moins prioritaire)
                \item La ressource est libre mais la priorité plafond d'une autre ressource en utilisation est plus grande que la priorité de la tâche.
            \end{itemize}

\end{document}
