Synthèse os

This commit is contained in:
Debucquoy Anthony 2024-01-18 15:45:17 +01:00
parent a66a649b48
commit d2ffcf8c9a
Signed by: tonitch
GPG Key ID: A78D6421F083D42E
5 changed files with 389 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

View File

@ -0,0 +1,389 @@
\documentclass{article}
\usepackage[utf8]{inputenc}
\usepackage[T1]{fontenc}
\usepackage[french]{babel}
\usepackage{graphicx}
\usepackage{amsmath, amsfonts, amssymb, amsthm}
\title{Synthèse Système d'éxploitation}
\author{Debucquoy Anthony}
\begin{document}
\maketitle
\tableofcontents
\newpage
Ce document est une synthèse du cours de système d'exploitation. Il n'est pas complet et ne
peut pas se substituer à une lecture du document originale. Il permet toutefois de lister les
diffèrents points et d'y accompagner une brève explication.
\section{Events}
Un programme est composé:
\begin{itemize}
\item \textbf{Du code machine}: exécuté par la machine
\item \textbf{D'un espace de données}: Stock les données statiques et dynamiques
\item \textbf{D'une stack}: Stoque les variables locales et les appels
\end{itemize}
Ces composants se trouvent dans un "espace d'adresssage" commun et sont liés logiquements par le
biais d'appels (lw, j, ...)
\begin{figure}[h]
\centering
\includegraphics[width=0.8\textwidth]{prog_components.png}
\caption{Composents d'un programme}
\end{figure}
Lors de l'éxécution, les composents sont placés dans la mémoire afin d'y être accésible par le processeur.
L'entièretée d'un programme est alors définie par 4 éléments:
\begin{enumerate}
\item La valeur des registres
\item le contenu de la stack (et le pointeur vers son sommet)
\item L'espaces de données utilisés
\item L'IP (instruction pointer)
\end{enumerate}
Nous pouvons alors sauvegarder l'état (instantané) du programme.
\subsection{Gestion d'évènements}
Nous distingons 2 types d'évènements traités par le processeur:
\begin{enumerate}
\item erreur / exceptions: ayant une origine \textbf{interne} au processeur
ex: division par zero, op-code invalide, right-fault, ...
\item evènement / interuptions: ayant une origine \textbf{externe} au processeur
ex: clavier, fichier pret, packet, clk, ...
\end{enumerate}
Suite à ces évènements, le programme sera mis en pause pour pouvoir traiter l'évènement. Puis
le programme pourra (ou non) reprendre son cours. Dans le cas d'un évènement \textbf{récupérable},
le programme continue. Dans l'un cas d'un évènement \textbf{irécupérable}, le programme s'arrète.
Pour que le programme puisse poursuivre, il faut sauvegarder son contexte. cette "snapshot" est
placée sur la stack. (cette stack peut basculer vers une autre pendant l'évènement pour éviter
de laisser la posibilitée au programme de récupérer des informations sur l'évent sans autorisation).
Cette sauvegarde sert à la fois pour les programmes récupérable pour la reprise/poursuite mais également
pour les programmes irécupérables, ces informations sont utiles au dévelopeur pour le debug de l'app.
Il est également nécéssaire de libèrer les resources aloués.
\subsection{Reprise ou poursuite}
Lors que l'évènement à été pris en charge par le processeur, nous envisageons deux scénario.
\begin{enumerate}
\item \textbf{reprise}: retente l'éxécution du IP
\item \textbf{pouruite}: passe à l'instruction suivante
\end{enumerate}
En générale, les intéruptions constituent tous des évènements récupérables avec poursuite.
\subsection{Déroutement}
Chaques architectures implémente sa propre méthode de déroutement. voici quelques examples:
\begin{itemize}
\item \textbf{Registre de cause}: C'est un registre mis à jours lors d'une exception ou d'une
intéruption. au terme du cycle d'éxécution, le processeur consulte ce registre pour savoir
si un déroutement est nécéssaire et dans ce cas vers quel adresse.
\item \textbf{Vectorisation}: chaques évent est un numéro dans une table de descripteur(IDT). Ce
descripteur est l'adresse de la fonction de gestion de l'évènement.
\end{itemize}
\subsection{Niveau de privilège}
Le jeu d'instruction du processeur contient des instructions "système" qui ne sont pas accésibles en
principe à tout les utilisateurs. Un programme typiquement fait appel à l'OS qui à son tours fait
les appels systèmes. on parle de ring 3/0 (0 étant le plus privilégié/OS)
\subsection{Basculement de stack}
Lors d'un déroutement, L'os utilise son propre stack. (après avoir sauvegardé le contexte du
programme) ainsi, le programme ne peu pas accéder a son contenu lors de la continuation.
\subsection{Multiple évènements}
Lorsque plusieurs évènements surviennents, la prioritée est : Abandon > Reprise > Poursuite.
Il est également envisageable qu'un évènement survienne pendant la gestin d'un autre évènement. Dans
ce cas, étant donné que nous utilisons le stack pour le stockage du contexte du programme, il est
tout à fait envisageable d'utiliser ce même stack pour le stockage du contexte de la gestion du dit
évènement. Nous avons alors une imbrication des évènement à la manière de fonctions récursives.
Dans le cas ou nous authorisons plusierus évènements à dérouter en même temps, il peut être
nécéssaire d'implémenter un ordre de priorité. Pour ce faire une solution est d'utiliser un masque
d'interruption. Un évènement place alors les bits de sa prioritée. un évènement postèrieur est
déclencher ou non selon son propre masque de priorité.
\section{Memoire}
Nous avons vu qu'un programme étais composé de trois compostantes: le code machine, l'espace de
données et le stack. Ces composantes sont liées logiquement au sein du programme à l'aide d'adresse.
(branchements, ...). Deux problèmes se posent alors:
\begin{enumerate}
\item \textbf{La concurence d'adresse}: Il est possible que les adresses choisies soient déjà
utilisées par un autre programme. Il est également important de vérifier quelles mémoires
sont accédées pour éviter qu'un programme accède à des donnes ne lui appartenant pas.
\item \textbf{Gestion de la mémoire disponible}: L'OS doit savoir quelle partie de la mémoire
est libre et quelle partie est utilisé.
\end{enumerate}
Il nous faut résoudre ces problèmes de manière efficace et performante. car ces opérations sont les
plus utilisées
\subsection{Projection et protection}
\begin{itemize}
\item \textbf{Adressage absolu}: Cas de figure le plus directe. Les adresses du programmes sont
copiées précisément aux adresses de la mémoire choisies au moment de la compilation.
Le programme fonctionne alors parfaitement. Si certaine des adresses du programme ne sont
pas libres, Nous pourions comprometre l'intègritée du système. Il n'est également pas
possible de vérifier si le programme accède a des adresse qui ne sont pas autorisées.
\item \textbf{Adressage relatif}: Nous définissons un décalage (offset) au démarage du
programme. Cet offset sera appliqué à tout les adresse utilisées dans le programme.
\begin{itemize}
\item \textbf{static}: Modifie les adresse en y ajoutant l'offset (adresse de base) à la
volée lors de la mise en mémoire. Pour celà, l'éxécutable doit contenir une table
contenant les emplacement des adresse à modifier. Cette aproche n'empèche toujours
pas l'accés à des adresse hors mémoire. De plus, les adresse générées pendant le
déroulement du programme ne seront pas corigées
\item \textbf{dynamique}: L'ajout de l'offset se fait à chaques accés mémoires pendant
l'éxécution. nous stockong également la \textbf{limite} (la taille du programme) qui
permet de vérifier, lors d'un accès mémoire, si cette addresse est inférieur à
limite. si ça n'est pas le cas c'est que le programme tente de d'accèder à une
adresse ne lui appartenant pas. dans le cas contraire, l'adresse est additionée à
\textbf{base}. Un avantage de cette aproche est que le programme peut à tout moment
être déplacer dans la mémoire en métant à jour \textbf{base} et \textbf{limit}. A
contrario, chaques appel mémoire demande deux opérations (une comparaison et une
addition). ajout non négligable même si ces opérations font partie des plus rapides
du procésseur
\end{itemize}
\item \textbf{Segmentation}: Le programme est ségmenté dans la mémoire physique. chaques
ségments est encodé dans un \textbf{déscripteur de ségment} à l'aide de sa \textbf{base} et
\textbf{limit} (il est également possible de stocker le type, la permission, la direction,
la présence et un champ pour savoir si l'adresse a été modifiée ultérieurement). ces
déscripteurs sont stocké dans une \textbf{table de segments} en mémoire physique. Pour
gagner du temps cette table est stocké dans un registre de sélécteur de ségment.
Ces ségments sont crées à la compilation et la table de segments est liée au programme.
Un avantage de la ségmentation est \textbf{le partage de segment}. En effet, il est possible
pour plusieurs programmes de partager un segment de mémoire physique commun (ex: librairies).
\item \textbf{Pagination}: Meilleure solution pour la souplesse dans la gestion de mémoire. Nous
considérons deux espaces. La pagination repose sur un découpage uniforme de ces deux espaces (e.g. 4Kib, 4Mib).
\begin{itemize}
\item \textbf{espace physique} : dont la taille dépend de l'architecture du processeur
(16/32/64 bits). Cet espace sera découpé en \textbf{pages}
\item \textbf{espace virtuel} : dont la taille dépend du controlleur et de la quantitée
de mémoire disponible. Cet espace sera découpé en \textbf{cadre}
\end{itemize}
Par conséquent, une page peut être contenue dans un cadre. Pour localiser l'endroit où une
page réside en mémoire physique, nous utilisons une \textbf{table des pages} (propre à
chaques programmes) \textbf{exhaustive} (contient une entrée pour chaques pages) et est
stockée dans la mémoire physique. L'MMU (\textit{Memory Management Unit}) s'occupe de la
traduction d'une page vers un cadre. La taille de la table des pages peut être trouvé par
\[\text{Taille} = \frac{\text{Espace virtuel}}{\text{Taille de page}} \times \text{Taille d'une
entrée}\]
Chaques entrées dans la table des pages contient un numéro de cadre qui identifie l'adresse
du cadre occupé par la page en question.(Nous y accompagnons un bit de présence, permission,
référencée, modifiée).
Un accés mémoire par le procésseur sépare l'adresse virtuelle en deux parties, suivant la
taille de découpage. Nous avons donc une portion qui encode le numéro de page (utilisé en
indice dans la table des pages) ainsi que l'offset qui sera apliqué une fois l'adresse
physique trouvée. L'utilisation du bit de présence est expliqué ci-dessous, il permet en
effet l'éxécution d'un programme quelque soit la quantité de mémoire physique disponible
pour le stocker.
\end{itemize}
Les méthodes utilisant le \textbf{bit de présence} permetent l'éxistance partielle d'un programme en mémoire.
Dans le cas d'un appel vers une mémoire non présente, le processeur déclanche une \textbf{excéption}
de type défaut de page qui va recharger la partie concernée en mémoire physique. Si au moins un
cadre est disponible, il peut être pris. Si ça n'est pas le cas, un remplacmeent doit être
effectué \ref{sec:Eviction} Eviction de page
Il devra alors modifier les entrées base et limites en conséquence ainsi que mettre le bit de
présence à 1.
\label{sec:Eviction}
L'éviction d'une entrée dans la table des pages prends en compte le bit \textbf{modifié}. Celà
permet d'éviter de perdre du temps d'écriture car si la page n'est pas modifiée, elle est encore
accècible. IL existe alors plusieurs stratégies d'évicitions:
\begin{itemize}
\item \textbf{Least Recently Used}: Eviction de la page la moins utilisée. nécéssite de
connaitre le temps depuis le dernier accès
\item \textbf{Not Recently Used} permet une simplification du LRU, un simple bit est tenu à
jours à la place du temps.
\item \textbf{First In First Out}: la page la plus ancienne est choisie. une structure de type
\textit{FIFO} peut être utilisée. Il est également possible d'implémenter la stratégie de la
\textbf{dernière change}. C'est à dire, d'implémenter l'utilisation du bit de référence
(régulièrement mis à jours par l'os) pour garder les pages régulièrement chargées. Il est
finalement possible d'implémenter une structure circulaire.
\end{itemize}
la \textbf{Préemption} est le fait de libèrer de la mémoire d'un programme qui ne s'éxécute pas à
l'instant pour la donner à un autre programme en cours.
\subsection{Gestion de la mémoire physique}
Le système d'exploitation doit pouvoir, à tout moment, connaitre l'état d'occupation de la mémoire
physique. L'allocation de mémoire est alors plus dynamique.
\begin{itemize}
\item \textbf{Bitmap}: tableau de bits, la mémoire physique est découpée en unités d'allocation
de taille constante. on place les bits respectifs selon si l'espace est aloué ou non.
\[\text{Nombres d'unités} = \frac{\text{Mémoire physique}}{\text{Taille d'une unité}}\]
\[\text{Taille du bitmap} = \frac{\text{Nombres d'unités}}{8}\]
Nous pouvons alors trouver le bit corespondant à une unitée en divisant l'adresse par la
taille d'une unitée. Il suffit alors à l'os de trouver des suites de bits consécutifs libre
pour alouer de la mémoire en conséquence.
\item \textbf{Liste chainée}: chaques noed est une plage d'adresse particulière. un champ
contient:
\begin{itemize}
\item état: libre ou aloué
\item base
\item longeur
\item suivant
\end{itemize}
Nous avons finalement une chaine d'état alternant entre libre et aloué. lorsqu'un programme
souhaite alouer une plage, il parcours la liste afin de trouver une plage libre dont la
taille est supérieur à celle demandée. et de crée une maille de la chaine avec l'état
aloué. lorsque nous avons deux états consécutifs, nous pouvons les fusioner en gardant
l'adresse du premier mayon et en sommant les longeurs.
\end{itemize}
\section{Processus}
Un processus représente la mise en activité d'un programme qui réside en mémoire. (complétement ou
partiellement). Ce processus va occuper un processeur en utilisant le context de sa stack lorsque
son code sera en cours d'éxécution. Ces processus auront la capacitée de faire des appels systems
(syscall) qui seront transmis à l'OS pour exécution. Il est donc nécéssaire d'alterner entre
l'éxécution du processus et celui de l'os (notament pour les évènements/syscall).
\begin{figure}[h]
\center
\includegraphics[width=0.8\textwidth]{process_exe.png}
\caption{Temps d'éxécution d'un processus}
\end{figure}
Le temps d'éxécution du programme augmente donc. Il est même souvent nécéssaire de partager le
processeur avec d'autre programmes en cours d'éxécution. (\textit{interleaving})
Tout les processus sont stockés dans la \textbf{table des processus} attaché à leurs état. Cet état
peut être: \textbf{éxécution, pret, bloqué, zombie} et chaques processus contient un context
nécéssaire à son fonctionnement. Il est également nécéssaire de sauvegarder l'état de la mémoire.
(avec sa table des segments ou la table des pages). Finalement nous devons garder les divers
descripteurs de resources (fichiers ouvers, ... et leurs status, décalage dans le fichiers, ...)
L'\textbf{état} est un élément éssentiel pour le processus. Celui-ci est constament dans l'un des
états décrits ci-dessous. Celà permet de savoir comment le processus est censé se comporter à un
instant t.
\begin{itemize}
\item \textbf{éxécution}: Le processus suis son éxécution courante. peut être utilisateur, où
les instructions du programme s'enchainent et passe en éxécution système lors d'un
évènement. Une fois cet évènement traité, le système redonne la main à l'utilisateur qui
peut continuer l'éxécution du programme. Il est également possible que l'évènement soit
irrécupérable. dans ce cas, le programme se termine est passe à l'état \textbf{zombie}
\item \textbf{prêt}: le processus est pret à etre éxécuté mais le processeur est actuellement
occupé. une fois celui-ci libéré, il choisit (en fonction de méthodes discutés plus tard) un
processus à l'état prêt et commence son éxécution.
\item \textbf{bloqué}: Le processus ne peux pas continuer car il n'a pas les resources
nécéssaire. Il peut par example attendre un signal réseau, une touche clavier, ... une fois
cet élément reçu, le processus passe à l'état \textbf{prèt}. Le tout se fait pendant
l'éxécution d'un évènement. ce qui implique que le procéssus en cours donne la main au
système.
\item \textbf{Zombie}: Un processus à l'état zombie ne peux plus rien faire. Il donne son état à
son processus parent qui peut, une fois cet état reçu, terminer le processus.
\end{itemize}
Un procéssus peut créer à son tours d'autre processus (parents/enfants) Le système fonctionne alors
sous forme d'arborescence de processus, tous défini par leurs entrée dans la table des processus.
\begin{figure}[h]
\centering
\includegraphics[width=0.8\textwidth]{process_diag.png}
\caption{cycles de vie des processus}
\end{figure}
Suite à cet agencement, il est nécéssaire de trouver des méthode d'ordonnancement. Ces méthodes
doivent s'assurer la bonne répartition du temps processeur qui sont pret. Nous en étudierons sous 3
type des systèmes différents.
Un point important à éviter est la \textbf{famine}. c'est à dire qu'une tache resterait à pret sans
jammais se voir éxécuter.
\subsection{systèmes batch}
éxécute des taches de manière régulière. chaques tache est simples mais effectuée sur une quantitée
importante de données. Les taches sont garantie de terminer et il est possible d'estimer le temps
d'une tache donnée.
Il faut alors une équité dans les resources distribuées.
Nous regardons deux unitées. le \textit{Throughput}: nombre de taches par unité de temps, à
maximiser; le \textit{Turnaround}: temps moyen entre création et terminaison, à minimiser.
\begin{itemize}
\item \textbf{First come first served (FCFS)}: Par ordre d'arrivé à l'état pret. Peut
s'implémenter à l'aide d'une file. cette méthode est équitable et évite la famine car toutes
les taches finirons par s'éxécuter. Mais ne garantis pas une utilisation optimale des
\item \textbf{Shortest job first (SJF)}: Nous pouvons éstimer la durée des tache (car batch)
donc nous pouvons priorisé les taches courtes. Cette stratégie minimise le turnaround mais
n'est pas équitable vis à vis des tache longues. nous avons un risque de famines.
\item \textbf{Shortest Remaining Task First (SRTF)}: Nous gardons le temps restant d'éxécution
de chaques processus. l'ordre d'éxécution suis alors cette liste.
\end{itemize}
\subsection{Systèmes intéractifs}
Centré autours de l'utilisateur et l'intéraction. Nous voulons une équité entre les utilisateurs et
les resources utilisées. Nous utilisons les métriques suivantes: \textit{latence}: le temps qui
s'écoule entre une action de l'utilisateur et la réaction du système, \textit{Proportionalité}
en adéquation avec les attentes de l'utilisateur.
\begin{itemize}
\item \textbf{Round-Robin}: Le temps CPU est divisé en \textit{quantum} de temps de durée fixe.
Ces quantums sont distribués aux processus prets. Nous pouvons l'implémenter à l'aide d'une
liste cylindrique. l'Os configure un timer pour générer une intérruption à la fin du quantum
de temps pour passer à l'éxécution suivante. Cette stratégie garantit une répartition
équitable du CPU entre les procéssus. Il ne faut pas que le quantum soit trop bas car au
final il y aurais plus de temps nécéssaire à la commutation qu'à l'éxécution des processus.
\item \textbf{Priority Schéduling}: Mise en place d'un niveau de priorité par processus.
\item \textbf{Guaranteed Scheduling}: A pour but une répartition du nombre de processs face à un
délai garanti.
\[\text{Délai garanti} = \frac{\text{Temps depuis sa création}}{\text{Nombre de processus}}\]
L'ordonnanceur réalise un suivi du temps d'éxécution par chaque processus.
\[\rho = \frac{\text{Temps reçu}}{\text{Délai garanti}}\]
L'ordonnancement se base alors sur la valeur minimale.
\item \textbf{Fair-share scheduling} : Deux niveau de round robin, un pour les utilisateurs et
un par processus d'un utilisateur. Celà permet une répartition équitable du temps CPU par
utilisateur.
\end{itemize}
\subsection{Systèmes temps réel}
Système nécéssitant un controle du temps (réel) critique. Il se doit de réagir dans un temps
déterminé. (example système de défense anti-missile scannant continuellement son périmètre.)
Dans ces système. chaques évènements est précisément calibré pour être traités dans un temps
impartis avec échéances strictes. Ce système est ordonnancable uniquement si
\[\sum_{i=1}^m \frac{C_i}{P_i} \leq 1\]
avec $P$ intervalle entre deux occurence d'évènements et $C$ Temps nécéssaire au traitement.
\vspace{1cm}Un thread (processus léger) permet un découpage des traitement qui, au sein d'un processus, peuvent
être effectué de mainère indépendante les uns des autres (\textbf{multi-threading} )
Le coup de la gestion d'un thread est moindre que celui d'un processus. car celui-ci prends toutes
les resources déjà alouées de son thread principal. Il suffit juste d'effectuer une allocation de
mémoire pour héberger le stack du thread engendré. La continuation est nest rendu également plus
simple.
L'implémentation d'un thread peut se faire à deux niveaux.
\begin{itemize}
\item \textbf{User-level}: l'os n'a pas conscience des threads, c'est à l'application de gérer
son ordonencement. Mais n'est pas très évicace faces aux évènements car un blocage
(ouverture de fichiers, ...) bloquera tout les autres threads
\item \textbf{Kernel-level}: la stratègie d'ordonencement apliqués aux processus est appliqué
aux threads. Le blocage n'a donc pas de conséquence sur les autres threads du même
processus.
\end{itemize}
\section{Concurence}
\section{Systèmes de fichiers}
\section{Entrées-sorties}
\end{document}