Introduction aux systèmes d'exploitation
Introduction aux systèmes d'exploitation
Département d’Informatique
ICT4D
Polycarpe Siekambe
1 Definition et historique 3
1.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.1.1 Qu’est-ce q’un système d’exploitation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.1.2 Historique des systèmes d’exploitation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.1.3 Structure d’un système d’exploitation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.1.4 Appels système . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
5 GESTION DE LA MEMOIRE 25
5.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
5.2 Monoprogrammation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
5.3 Multiprogrammation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
5.3.1 Multiprogrammation à partitions fixe. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
5.3.2 Fragmentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
5.3.3 Swaping (Va-et-vient) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
5.3.4 Gestion de la mémoire par table de bits . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
5.3.5 Gestion de la mémoire par liste chaînée . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
5.3.6 Algorithmes d’allocation de la mémoire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
1
2/32 TABLE DES MATIÈRES
Definition et historique
1.1 Introduction
Un système informatique a pour objectif d’automatiser le traitement de l’information. Il s’agit en effet d’un ensemble de
matériels et de logiciels (hardware et software) destinés à réaliser des tâches qui mettent en jeu le traitement automatique
de l’information. La représentation schematique d’un système d’information est la suivante :
La partie matérielle (hardware) du système d’information est contituée des composants physiques (Ecran, clavier,
souris, ...), de la microarchitecture (registres, horloge, ...) et du langage machine.
La partie logicielle est constituée des programmes systèmes et des programmes applicatifs. L’objectif du logiciel est
d’offrir aux utilisateurs des fonctionnalités adaptées à leurs besoins et aussi de masquer les caractéristiques physiques
du matériel. Les programmes applicatifs concourent à la résolution des problèmes spécifiques des utilisateurs tandis que
les programmes systèmes (utilitaires et système d’exploitation) permettent plus le bon fonctionnement de la machine en
elle-même.
Dans le cadre de ce cours, nous nous focaliserons sur la partie logicielle du sistème informatique et plus spécifiquement
sur le sytème d’exploitation et son fonctionnement.
3
4/32 CHAPITRE 1. DEFINITION ET HISTORIQUE
1. Gestionnaire de ressources
2. Machine virtuelle
En tant que gestionnaire de ressources, le système d’exploitation permet entre autres :
— La gestion du/des processeurs(s).
— La gestion de la mémoire.
— La gestion des périphériques.
— ...
En tant que machine virtuelle, le système d’exploitation permet entre autres :
— Le chargement et le lancement des programmes d’applications.
— La gestion des processus et des fichiers.
— La protection contre les erreurs et la détection des erreurs.
— ...
Inconvénients de ce systèm :
— Le processeur reste inutilisé pendant les opérations d’entrées/sorties.
— Le temps d’attente des résultats est trop long.
— Pas d’interaction avec l’utilisateur.
Structure en couches
Ici le système d’exploitation est organisé en couhes, les couches étant fonctionnellement liées (chaque couche utilise les
fonctions des couches inférieures).
— Au plus bas niveau on trouve le noyau, l’interface entre le matériel et le logiciel. Il se charge, en utilisant les fonctions
fournies par le matériel, de gérer la UCT, les interruptions et les processus (la communication et la synchronisation).
Il doit entièrement résider en mémoire.
— Au second niveau, on trouve le gestionnaire de la mémoire qui se charge du partage de la mémoire entre les processus
en attente d’exécution.
— Au troisième niveau, on a le module de gestion des entrées/sorties qui se charge de gérer tous les périphériques
(clavier, écran, disques, imprimantes, etc.).
— Au quatrième niveau, on trouve le gestionnaire de fichiers qui se charge de la gestion de l’espace du disque, de la
manipulation des fichiers tout en assurant l’intégrité des données, la protection des fichiers, etc.
— Au quatrième niveau, on trouve le gestionnaire de fichiers qui se charge de la gestion de l’espace du disque, de la
manipulation des fichiers tout en assurant l’intégrité des données, la protection des fichiers, etc.
Modèle client/serveur
Dans le modèle client/serveur Le système d’exploitation est composé d’un noyau et d’un ensemble de serveurs. Le
noyau gère la communication entre les clients et les serveurs. Les clients sont les demandeurs de services. Par exemple,
pour demander un service, comme la lecture d’un bloc d’un fichier, un processus utilisateur (aussi appelé processus client)
envoie une requête à un processus serveur qui effectue le travail et renvoie une réponse. Les serveurs s’exécutent en mode
utilisateur. Ils ne peuvent donc pas accéder directement au matériel. Par conséquent, une erreur ou un bogue dans le
serveur de fichiers, par exemple, n’affectera pas, en général, l’ensemble de la machine. Les dégâts se limitent au serveur.
Les programmes applicatifs sont exécutés en mode utilisateur et le code du système d’exploitation est exécuté en mode
noyau.
Le code qui s’exécute en mode noyau possède un accès ilimité au matériel et du coup peut exécuter n’importe quelle
instruction du processeur, et peut référencer n’importe quelle adresse mémoire. Tout incident qui se produirait donc sous
ce mode pourrait interrompe le fonctionnement de l’ordinateur.
Les programmes applicatifs quant à eux s’exécutant en mode utilisateur ne disposent pas d’un accès direct au matériel
et donc aux emplacements mémoires. Mais cependant un programmes utilisateur voulant accéder aux ressources matérielles
doit passer par le système d’exploitation par le biais d’un appel système.
Un appel système est donc une interruption logitielles (TRAP) qui a pour rôle d’activer le système d’exploitation.
Il est lancé par un programme dit appelant, et permet de passer du mode utilisateur au mode noyau en vue d’effectuer
certaine tâches spécifiques (récupération des paramètres, vérification de la validité de l’appel, lancement de l’exécution
de la fonction demandée, récupération du résultat) et de retourner au programme appelant (avec un retour en mode
utilisateur).
Le tableau ci-dessus est la liste non exhaustive des appels systèmes Unix conformement à la norme Posix (ensemble
des procédures standard d’Unix).
8
9/32 CHAPITRE 2. GESTION DES PROCESSUS ET DES THREADS
de ses fils. L’espace d’adressage du processus fils est obtenu par duplication de celui du père.
Si la création d’un processus fils suspend l’exécution du père jusu’à sa terminaison, on dit que l’exécution est séquen-
tielle. MS-DOS est un exemple de sytème à exécution séquentielle.
Si la création d’un processus fils ne suspend pas l’exécution du père, on dit que l’exécution est asynchrone. Unix est
un exemple de système à exécution asynchrone.
Un processus se termine par une demande d’arrêt volontaire (appel système exit()) ou par un arrêt forcé provoqué par
un autre processus (appel système kill()). Lorsqu’un processus se termine toutes les ressources systèmes qui lui ont été
allouées sont libérées par le système d’exploitation.
# include <stdio.h>
# include <sys/types.h> // pour fork ()
# include <unistd.h>
int main(){
int fils_pid ;
if((fils_pid = fork())==0){
printf("Je suis le fils avec pid %d \n", getpid()) ;
}else{
if(fils_pid > 0){
printf("Je suis le père avec pid %d \n", getpid()) ;
}
}
return 0 ;
}
# include <stdio.h>
# include <sys/types.h>
# include <unistd.h>
int a=20 ;
int main(int argc, char *argv) {
pid_t x ;
switch (x = fork()) {
case -1 : // le fork a échoué
perror("le fork a échoué !") ;
break ;
case 0 : // seul le processus fils exécute ce < case >
printf("ici processus fils, le PID %d.\n ", getpid()) ;
a += 10 ;
break ;
default : // seul le processus père exécute cette instruction
printf("ici processus père, le PID %d.\n", getpid()) ;
a += 100 ;
}
printf("Fin du Process %d. avec a = %d.\n", getpid(), a) ;
return 0
}
#include <sys/wait.h>
int wait (int *status) ;
int waitpid(int pid, int *status, int options) ;
void exit(int return_code) ;
— wait() : Permet à un processus père d’attendre jusqu’à ce qu’un processus fils termine son exécution. Il retourne
l’identifiant du processus fils et son état de terminaison dans & status.
— waitpid() : Permet à un processus père d’attendre jusqu’à ce que le processus fils numéro pid termine. Il retourne
l’identifiant du processus fils et son état de terminaison dans & status.
— exit() : Permet de finir volontairement l’exécution d’un processus et donne son état de terminaison.
Les appels système wait() et waitpid() ont un grand intérêt pour le système. Lorsqu’un processus fils termine son
exécution (exit()) et que son père n’est pas au courant de sa terminaison (c’est-à-dire que son père n’a pas fait de
de wait() ou de waitpid()), alors ce processus fils devient un processus zombie. Un processus zombie bien que
n’utilisant pas les ressources du noyau, est toujours présent dans la table des processus jusqu’à ce que son père
exécute wait() ou waitpid().
Un thread est donc une unité d’exécution rattachée à un processus, chargé d’exécuter une partie du programme
du processus. Dans un système multithreading, un processus est vu comme un ensemble de ressources partagées (code
exécutable, segement de données, fichiers, périphérique, ...) entre les threads.
Lorsqu’un processus est crée, un seul fil d’exécution (thread principal) est associé au processus et ce dernier exécute
la fonction principale du processus. Ce thread en crééra donc d’autres. Chaque thread possède :
— Un identificateur unique : tid.
— Une pile d’exécution.
— Des registres (un compteur ordinal).
— ...
Avantages :
— Ils sont généralement créés et gérés rapidement.
— Ils facilitent la portabilité, du fait qu’ils ne ne dépendent pas du noyau.
Inconvénients :
— A tout instant, au plus un thread du processus est en cours d’exécution, ce qui rend cette implémentation non
intéressante pour les systèmes multiproceseurs.
— Si un thread d’un processus se bloque, tout le pocessus est bloqué.
2. Au niveau noyau.
Les threads noyau sont directement supportés par le noyau du système d’exploitation. Il se charge de leur gestion, du
temps processeur alloué à chaque thread. Dans les systèmes mutiprocesseurs, cette implémentation est intéressant,
car chaque processeur est attribué à un thread (modèle un à un). Si un thread d’un processus est bloqué, un autre
thread de ce processus peut être élu par le noyau.
Inconvénients :
— Au niveau de la performance, la gestion est plus coûteuse.
— Portabilité : les programmes utilisant les threads noyau sont moins portables que ceux utilisant les threads
noyau.
3. Aux deux niveaux (hybride).
Il s’agit du modèle plusieurs à plusieurs. Il sont implantés par le système d’exploitation (utilisateur et système).
Les threads utilisateur sont associés à des threads système. La plupart des tâches gestion s’effectuent sous le mode
utilisateur.
— Suspension de Threads
int pthread_join(pthread_t *thid, void **valeur_de_retour) ;
pthread_join() suspend l’exécution d’un processus léger jusqu’à ce que termine le processus léger avec l’identificateur
thid. Il s’agit de l’équivalent de waitpid(). valeur_de_retour sert à récupérer la valeur de retour l’état de terminaison
du processus léger.
pthread_tpthread_self(void) ; Cet appel retourne le tid du thread appelant.
— Terminaison de threads
void pthread_exit(void *valeur_de_retour) ;
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate) ;
pthread_exit() permet à un processus léger de terminer son exécution, en retournant l’état de terminaison.
pthread_attr_setdetachstate() sert à établir l’état de terminaison d’un processus léger :
1. Si detachstate = PTHREAD_CREATE_DETACHED, le processus léger libérera ses ressources quand il ter-
minera
2. Si detachstate = PTHREAD_CREATE_JOINABLE le processus léger ne libérera pas ses ressources. Il sera
donc nécessaire d’appeler pthread_join().
# include <stdio.h>
# include <sys/types.h>
# include <unistd.h>
# include <pthread.h>
int glob=0 ;
void* increment(void *) ;
void* decrement(void *) ;
int main() {
pthread_t tid1 , tid2 ;
printf(" ici main[%d], glob = %d", getpid(),glob) ;
// création d’un thread pour increment
if(pthread_create(tid1, NULL, increment, NULL) != 0)
return -1 ;
printf("ici main : creation du thread[%d] avec succès \n", tid1) ;
// création d’un thread pour decrement
if ( pthread_create(tid2, NULL, decrement, NULL) != 0)
return -1 ;
printf("ici main : creation du thread [%d] avec succès \n", tid2 ) ;
// attendre la fin des threads
pthread_join(tid1,NULL) ;
pthread_join(tid2,NULL) ;
printf("ici main : fin des threads, glob = %d \n",glob) ;
return 0 ;
}
void* increment(void *) {
int dec=2 ;
sleep (10) ;
glob = glob + dec ;
printf (" ici decrement[%d], glob = %d", getpid(), glob) ;
pthread_exit(NULL) ;
}
void* decrement(void *) {
int dec=1 ;
sleep (1) ;
glob = glob - dec ;
printf (" ici decrement[%d], glob = %d", getpid(), glob) ;
pthread_exit(NULL) ;
}
Le programme doit être compilé avec la librairie -lpthread. L’exécution en tâche de fond de pthread-pid.
Une trace d’exécution de ce programme est la suivante :
# include <stdio.h>
# include <sys/types.h>
# include <unistd.h>
# include <pthread.h>
void *threadf unction(void ∗ arg);
char message[] = "Hola mundo" ;
int main() {
int res ;
pthread_t a_thread ;
void *threadr esult;
res = pthreadc reate(a_thread, N U LL, thread_f unction, (void∗)message);
if(res != 0) {
perror("Thread creation failed ") ;
exit (EXIT_FAILURE) ;
}
printf ("En attente que le thread termine..." ) ;
res = pthread_join(a_thread, threadr esult);
if ( res != 0){
perror("Echec dans l’union du thread) ;
exit (EXIT_FAILURE) ;
}
printf ("Thread termine %s", (char *)thread_result) ;
printf("Le message est %s", message) ;
exit (EXIT_SUCCESS) ;
return 0 ;
}
void *thread_function(void *arg) {
printf ("La fonction du thread demarre. L’argument est ’%s’", (char *) arg) ;
sleep (5) ;
strcpy(message, "Adios !") ;
pthread_exit("Merci par le temps de CPU") ;
}
3.1 Introduction
Nous avions précédement défini un processus comme étant un programme en cours d’exécution. Il peut être prêt (en
attente de la ressource processeur), bloqué (en attente dun évènement d’E/S), ou alors en exécution (utilisant la ressource
processeur).Pour être dans l’un de ses états, le processus doit préalablement être présent en mémoire. Dans les système
multi-utilisateurs à temps partagé, plusieurs processus peuvent être présent dans la mémoire en attente d’exécution,
autrement dit en attente de la ressource processeur. Comment donc se fait l’allocation du processeur entre les processus
prêts ? L’objectif principal de ce chapitre est de pouvoir répondre à cette question.
18
19/32 CHAPITRE 3. ORDONNANCEMENT DES PROCESSUS
2. A moyen terme
Il décide lesquels des processus précédemment sélectionés doivent aller en mémoire (principale). Il peut aussi
transférer les processus de la mémoire principale (processus prêt actif) à la mémoire secondaire (processus prêt non
actif). Il effectue ses tâches de géstion en fonction du degré de multiprogrammation du système, et aussi en base
aux requêtes d’E/S des périphériques.
L’ordonnanceur à moyen terme introduit le concept d’états actifs et non actifs et deux états supplémentaires : prêt
en suspension et attente en suspension.
Un processus est donc prêt en suspension lorsqu’il est déjà prêt à sexécuter (il a déjà été sélectionner par l’ordon-
nanceur à long terme), mais cependant ne se trouve pas encore en mémoire principale ; Un tel processus ne peut
pas se voir attribuer le processeur.
L’attente en suspension est l’état d’un processus qui attend. Dans cet état, le processus se trouve en mémoire
secondaire.
3. A court terme
Il gère la file d’attente des processus prêts (actifs), en choisissant quel processus doit passer en exécution, et par
conséquent se charge des changements de contexte.
La figure ci-dessus nous présente le modèle des états actifs et non actifs des processus.
Figure 3.1 – Modèle des états actifs et non actifs d’un processus
La comparaison entre différentes politiques d’ordonnancement peut être faite à partir de certaines valeurs :
— Le temps de séjour. Le temps de séjour d’un processus (temps de rotation ou de virement) est l’intervalle de
temps entre la soumission du processus et son achèvement.
— Le temps d’attente. Le temps d’attente d’un processus est la différence entre le temps de séjour et le temps
d’exécution du processus.
Exemple 1 : Considérons cinq travaux A, B, C, D et E, dont les temps d’exécution et leurs arrivages respectifs
sont donnés dans le tableau ci-desous. Faire un schéma qui illustre son exécution et calculer le temps de séjour de
chaque processus, le temps moyen de séjour, le temps d’attente et le temps moyen d’attente en utilisant les politiques
d’ordonnancement :
1. Premier arrivé premier servi (PAPS).
2. Le plus court d’abord (SJF).
Processus T’emps d’exécution Temps d’arrivage
A 3 0
B 6 1
C 4 4
D 2 6
E 1 7
Exemple 2 : Soient deux processus A et B prêts tels que A est arrivé en premier suivi de B, 2 unités de temps
après. Les temps de l’UCT nécessaires pour l’exécution des processus A et B sont respectivement 15 et 4 unités de temps.
Le temps de commutation est supposé nul. Calculer le temps de séjour de chaque processus A et B, le temps moyen
de séjour, le temps d’attente, le temps moyen d’attente, et le nombre de changements de contexte pour les politiques
d’ordonnancement :
1. SRT.
2. Round robin (quantum = 10 unités de temps).
3. Round robin (quantum = 3 unités de temps).
Exemple 3 : Soient trois processus A, B et C. Le processus A arrive en premier suivi de B (3 unités de temps après),
puis C (4 unités de temps après B). On suppose que l’exécution du processus A nécessite 8 unités de temps de l’UCT.
L’exécution du processus B nécessite dans l’ordre 5 unités de temps de l’UCT, 2 unités de temps d’E/S et 3 unités de
temps de l’UCT. L’exécution du processus C nécessite 4 unités de temps UCT. Le quantum est de 3 unités de temps. Le
temps de commutation est de 1 unité de temps.
1. Montrer comment les trois processus vont utiliser le processeur, dans le cas d’un ordonnancement circulaire.
2. Calculer le temps de séjour pour chacun des trois processus et le temps de séjour moyen des trois processus.
3. Que se passe-t-il, si on augmente la valeur du quantum ? (Qt = 5).
4. Que se passe-t-il, si on diminue la valeur du quantum ? (Qt = 2).
Chaque fois qu’un processus s’exécute sa priorité est revue à la baisse. La priorité du processus en cours est comaprée
régulièrement à celle du processus prêt le plus prioritaire, une fois qu’elle devient inférieure, la commutation a lieu et le
processus suspendu est inséré en queue de file correspondant à sa nouvelle priorité.
Remarque : l’attribution et l’évolution des priorités dépendent des objectifs fixés et aussi des paramètres divers. Nous
pouvons par exemple avoir comme priorité le rapport du quantum et de la durée d’exécution restante pour le processus.
Exemple 4 : Considérons quatre travaux A, B, C, et D, dont les temps d’exécution, la priorité et leurs arrivages
respectifs sont donnés dans le tableau ci-desous. Faire un schéma qui illustre leur exécution et calculer le temps de séjour
de chaque processus, le temps moyen de séjour, le temps d’attente et le temps moyen d’attente en utilisant l’ordonnancement
à priorité.
COMMUNICATION SYNCHRONISATION
INTERPROCESSUS
4.1 Introdution
La communication interprocessus est un mécanisme dans lequel plusieurs processus s’exécutant en parallèle ou en
pseudo-parallèle coopèrent en s’échangeant les informations dans un but de résourdre des problèmes plus ou moins com-
plexes. Les systèmes d’exploitaton actuels offrent cette possbilité à plusieurs processus et threads concurrents de copérer.
Ceux-ci peuvent s’exécuter sur un même ordinateur (monoprocesseur ou multipocesseur) ou sur des ordinateurs différents
en réseau. Il existe plusieurs mécanismes de communication interprocessus :
— Les données communes (variables, fichiers, segment de données).
— Les signaux
— Les messages
— Les tubes de communication
— Les sockets
23
24/32 CHAPITRE 4. COMMUNICATION SYNCHRONISATION INTERPROCESSUS
Pour résoudre ces problèmes de confits d’accès, il s’avère donc nécessaire de contrôler les accès concurrents aux données
partagées au travers de la synchronisation des processus.
NB : La suite de ce chapitre se fera sous forme d’exposés qui porterons sur des sujets relatifs au présent chapitre.
GESTION DE LA MEMOIRE
5.1 Introduction
La mémoire principale est le lieu de stockage des programmes en cours d’exécution. Le besoin continuellement gran-
dissant de la mémoire par les programmes, necessite une gestion optimale de celle-ci. Le gestionnaire de mémoire est la
partie du système d’exploitation qui gère la hiérarchie de la mémoire installée sur un ordinateur.
L’espace d’adressage logique (ou virtuel) est un ensemble d’adresses pouvant être générées par un programme
(compilation ou édition des liens) ; Se sont des adresses manipulées par l’unité centrale.
L’espace d’adressage physique est un ensemble d’adresses physiques correspondant à un espace d’adressage lo-
giques. Il s’agit des adresses manipulées par la mémoire.
Dans sa gestion de la hiérarchie de la mémoire, le gestionnaire de la mémoire a deux objectifs principaux :
1. Le partage de la mémoire principale entre les programmes et les données des processus prêts.
2. La mise en place des mécanismes de calcul d’adresse, ceci permettant de passer de l’adresse virtuelle à l’adresse
physique et vice versa.
Pour atteindre ces objectifs, le gestionnaire de la mémoire doit remplir plusieurs tâches :
— Connaître l’état de la mémoire (quelles sont les parties libres et occupées ?).
— Allouer de la mémoire à un processus avant son exécution.
— Récupérer l’espace alloué à un processus dès la fin de son exécution.
— Traiter les va-et-vient (swaping) entre le disque et la mémoire principale (lorsque cette dernière ne peu pas contenir
tous les processus).
— Posséder un mécanisme de calcul d’adresse physique (absolue) à partir des adresses virtuelles (logiques ou relatives)
générées par le programmes (compilation ou édition des liens). Le calcul d’adresse est réalisé par le matériel, dans
un but de ne pas ralentir l’accès à la mémoire.
25
26/32 CHAPITRE 5. GESTION DE LA MEMOIRE
5.2 Monoprogrammation
Dans les systèmes monoprogrammé, il n’est possible d’avoir qu’un seul programme utilisateur en mémoire à la fois. Le
gestionnaire de la mémoire partage donc la mémoire entre un programme utilisateur et le système d’exploitation. Cette
gestion de la mémoire est assez simple :
— L’utilisateur entre une commande sur un termina ;
— Le système d’exploitation charge le programme demandé en mémoire et ce dernier est exécuté ;
— L’exécution une fois terminée, le système d’exploitation afiche une invitation sur le terminal et attend la commande
suivante pour charger un nouveau processus qui remplace le précédent.
Bien qu’étant très simple à gérer, la gestion de la mémoire dans les système monoprogrammé nécessite la protection
du système d’exploitation ; Il faudrait donc s’assurer qu’un programme utilsateur n’écrase pas le système d’exploitation.
Pour ce faire, on sauvegarde dans un registre (registre limite), l’adresse à partir de laquelle commencerons les instructions
et les données des programmes utilisateurs. Donc chaque adresse générée par un programme est comparée avec le contenu
de ce registre ; Si la valeur de l’adresse générée est supérieure ou égale à celle du registre, le programme est chargé en
mémoire, sinon, le programme n’est pas chargé et un message d’erreur est généré.
5.3 Multiprogrammation
La multiprogrammation autorise l’exécution de plusieurs processus utilisateurs. Dès qu’un procssus se bloque (en
attente d’E/S), un autre processus prêt peut devenir actif. Ceci permet donc d’optimiser le taux d’utilisation du processeur.
Cette technique exige que plusieurs programmes soient chargés en mémoire à un instant donné. La méméoire est
donc partagée entre le système d’exploitation et plusieurs programmes utilisateurs. Plusieurs techniques de gestion de la
mémoire peuvent donc être implémentées :
— Partitions fixes.
— Partitions variables.
— Le swaping.
— Gestion de la mémoire par table de bits.
— Gestion de la mémoire par listes chaînées.
des petites partitions pleine, tandis que les files d’attente des grandes sont vides ; Dans ce cas les tâches nécessitant
de petites tailles de mémoire attendrons pendant longtemps avant d’accéder en mémoire pourtant la mémoire
pourrait contenir des partitions inutilisées.
2. File unique. Pour palier au problème présenté précédemment, on peut utiliser une file d’attente unique. Dans
ce cas, dès qu’une partition est libre, tout processus pouvant y tenir et se trouvant en tête de file est chargé.
L’inconvénient de cette technique est le gaspillage de la mémoire ; En effet un petit processus peut se voir allouer
une grande partition. Pour éviter ce gaspillage, la getion de la file unique n’est le FIFO, mais dès qu’une partition
devient libre, on parcourt la file d’attente à la recherche du plus grand processus pouvant y tenir et on charge
ce dernier dans cette partition. Pour éviter que les petits processus ne soient pénalisés, à chaque petit processus
on attribue un compteur initialisé à 0, ce compteur est incrémenté d’une unité à chaque fois que le processus est
ignoré. Un processus ne pourra pas être ignoré k fois, où k est un paramètre fixé.
5.3.2 Fragmentation
Dans la multiprogrammation à partitions fixes, les processus pouraient ne pas (et c’est le cas très souvent) occuper tout
l’espace de la partition qui lui est attribuée, laissant donc de la mémoire non utilisée dans la dite partition. Il peut aussi
arrivé que toutes les partitions de la mémoire ne puisse pas être utilisées. Ce phénomène de présence d’espaces inutilisés,
aussi bien dans une partition et aussi due à la non allocation d’une partition à un processus est appelé fragmentation. On
parle de fragmentation interne dans le premier cas et de fragmentation externe dans le deuxième.
Supposons qu’un système simple a une mémoire de 120 Ko, et que tous les processus utilisent moins de 20 Ko, sauf un
qui utilise 80 Ko et qui s’exécute une fois par jour. Évidement pour permettre à tous les processus de s’exécuter le système
doit posséder au moins une partition de 80 Ko, et les autres partitions de 20 Ko. Ainsi le système utilisera seulement une
fois par jour les 80 Ko, et le reste du temps elle sera inutilisée, créant donc avec une fragmentation externe de 80 Ko ;
ou bien elle sera occupée partiellement par un processus de 20 Ko, créant de ce fait une fragmentation interne de 60 Ko.
Dans les deux cas on n’utilise que la moitié de la mémoire du système. La technique de vat-et-vient (ou swaping) apporte
une solution à ces problèmes.
Les partitions au lieu d’être de taille fixe ne le sont plus, mais leurs tailles et leur nombre sont variables (MVT :
Multiprogramming with a Variable number of Tasks). Lorsque tous les processus ne peuvent pas tenir simultément
en mémoire, on déplace alors certains sur disque à un espace réservé à cet effet (swap area ou backing store). Aussi, Un
processus qui est inactif (bloqué) peut être déplacé sur disque (swap out). Le gestionnaire de la mémoire dispose d’une
liste de toutes les zones utilisées et non utilisées de la mémoire. Ainsi, quand un processus requiert de la mémoire pour
son exécution, le gestionnaire trouve un espace libre contigue pouvant le contenir et cet espace lui est attribué (swap in).
Dans la gestion par partitions variables, l’allocation de la mémoire est dynamique, elle varie en fonction de l’arrivée et
du départ des processus de la mémoire principale. Cependant cette gestion pourrait conduire à l’apparition des trous dans
la mémoire. La conséquence de l’apparition des trous est que la mémoire peut avoir suffisamment d’espace (somme des
espace des trous) pouvant acquellir un processus, mais cependant cet espace libre n’est pas contigue et donc le processus ne
sera pas admis en mémoire. Pour résourdre ce problème, on réunit les espaces inutilisés en une seule partition en déplacant
tous les processus vers le bas : c’est le compactage de la mémoire.
Pour que le compactage soit effectif, le gestionnaire se doit de connaître l’état de la mémoire (Quelles sont les espaces
libres ? Quelles sont les esapces occupés ?). Ceci peut être assuré par les techniques de tables de bits (bitmaps), les listes
chaînées et les subdivisions.
Plus l’unité d’allocation est faible, plus la table de bits est impotante et en augmentant la taille de l’unité d’allocation,
on réduit la table des bits, mais cependant on augmente le niveau de fragmentation interne. Pour charger un processus de
k unités, il faut trouver k trous consécutifs en mémoire, autrement dit le gestionnaire le gestionnaire de la mémoire doit
parcourir la table à la recherche de k zéros consécutifs.
Lorsque la mémoire occupée par un segment est libérée, le gestionnaire fusionne le segment libre avec le ou les segments
adjacents libres s’il y en a. Quatre cas sont envisageables :
5.4.1 Pagination
Dans la technique de pagination :
— L’espace d’adressage d’un processus (espace d’adressage virtuelle) est divisés en unités de taille fixe appelées pages.
— La mémoire physique est découpée en unités d’allocation de taille identique (et égale à celle d’une page) appelées
cadres.
Dan la pagination, nous ne pouvons pas avoir de fragmentation externe (toute les pages on la même taille), mais
cependant une fragmentation interne peut apparaitre, ce qui arrivent si la dernière page de l’espace d’adressage logique
n’est pas pleine.
Chaque processus dispose donc d’une table de page qui lui permettra de faire la correspondance entre les adresses
virtuelles et les adresse physique afin de permettre le chargement des pages en mémoire. Lorsqu’un processus est en cours
d’exécution, seule une partie de son espace d’adressage est en mémoire : c’es la partie résidente (les pages utilisées). Chaque
adresse virtuelle est compsée d’un numéro de page et d’un déplacement dans la page.
Le numéro de page sert d’index dans la table de pages. Il y a autant d’entrées dans la table de pages qu’il y a de pages
dans la mémoire virtuelle. Chaque entrée de la table des pages est une structure comportant :
— Un bit de présence. Ce bit vaut 1 si la page correspondante a déjà été chargée en mémoire et 0 sinon.
— Un bit de référence. Ce bi est positionné chaque fois qu’on accède à la page (en lecture ou en écriure).
— Les bits de modification. Ils permettent de savoir si la page a éé modifiée depuis sa dernière sauvegarde.
— Le numéro de cadre de page. Ce numéro indique dans quelle cadre de la mémoire physique a été chargée la page.
— Les bits de protection.
Conversion d’adresses : Les adresses virtuelles doivent être convertis en adresse physique afin que une page soit
transférée en méoire. Cette convertion est effectuée par le MMU (Memory Management Unit) qui sont des circuits
matériels.
Quand le processeur veut accéder à une zone mémoire, il passe l’adresse virtuelle au MMU qui la transforme en
adresse physique en se servant de la tale des pages. Deux cas peuvent être envisageable quand le MMU accède à l’entrée
correspondant à la page :
1. La page recherchée se trouve en mémoire. Dans ce cas, il suffit alors d’accéder à l’information recherchée.
2. La page recherchée ne se trouve pas en mémoire. Il se produit alors un déroutement appelé défaut de page. Pour
le traitement des défauts de page :
— S’il y a un cadre de page libre, alors on charge la page virtuelle dans ce cadre.
— S’il n’y a pas de cadre de page libre, alors on utilise un algorithme de remplacement de pages dans un but de
libérer un cadre pour y inclure notre page.
Exemple : Pour un programme de 64 Ko (Son espace d’adressage logique est sur le disque) sur une machine 32 Ko
de mémoire, si la taille d’une page est de 4 KO nous avons 16 pages et 8 cadres.
Quelques livres Quelques documents utilisés pour la mise sur pied du présent document :
1. Operating systems-with pdf index Modern Operating Systems 2nd Ed by ANDREW S. TANENBAUM.
2. Systèmes d’exploitation, par Hanifa Boucheneb et Juan Manuel Torres-Moreno, Département de génie informatique,
École Polytechnique de Montréal, version 3.90