diff --git a/content/systemes_exploitation/1-introduction/index.md b/content/systemes_exploitation/1-introduction/index.md index d4a473e..f630bd6 100644 --- a/content/systemes_exploitation/1-introduction/index.md +++ b/content/systemes_exploitation/1-introduction/index.md @@ -8,10 +8,9 @@ categories: ["Systèmes d'exploitation", "Cours"] ## Qu'est ce que c'est? D'après -[Wikipedia](https://fr.wikipedia.org/wiki/Syst%C3%A8me_d%27exploitation) " - -> un système d'exploitation est est un ensemble de programmes qui dirige -> l'utilisation des ressources d'un ordinateur par des logiciels applicatifs. +[Wikipedia](https://fr.wikipedia.org/wiki/Syst%C3%A8me_d%27exploitation), "un +système d'exploitation est est un ensemble de programmes qui dirige +l'utilisation des ressources d'un ordinateur par des logiciels applicatifs." Il permet l'abstraction matérielle *(via les pilotes de périphériques)*, la gestion des processus *(et leur séparation, permettant à un processus défectueux @@ -27,17 +26,17 @@ le premier étant une partie du second. ## Les interruptions -D'après [Wikipedia](https://fr.wikipedia.org/wiki/Interruption_(informatique)) : +D'après [Wikipedia](https://fr.wikipedia.org/wiki/Interruption_(informatique)), +"une interruption est une suspension temporaire de l'exécution d'un programme +informatique par le microprocesseur afin d'exécuter un programme prioritaire +(appelé service d'interruption)." -> une interruption est une suspension temporaire de l'exécution d'un programme -> informatique par le microprocesseur afin d'exécuter un programme prioritaire -> (appelé service d'interruption)." Les interruptions peuvent être envoyées par les périphériques ou le CPU -lui-même. Lors de son envoi, aucun message n'est envoyé, seulement son numéro, -le CPU fait alors un saut vers une routine définie par **la table -d'interruption**. Cette table est mise en place par le *noyau* en RAM. Cette -table contient une entrée par interruption. +lui-même. Lors de l'envoi d'une interruption, aucun message n'est envoyé, +seulement son numéro, le CPU fait alors un saut vers une routine définie par +**la table d'interruption**. Cette table est mise en place par le *noyau* en +RAM. Cette table contient une entrée par interruption. Il existe deux grand type d'interruption: @@ -56,30 +55,28 @@ ne peuvent pas être ignorées)*. ### Le cas du timer C'est une interruption déclenchée à intervalle régulier, en général toutes les -10ms. Ainsi un processus ne peut pas monopoliser indéfiniment le CPU. Le noyau -peut stopper, voire tuer un processus trop gourmand lors de son passage sur le -CPU. +10ms. Ainsi un processus ne peut pas monopoliser indéfiniment le CPU. Ainsi le +noyau peut stopper, voire tuer un processus trop gourmand. ## Les privilèges -Nous avons besoin de contrôler ce que font les processus, seul le noyau doit -être tout-puissant. Le contrôle des instructions autorisées (ou non) ne peut se +Nous avons besoin de contrôler ce que font les processus, seult le noyau doit +être tout-puissant. Le contrôle des instructions autorisée ou non ne peut se faire **qu'au niveau du matériel** et donc du CPU. Ainsi ce dernier disponse de deux modes de fonctionnement: - * **protégé**: seul un nombre restreint d'instructions sont disponibles + * **protégé**: seul un nombre restreint d'instruction sont disponibles * **réel** (ou noyau) toutes les instructions sont disponibles. Si une instruction privilégiée est exécutées par un processus, une exception (sorte d'interruption) est lancée. Pour des questions de sécurité, un processus ne doit pas pouvoir exécuter des -instructions privilegiées. En effet le rôle du noyau est aussi de faire +instructions privilegiées. En effet le rôle du noyau ets aussi de faire abstraction du matériel, lui seul y a accès. Cependant certains processus ont besoin d'y accéder **un affichage** avec `printf` ou la **saisie d'un texte au -clavier** ou encore **créer un processus**, et utiliser des instructions du mode -réel. +clavier** ou encore **créer un processus**. ### Les appels systèmes @@ -100,6 +97,6 @@ numéro. Les paramètres necessaires aux appels sont eux stockés sur la pile. Ils sont nombreux : on en compte environ 330 dans Linux et plus de 500 dans MacOSX. -Pour Linux, certaines routines sont inclues dans la `libc` et ne sont donc pas +Sour Linux, certaines routines sont inclues dans la `libc` et ne sont donc pas des appels système. Par exemple `printf` qui affiche des éléments à l'écran et inclu dans la `libc` utilise l'appel système `write`. diff --git a/content/systemes_exploitation/2-processus/index.md b/content/systemes_exploitation/2-processus/index.md index aa8031a..9b56f6e 100644 --- a/content/systemes_exploitation/2-processus/index.md +++ b/content/systemes_exploitation/2-processus/index.md @@ -8,15 +8,15 @@ categories: ["Systèmes d'exploitation", "Cours"] Les processus sont des instances vivants de programmes. Un programme représente du code binaire stocké sur un support de stockage. -Un processus est composé d'un espace d'adressage en mémoire et d'un contexte -d'exécution. Plus d'information est disponible [dans les cours de prog. -système]({{}} "Les processus") +Un processus est un espace d'adressage en mémoire et d'un contexte d'exécution. +Plus d'information est disponible [dans les cours de prog. système]({{< ref +"../../progsys/3-processus/index.md">}} "Les processus") ## Accès à la mémoire L'espace d'adressage contient des segments mémoire : - * le *segment de texte* / de code : les instructions optimisées par le + * le *segment de texte* / de code: les instructions optimisées par le compilateur, souvent en lecture seule dans les systèmes modernes. * le *segment data* contenant lui même le segment des *données initialisées* et le BSS (données non-initialisées) @@ -26,7 +26,8 @@ L'espace d'adressage contient des segments mémoire : la `libc` effectue un appel système (mais le noyau peut refuser d'allouer) * la *pile d'exécution*, sa taille est de 8MiB maximum sous Linux. Ce segment contient les paramètres des fonctions et leurs variables locales. - * les *librairies partagées* mappées à la demande. + * les *librairies partagées* mappees à la demande. + Il est possible de voir les espaces de mémoire alloués pour un processus donné : @@ -64,11 +65,11 @@ On y voit biens les adresses de début, ceux de fin, les droits (`r`ead, L'accès par un processus à un espace mémoire invalide donne lieu à la fameuse `segmentation fault`. Mais il est tout à fait possible de lire et écrire vers une zone non allouée du tas. Par exemple j'initialise un tableau de 10 éléments -et le rempli avec une boucle de 15 itérations. +et les rmpli avec une boule de 15 itérations. ## Attributs d'un processus -En plus de l'espace mémoire alloué pour le processus, le noyau stocke en mémoire +en plus de l'espace mémoire alloué pour le processus, le noyau stocke en mémoire un ensemble d'attributs : son identitiants (`PID`), sa priorité, l'`UID` (réel/effectif), la table des descripteurs de fichiers, la table des signaux, un espace pour sauvegarder les registres (changement de contexte, reprise sur @@ -108,7 +109,7 @@ détailler certaines. Une liste chainée de processus, on exécute le premier jusquà la fin de son exécution ou qu'il soit bloqué puis le second et ainsi de suite. -C'est une technique facile à implémenter, elle est très peu couteuse en temps +C'est une technique facile à implémenter, il est très peu couteux en temps processeur (le noyau intervient peu, peu de changement de contexte) mais comporte un gand risque de **famine** : un processus en boucle ne rendrai jamais la main. @@ -133,7 +134,7 @@ comportement. Dans le cas d'une **opération de compilation** par exemple, le compilateur lit les fichiers sources effectuant beaucoup de `read` et se -bloque régulièrement donc. Ensuite il compile et utilise beaucoup de CPU. +bloauqnt donc. Ensuite il compile et utilise beaucoup de CPU. L'ordonnanceur observe donc les métriques du passé pour prévoir l'avenir. Dans l'example du compilateur, le noyau observe que sur les 10ms de temporisation, @@ -191,7 +192,8 @@ plusieurs threads peut conduire à des fonctionnements arbitraires. Il est à noter que les accès à la mémoire sont de toute façon atomique: *une opération de lecture ou écriture à la fois*. Mais ce n'est pas suffisant, un -example de code est disponible [dans les cours de prog. système]({{}} "Les processus légers") +example de code est disponible [dans les cours de prog. système]({{< ref +"../../progsys/5_les-processus_legers/index.md">}} "Les processus légers") + Le noyau doit donc mettre en place des primitive de synchronisation. diff --git a/content/systemes_exploitation/3-synchronisation/index.md b/content/systemes_exploitation/3-synchronisation/index.md index 86e6b9a..e999f31 100644 --- a/content/systemes_exploitation/3-synchronisation/index.md +++ b/content/systemes_exploitation/3-synchronisation/index.md @@ -42,17 +42,17 @@ void exit_sc () ``` Nous avons donc besoin d'une solution non seulement valable pour un nombre -indéfini de fils d'exécutions, mais aussi plus efficace. +indéfinis de fils d'exécutions, mais aussi plus efficace. ## Une solution matérielle Vu du processeur, une opération atomique ne peut être interompue par une autre. -Les processeurs modernes en comportent un certain nombre. La solution vient +Les processeurs modernes en comportent un certain nombres. La solution viens donc des fabricant de processeurs. Pour forcer l'exclusion mutuelle, nous en avons besoin d'une seule: `Test_and_Set`. C'est donc une instruction matérielle, voici une pseudo -implémentation en C : +implémentation en C: ```c int test_and_set (int *verrou){ @@ -104,13 +104,12 @@ threads en attente. Car seul le noyau peut endormir les processus. ## Les sémaphores -On en a déjà parlé [en lpro]({{}} "Les IPC"). Ce sont +On en a déjà parlé [en lpro]({{< ref"../../progsys/8_IPC">}} "Les IPC"). Ce sont des outils de haut-niveau, implémentés dans le noyau. Il se compose d'une -structures contenant un entier positif, d'une liste de processus en attente et -de deux méthodes `P()` et `V()`. Il ont été inventés en 1962 par E. Dijsktra. +structures contenant un entier positif et une liste de processus en attente et +de deux méthodes `P()` et `V()`. Il ont été inventés en 1962 par E. Dijsktra. Les méthodes permettent : - * `P()`: attendre un jeton, le prendre lorqu'il devient disponible et continuer son exécution. * `V()`: remettre un jeton @@ -147,7 +146,7 @@ void barrier (int i) count[i] = 0; V(mutex[i]); - // point 2 + // point for (int k=0; k < N-1; k++) V(wait[i]); } @@ -155,7 +154,6 @@ void barrier (int i) ``` Explication de code : - * **point 1**: Utiliser des tableaux de 2 éléments permet de réutiliser notre fonction `barrier()` pour fixer plusieurs rendez-vous. Il suffit alors d'apeller à tous de rôle `barrier(0);` et `barrier(1);`. Sans ça un @@ -180,24 +178,24 @@ Un producteur appelle la fonction `put(element)` et un consommateurs ```c #define MAX 8 -semaphore conso(0), prod(MAX) +semaphore cons(0), prod(MAX) // Pour le consommateur -P(conso); +P(cons); elemet = get(); V(prod); // Pour le producteur P(prod); put(element); -V(conso); +V(cons); ``` Le fonctionnement est ici assez simple : * **Pour le consomateur**: - 1. il prend un jeton sur le sémaphore `conso` s'il est disponible (ou - attend qu'il le soit) + 1. il prend un jeton sur le sémaphore `cons` s'il est disponible (ou attends + qu'il le soit) 2. consomme l'élement 3. on relache celui sur `prod`. Cette dernière action permet de relancer la production si la file était pleine (`prod` en attente). @@ -206,8 +204,8 @@ Le fonctionnement est ici assez simple : action permet d'arrêter la production s'il n'y a plus de jeton disponible. 2. On produit ensuite l'élément - 3. puis on relache le sémaphore `conso`. On réveille ansi notre - consommateur s'il dormait en attendant la disponibilité d'un élement + 3. puis on relache le sémaphore `cons`. On réveille ansi notre consommateur + s'il dormait en attendant la disponibilité d'un élement #### Multiple producteurs et consomateur @@ -215,15 +213,15 @@ Dans la vraie vie, il y souvent plusieurs consomateurs / producteurs. Le problème devient alors plus complexe... Le code ressemble à celui ci-dessus, saut qu'il faut faire attention à ce qu'il -y ai un seul consomateur à la fois sur `get()` et un seul producteur sur +y ai un seul consmateir à la fois sur `get()` et un seul producteur sur `put(element)`. nous allons donc rajouter deux *"mutex"* : ```c {linenos=table,hl_lines=[6,8,13,15]} #define MAX 8 -semaphore conso(0), prod(MAX), mutex_c(1), mutex_p(1); +semaphore cons(0), prod(MAX), mutex_c(1), mutex_p(1); -// Pour le consommateur -P(conso) +// Pour le consomateir +P(cons) P(mutex_v); element = get(); V(mutex_v); @@ -234,7 +232,7 @@ P(prod); P(mutex_c); put(element); V(mutex_p); -V(conso) +V(cons) ``` ### Problème des lecteurs rédacteurs @@ -275,18 +273,16 @@ if(--nbr == 0) // last to leave V(write_token); V(mutex_r); ``` - -Pour les lecteurs, il faut envoyer **un éclaireur** afin de savoir si un lecteur +Pour les lecteur, il faut envoyer **un éclaireur** afin de savoir si un lecteur est actif. Le problème est similaire à celui des producteur consommateurs sauf qu'il faut savoir si notre lecteur est le premier *(ligne 12)* -De plus, afin que notre algorithme ne privilegie pas les lecteurs au -détriment des rédacteur; ils pourraient même ne jamais rendre la main aux -rédacteurs, nous devons mettre en place une **salle d'attente** *(ligne 8)*. +De plus. afin que notre algorithme ne privilegie pas les lecteurs au +détriment des rédacteur; ils peuvent même ne jamais rendre la main aux +rédacteurs. Pour pallier au problème, nous devons mettre en place une **salle +d'attente** *(ligne 8)*. -Voici le code pour les rédacteurs: - -```c {linenos=table} +```c {linenos=table,hl_lines=[8,12]} P(wait_room); P(write_token); V(wait_room); @@ -300,7 +296,7 @@ Ce code est plutôt clair et ne comporte rien de particulier. Les moniteurs sont des primitives de synchronisation initialement proposées dans les langages objet. Il est utilisé actuellemt dans des langages tel que ADA ou -Java et implementé au sein de systèmes d'exploitation. +Java et implemente au sein de systèmes d'exploitation. Le moniteur se positionne sur une classe et les mutexes sur ses methodes et sont basés sur des variables condition. elle sont forcement privée et inaccessible à @@ -321,7 +317,7 @@ public method g() { } ``` -Il sont différents des *sémaphores*, il n'y a pas de jeton à prendre. Leur +Il sont différent des *sémaphores*: il n'y a pas de jeton à prendre. Leur implémentation dans les systèmes d'exploitation est différente, elle se fait par les les types `mutex_t` et `cond_t`: @@ -404,10 +400,9 @@ tout comme les barrières, le code est ici bien plus simple. Et bien entendu il n'y a pas d'attente active... Dans les codes ci-dessous, il est préféfable d'utiliser `while (nbe ...)` plutôt -qu'un `if (nbe ...)`. Si le processus se reveille, il faut s'arrurer que les -autres n'on pas déjà pris toutes les places disponibles. +qu'un `if (nbe ...)`. (Mais je ne sais plus pourquoi...) -Voici les variables nécessaires aux producteurs et au consommateurs. +Voici les variables nécessaires aux producteurs et au consomate ```c #define MAX 8 @@ -543,7 +538,7 @@ rwl_writeunlock(mylock); ## conclusion -Les sémaphores et moniteurs sont implémentés au niveau du système d'exploitation +Les sémaphores et moniyeurs sont implémentés au niveau du système d'exploitation et utilient le matériel sous-jacent (`test_and_set`). Ces appels sont **coûteux**, nécessitent des **changements de contexte**. Dans les systèmes modernes, on leur préfèrera un **mix de primitive de synchronisation et de @@ -553,11 +548,11 @@ pendant quelques cycles et se bloquera si elle n'est pas disponible. Les moniteurs de Hoare sont en général préférés par les programmeurs cas comme on l'a vu, ils sont plus simple à implémenter. -Il est intéressant aussi de parler des **FUTEX**, apparus en 2003 sur Linux et -plus tard sous Windows 8 (sous l'appelation `wait_on_address` et breveté en +Il eest intéressant aussi de parler des **FUTEX**, apparus en 2003 sur Linux et +plus tard sous Windows 8 (sous l'appelation `wait_on_address` et brevete en 2013). -Ils utilisent des opérations atomiques sur des variables entières de 32bits *en +Ils utilisent des opétations atomiques sur des variables entières de 32bits *en espace utilisateur* et deux operation bloquante si nécessaires: ```c diff --git a/content/systemes_exploitation/4-Memoire/images/base_limit.svg b/content/systemes_exploitation/4-Memoire/images/base_limit.svg deleted file mode 100644 index bc5a89c..0000000 --- a/content/systemes_exploitation/4-Memoire/images/base_limit.svg +++ /dev/null @@ -1,767 +0,0 @@ - - - - - Registres base + limit - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0x0f2 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Registres base + limit - 2021.11.26 - - - Yorick Barbanneau - - - Utilisation des registres base et limit dans la gestion de la mémoire - - - - - - - - - - - - diff --git a/content/systemes_exploitation/4-Memoire/images/base_limit_right.svg b/content/systemes_exploitation/4-Memoire/images/base_limit_right.svg deleted file mode 100644 index a9f268c..0000000 --- a/content/systemes_exploitation/4-Memoire/images/base_limit_right.svg +++ /dev/null @@ -1,1169 +0,0 @@ - - - - - Registres base + limit + mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Registres base + limit + mode - - - - - - - - - - - - - diff --git a/content/systemes_exploitation/4-Memoire/images/base_limit_segments.svg b/content/systemes_exploitation/4-Memoire/images/base_limit_segments.svg deleted file mode 100644 index 398690e..0000000 --- a/content/systemes_exploitation/4-Memoire/images/base_limit_segments.svg +++ /dev/null @@ -1,1011 +0,0 @@ - - - - - Registres base + limit - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Registres base + limit - 2021.11.26 - - - Yorick Barbanneau - - - Registres base et limit pour chaque segments mémoires - - - - - - - - - - - - diff --git a/content/systemes_exploitation/4-Memoire/images/bits_adresse_memoire.svg b/content/systemes_exploitation/4-Memoire/images/bits_adresse_memoire.svg deleted file mode 100644 index 77f30d4..0000000 --- a/content/systemes_exploitation/4-Memoire/images/bits_adresse_memoire.svg +++ /dev/null @@ -1,82 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/content/systemes_exploitation/4-Memoire/images/memoire_paginee_correspondance.svg b/content/systemes_exploitation/4-Memoire/images/memoire_paginee_correspondance.svg deleted file mode 100644 index d438feb..0000000 --- a/content/systemes_exploitation/4-Memoire/images/memoire_paginee_correspondance.svg +++ /dev/null @@ -1,470 +0,0 @@ - - - - - Memoire paginée, correspondance mémoire virtuelle et réelle - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Memoire paginée, correspondance mémoire virtuelle et réelle - - - Yorick Barbanneau - - - - - - diff --git a/content/systemes_exploitation/4-Memoire/images/mmu.svg b/content/systemes_exploitation/4-Memoire/images/mmu.svg deleted file mode 100644 index ce038ef..0000000 --- a/content/systemes_exploitation/4-Memoire/images/mmu.svg +++ /dev/null @@ -1,1374 +0,0 @@ - - - - - - Registres base + limit + mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Registres base + limit + mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - page - 20 bits - décalage - 12bits - &table - - - - - - - - - - - - - - - - - - - - - - - - - page - 20 bits - décalage - 12bits - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - RAM - MMU - CPU - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - r - w - x - - - - - - - - - - - write: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - &soustable - - - diff --git a/content/systemes_exploitation/4-Memoire/images/mmu_right_exception.svg b/content/systemes_exploitation/4-Memoire/images/mmu_right_exception.svg deleted file mode 100644 index c29f4fb..0000000 --- a/content/systemes_exploitation/4-Memoire/images/mmu_right_exception.svg +++ /dev/null @@ -1,1195 +0,0 @@ - - - - - MMU avec mode et exception - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - MMU avec mode et exception - - - Yorick Barbanneau - - - - - - - - - - - - - - diff --git a/content/systemes_exploitation/4-Memoire/images/mmu_simple.svg b/content/systemes_exploitation/4-Memoire/images/mmu_simple.svg deleted file mode 100644 index 151771f..0000000 --- a/content/systemes_exploitation/4-Memoire/images/mmu_simple.svg +++ /dev/null @@ -1,1119 +0,0 @@ - - - - - - Registres base + limit + mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Registres base + limit + mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/content/systemes_exploitation/4-Memoire/images/mmu_table_2.svg b/content/systemes_exploitation/4-Memoire/images/mmu_table_2.svg deleted file mode 100644 index 2d4e3d2..0000000 --- a/content/systemes_exploitation/4-Memoire/images/mmu_table_2.svg +++ /dev/null @@ -1,1712 +0,0 @@ - - - - - MMU deux niveaux de table de pagination - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - MMU deux niveaux de table de pagination - - - Yorick Barbanneau - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/content/systemes_exploitation/4-Memoire/images/page_noyau.svg b/content/systemes_exploitation/4-Memoire/images/page_noyau.svg deleted file mode 100644 index 64ef05e..0000000 --- a/content/systemes_exploitation/4-Memoire/images/page_noyau.svg +++ /dev/null @@ -1,938 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/content/systemes_exploitation/4-Memoire/images/page_noyau_meltdown.svg b/content/systemes_exploitation/4-Memoire/images/page_noyau_meltdown.svg deleted file mode 100644 index 816c2aa..0000000 --- a/content/systemes_exploitation/4-Memoire/images/page_noyau_meltdown.svg +++ /dev/null @@ -1,1193 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - mode utilisateur - mode noyau - - - - - - - - - - - - - - - - - - - - - Yorick Barbanneau / ephase - - - - - - CC BY-SA - - - Français - - - - - - - - - - - - diff --git a/content/systemes_exploitation/4-Memoire/index.md b/content/systemes_exploitation/4-Memoire/index.md deleted file mode 100644 index d3aef9f..0000000 --- a/content/systemes_exploitation/4-Memoire/index.md +++ /dev/null @@ -1,470 +0,0 @@ ---- -title: "Systèmes d'exploitation : Gestion de la mémoire" -date: 2021-09-24 -tags: ["système", "mémoire", "pagination", "thread"] -categories: ["Systèmes d'exploitation", "Cours"] ---- - -Au départ étaient les systèmes mono-tâche et mono-utilisateur. Les programmes -accédaient à toute la mémoire disponible. Il n'y avait pas besoin de mécanisme -spécifique de gestion. Mieux, **l'adresse en mémoire de notre programme était -connu à l'avance** au moment de la compilation. Simple quand il y a un seul -processus en mémoire. - -Le vénérable MS-DOS par exemple ne proposait aucune protection mémoire, **même -la table d'interruptions** était accessible (et modifiable) par les programmes. -Il était alors possible d'adresser 1Mo de mémoire dont 640Ko de RAM. - -L'accès aux routines du systèmes se faisait par les interruptions en passant -l'adresse d'une sous-routine: - - * int 08h — Timer interrupt - * int 10h — Video services - * Int 16h — Keyboard services - * int 21h — MS-DOS services - -```asm -; SC_PutChar == 0x02 la sous routine de notre -; interruption 21h (MS-DOS services) - -mov ah, 02h ; on passe la sous-routine -mov dl, ‘A’ -int 21h -mov ah, 4Ch ; SC_Exit == 0x4C -mov al, 0 ; EXIT_SUCCESS -int 21h -``` - -## Les systèmes multi-tâches - -Les opérations disques, à l'origine sur des bandes magnétiques, prenaient du -temps. Afin de rentabiliser l'utilisation des processeurs qui passaient la -plupart de leur temps à "glander", sont apparus les systèmes multi-tâches. Le -principe est simple : permettre à **plusieurs processus de cohabiter en -mémoire**. Plusieurs problèmes se posent alors : - - * Comment compiler un programme alors que nous ne connaissons pas son - emplacement en mémoire? - * Comment éviter les problèmes de fragmentation de la mémoire? - * Comment gérer l'expansion des processus en mémoire? - * Comment gérer la protection de la mémoire. - -## La compilation d'un programme multi tâche - -Il est du coup impossible pour le compilateur de connaitre à l'avance les -adresses mémoires utilisées par notre programme. Il faut donc trouver un -mécanisme pour gérer ce problème. - -Le compilateur fournit alors une liste d'emplacement des pointeurs. Il génère -contient alors **le code plus une table de correspondance** à l'étape de -liaison. Ces informations seront positionnée dans la section ELF `.rel.text` -(x86_32) ou `.rela.text` (x86_64) du binaire. - -Le problème est donc réglé non seulement au moment **de la compilation** mais -aussi **de son chargement** par le système. - -## Fragmentation mémoire et expansion des processus - -Il arrive forcément un moment ou le système doit copier et / ou déplacer -des elements en mémoire. Ce sont des opérations coûteuses. - -Lorsque un processus demande beaucoup de mémoire à l'aide de `malloc()`, le -système doit prendre une décision : **mettre le processus ailleurs ou en bouger -d'autres afin de faire de la place?** - -La fragmentation est un problème complexe, on l'abordera tout au long de ce -chapitre. - -## Protection de la mémoire - -Afin de s'assurer qu'un processus *P1* ne puisse pas accéder à l'espace mémoire -de *P2* nous devons mettre en place des protections. Doit-on le faire au moment -de la compilation? Mais que faire des accès indirects? - -Demander au compilateur de **lancer des vérifications lors de chaque accès -mémoire**? En plus d'être très coûteuse, cette solution est inefficace. - -Encore une fois la solution **vient des fabricants des microprocesseurs** pour -nous permettre deux choses: - - * **contrôle** des accès. - * **relocation efficace**. - -Seul deux registres sont nécessaires : *limit* `<` et *base* `+`. Ceux-ci sont -renseignés à chaque changement de contexte. Le systèmes initialise l'adresse de -base et la limite du processus en cours. - -Une interruption *SegFault* est renvoyée si le processus ne respecte pas ces -deux valeurs. - -![Exemple d'utilisation des registres *base* et *limit*](images/base_limit.svg) - -Le schema ci-dessus montre l'utilisation de ces deux registres afin de -déterminer l'adresse mémoire physique à partir de l'adresse logique fournie par -le processus. Le second accès est invalide: l'adresse demandée dépasse la -limite, une **erreur de segmentation** est alors envoyée. - -La conversion d'une *adresse logique* en *adresse physique* ne demande aucun -effort supplémentaire à l'unité centrale. L'isolation mémoire des processus est -maintenant garantie par le matériel. Mais ce mécanisme empêche tout de même le -partage de segments mémoire entre processus (et nos librairie partagées alors?). - -### Base et Limit par segment, gestion des droits - -Ceci étant dit, pourquoi garder les segments mémoire (code, data, tas, pile) -ensemble? Nous pouvons les séparer, mais pout ça il va nous falloir plusieurs -*base* et *limit*. Du coup, lors d'un accès à la mémoire nous **devons connaitre -le segment concerné**. - -![registres *base* et *limit* pour chaque segments](images/base_limit_segments.svg) - -Le compilateur préfixe l'adresse mémoire par le segment. - -Ce mode de fonctionnement permet plus de souplesses dans l'allocation de la -mémoire. Il permet aussi le partage d'espace de segments entre processus : il -suffit qu'ils aient les **même *base* et *limit***. Il peuvent même partager le -code, utile pour économiser de la RAM. - -### Droits sur les segments - -Il est du coup intéressant d'ajouter un peu plus de protection. Un registre -supplémentaire, *mode* : il permet de spécifier les droits en lecture, écriture -et execution. - -![L'ajout du registre mode](images/base_limit_right.svg) - -Ces droits sont positionnés par le CPU - -Mais tous ces mécanismes ne permettent pas de régler le problème de -fragmentation. pour ça il faut trouver autre chose. - -## La pagination mémoire - -Son principe est simple: créer des espaces mémoire de taille fixe, **une page** -(ou frame en anglais -- 4ko sur x86). Du point de vue du système, une page est -soit libre, soit occupée. Lors de l'allocation, le système arrondi la taille -demandée pour déterminer le nombres de pages à allouer. - -Il n'est pas garanti que des pages contigües soient allouées **de façon contigüe -en mémoire**. - -### Adresses virtuelles - -Lorsque le processeur execute du code en espace utilisateur, il ne voit que les -adresses memoires virtuelles. Il faut donc traduire ces adresses virtuelles en -adresse physique (RAM). - -Prenons l'exemple d'une variable `i` où `&i=8320`. Pour des pages de *4Ko* notre -variable se trouvera dans la troisième (2 x 4096) page avec un décalage de 128 -bits. On utilisera ce décalage pour trouver `i` comme dans le schéma -ci-dessous: - -![Correspondance page virtuelle / memoire](images/memoire_paginee_correspondance.svg) - -Les adresses mémoire sont stockée sur 32bits. La représentation binaire notre -adresse mémoire virtuelle correspond au schema ci-dessous: - -![Composition de l'adresse virtuelle](images/bits_adresse_memoire.svg) - - 1. Cette partie de l'adresse (20 bits) nous permet de connaitre la page - mémoire. Comme nous le verrons plus tard, cette partie servira pour la table - de pagination. - 2. Cette partie fait office de décalage, une fois la page physique trouvée on - se sert de ce dernier comme indique sur le schéma précédent. - - -### Table de pagination - -Nous avons besoin de traduire les adresses virtuelles en adresses physiques, -comment pouvons nous procéder? L'utilisation d'une **table de correspondance** -est toute indiquée. - -Une table par processus est indiquée avec une capacité de 2^20 entrées. Chaque -entrée de ces tables a une taille de 20bits, arrondi à 32 - 4 octets. Un des -bits est utilisé pour indiquer si la page est allouée ou non (champ *valid*) -comme le tableau ci-dessous: - - -page viruelle| page physique | valide | -:---: | :---: | :---: - 0 | 4 | 1 - 1 | 18| 1 - 2 | | 0 - 3 | 2 | 1 - 4 | | 0 - 5 | | 0 - 6 | 23 |1 - ... | ... | ... - -Mais nous verrons plus tard que d'autres bits seront utilisés... - -Problème cependant, **chaque table prends donc 4Mo de place**, c'est beaucoup! -Le CPU a besoin de connaitre la table des pages en cours. Pour cela il faut un -**registre spécial** mis à jour à chaque changement de contexte. La table a -juste besoin d'être référencée par un pointeur. - -## MMU - Memory Management Unit - -La MMU est un élément du microprocesseur, son rôle est de transformer les -adresses logiques en adresse physique. La MMU se compose d'un registre contenant -la page des tables (aussi appelée table de translation) et d'un circuit -permettant la conversion. - -![Fonctionnemenmt de la MMU: conversion d'adresse](./images/mmu_simple.svg) - -Voici un premier schéma de fonctionnement de la MMU. Les 20 premiers bits de -l'adresse virtuelles permettent de vérifier la table physique. lors de la -translation, la MMU vérifie au passage **la validité de la page**. - -Mais il faut tout de même ajouter un peu de sécurité à tout ça comme vu lors du -chapitre sur les registres *limit*, *base* et *mode*. - -![Fonctionnemenmt de la MMU: exception liée aux -droits](./images/mmu_right_exception.svg) - -Dans l'exemple ci-dessous, le processeur fait un accès mémoire en écriture. Lors -de la traduction de l'adresse, la page est noté en lecture et exécution: **une -exception** et lancée. - -La MMU est un circuit matériel, il n'empêche que son fonctionnement introduit -des accès mémoires supplémentaire et donc **penalise les performances**. Sachant -qu'un accès mémoire consomme une centaine de cycles! Nous avons donc deux -problème majeurs: - - 1. Les performances pénalisée - 2. L'empreinte mémoire des tables de pages - -### Réduire l'empreinte mémoire - -Dans les fait, une table des pages contient généralement une majorité de **pages -invalides**. Il serait possible de la compresser, mais on perdrait l'indexation --- et donc les avantages d'un tableau. La solution: utiliser plusieurs niveau de -tables organisées de façon hiérarchique. Les processeurs et systèmes modernes -comportent en général 4 niveaux. - -Avec cette technique, lorsqu'il n'y a que des entrées valides dans une table de -niveau n, on pose `NULL` dans l'entrée correspondante de la table n-1. Le noyau -alloue des tables de niveau n (ou n > 1) seulement lorsqu'il en a besoin. - -![Fonctionnemenmt de la MMU: deux niveau de tables](images/mmu_table_2.svg) - -On économise de la mémoire, mais au prix encore une fois de **perte de -performances**. Il faut maintenant trois accès mémoire : un premier accès pour -lire une entrée dans un répertoire de table de pages (page directory), un second -pour lire une entrée dans une table de pages (page table), et un dernier pour -accéder à la page physique. - -### TLB -- Translation Lookaside Buffer - -Pour éviter le problème, la MMU intègre un cache permettant de stocker les -dernières opérations effectuées. Celui-ci mémorise *l'adresse de la page -virtuelle*, *l'adresse de pa page physique* et *le mode*. - -Le TLB est un cache associatif rapide, de type LRU -- *Last Recently Used* -- -lorsqu'il est plein, la ligne la plus ancienne est évincée et remplacée. Il est -en général limité à 32 / 64 entrées (c'est un type de cache coûtant cher en -fabrication). - -Afin de permettre la cohabitation de plusieurs processus dans le cache, il -enregistre une autre information : le *tag* -- un pointeur vers la page de table -concerné par l'entrée. - -Les processeurs modernes comporte en général deux TLB de premier niveau : un -pour les **instructions** (iTBL) et un autre pour **les données** (dTLB). Ils -contiennent aussi plusieurs niveau : un TLB de premier niveau privée et rapide -puis un de second niveau partagé et plus lent. - -## Côté du noyau - -Nous avons vu comment la memoire est utilisée en espace utilisateur, mais -comment tout ça se passe en espace noyau? Particulièrement lors d'un appel -système? Car le noyau doit avoir accès **aux deux espaces** - -Comme on pouvait se douter, le noyau utilise aussi la pagination. Il suffit de -rajouter un bit afin de déterminer si la page appartient à l'utilisateur *(1)* -ou au noyau *(0)*. Les entrées noyau sont positionnée en bas, comme sur -l'exemple ci-dessous. - -![Pages noyau et pages utilisateur](./images/page_noyau.svg) - -Les entrées pagination du noyau sont partagées entre toutes les pages via un de -simple pointeurs depuis la page de niveau 1, ainsi tous les processus **"voient" -les pages mêmes pages du noyau**. Sur Linux 32bits, 3Go sont allouées aux -processus et 1Go au noyau. En version 64bits toute la mémoire physique est -alloué à l'espace d'adressage virtuel du noyau. - -## Meltdown - faille de sécurité matérielle - -Cette faille, largement médiatisée et documentée touche les processeurs Intel, -IBM Power et certains ARM. Elle tire parti des conditions d'execution -particulières de code dans les processeurs modernes : ils sont capable -d'exécuter les instructions dans le désordres ([out of order -execution](l_woutoforder)) et même faire de l'exécution spéculative - -L'exécution spéculative, correspondant au lancement anticipé d'instructions. -Mais celles-ci ne doivent **pas être validée en cas d'erreur de prédiction**. -Mais est-ce vraiment le cas? - -### Un petit programme de test. - -Pour bien comprendre comment fonctionne l'exécution, prenons un code d'exemple: - -```c -char array [N * 4096]; -// mais que vaut data? -int data = <...>; -char c; -*((int *)NULL) = 12; - -// Nous n'arriverons jamais jusqu'ici, nous -// aurons droit a un segfault! -c = data[data *4096]; -``` - -La première instruction va forcément produire un *segfault*, `c` ne sera pas -modifié. Mais a cause de l'execution spéculative, la seconde instruction a été -exécutée avant le lancement de l'exception. - -Donc la zone mémoire `array[data * 4096]` a été lue, et son adresse est présente -dans le cache. En mesurant les temps d'accès pour chacun des `array[ i * 4096]`, -il nous est possible de deviner la caleur de `data`. - -### Lire une donnée du noyau - -Maintenant comment utiliser tout cela pour lire une donnée du noyau? voici un -petit exemple en assembleur: - -```asm {linenos=table} -;rxc: adresse noyau -;rbx: adresse de base de notre tableau -retry: - mov al, byte rxc ;al: partue de rax (8bits) - shl rax, 0xcc ;decallage de 12bits - 4096 - jz retry ;reessayer l'execution spéculative - mov rbx, qword[rbx + rax] -``` - -La première instruction ligne 4 va générer une exeption, mais parrallèllement -l'instruction ligne 7 sera exécutée puis annulée. Top tard, les adresses -memoires seront dans le cache. - -La répetition ce des commandes pour les adresses noyau permet done de lire -**toute la memoire physique** (démarre à `0xffff 8800 0000 0000` sous Linux sans -*Kernel Address Space Layour Ransomization*). Les auteurs de la découverte ont -réussi à lire la memoire à une vitesse de 503 Ko/s. - -### Et comment l'éviter? - -Il n'est bien entendu **pas question de désactiver l'exécution spéculative**, -les fabrivcant de puces s'y refusent. Et pour cause, l'impact sur les -performances est trop importante. - -Mais si on y réfléchit, Meltdown pose problème parceque **les pages du noyau -sont projetées dans la table des processus** comme nous l'avons vu plus haut. La -solution alors: *Kernel Page Table Isolation* ou KPTI. La table des pages du -noyau n'est accessibles qu'en mode noyau. - -![Pages noyau indisponible en mode utilisateur](./images/page_noyau_meltdown.svg) - -Comme on peu le voir sur le schema ci-dessus, les pages noyau sont invisible en -mode utilisateur, ainsi il est impossible de lire des pages mémoire noyau en -utilisant Meltdown. Conéquence directe : **une perte de performance entre 5 et -25%** msesuré sur une architecture Inter Haswell / Skylake. - -## Optimiser la pagination - -Dans le but d'accélerer l'allocation mémoire qui console beaucoup de cycle CPU, -les systèmes utilisent des optimisations. Dans cette partie, nous allons -explorer deux pistes: - - * l'allocation paresseuse / à la demande - * le copy on write - -### L'allocation à la demande - -Son principe est simple: **reporter à plus tard** l'allocation des pages qu'un -processus y accèdera pour la première fois. - -Il est rare qu'un processus utilise les sements de pile qui luis sont allouées -par défaut sous *Linux*. Il est aussi possible que certain tableaux alloués -statiquements ne soient pas utilisés. Dans le même ordre d'idée il y a des -fonctions d'un programme que l'on utilise pas. - -Bien entendu un minimum de pages sont allouées au départ afin de permettre le -bon fonctionnement du processus. Mais alors **comment distinguer les erreur de -segmentation d'une page non encore allouée?** Car une page non allouée est -marquée **invalide** par la MMU. - -Le cheminement doit être le suivant: - - 1. On accède à une page non encore allouée - 2. Une exception **page invalide** - 3. La MMU positione l'adresse mémoire dans un registre - 4. Le noyau vérifie si l'adresse virtuelle est présent dans une structure VMA - 5. Si c'est le cas lancement d'un `get_free_page()` et correction de la table - des page. Sinon on envoi un `SIGSERV` au processus. - -#### la structure Virtual Memory Area - -Elle est évoquée ci-dessus, cette struture permet au noyau de garder une trace -des pages virtuelles qui lui sont allouées, elle prend la forme suivante: - -```c -struct vm_area_struct { - unsigned lzong vm_start; - unsigned long vm_end; - pgprot_t vm_page_prot; - unsigned short vm_flags; -struct file * vm_file; -``` -#### conséquences - -Les larges allocations mémoires sont faites à la demande, une page à la fois, -évitant de nombreux cycle CPU au démarrage du processus. - -### Copy on write - -Le fonctionnement historique de la création de processus sous Unix (encore en -place aujourd'hui) se fait à base de `fork()` et `exec()`: - - * `fork()` créer un espace d'adressage contenant une copie de son processus - père, un clone en fait. - * `exec()` charge un nouveau programme (dans le segment code). - -Le fonctionnent du `fork` et du `exec` a déjà été abordé lors du [chapitre des -processus]({{< ref "../../progsys/5_les-processus_legers/index.md#création-de-processus">}} -des cours le Lpro. en voici un exemple en C: - -```c -int main (int argc, char *argv[]) -{ - pid_t pid = fork (); - if (pid) { // Parent - wait (NULL); - } else { // Child - execl ("/bin/ls", "ls", "-l", NULL); - perror ("ls"); - exit (EXIT_FAILURE); - } - return 0; -} -``` - -La séquence formée de ces deux fonctions est coûteuse (surtout la copie des -espaces d'adressage), y-a-t-il un moyen de faire mieux. Surtout qu'après le -`fork()`, `exec()` va reinitialiser la plupart des pages, c'est donc en prime -inefficace. - -La table des pages n'est que pointeurs, alors pourquoi ne pas la copier du -processus père vers le fils? C'est là que le *Copy on Write* entre en jeu : - - 1. La table des pages est dupliquée - 2. Les pages passent en lecture seule - 3. Lorque'un processus veux accéder à une page **en écriture**, le noyau lui - donne sa copie à lui. - 4. Le noyau corrige la table des pages. - -Pour s'assurer que l'accès est autorisé en écriture, le système utilise la -structure *Virtual Memory Area*. - -[l_woutoforder]:https://fr.wikipedia.org/wiki/Ex%C3%A9cution_dans_le_d%C3%A9sordre