document introduction

publicité
Méthodes de programmation système : Introduction
1) Aspects Matériels
Un système informatique est constitué d'un certain nombre d'éléments matériels qui vont
ensemble permettre l'exécution de code. Ce code est constitué d'une suite d'instructions
exécutables par le ou les processeurs. Il est stocké dans la mémoire du système. Des
dispositifs d'entrée/sorties complètent le système en lui permettant d'interagir avec le monde
extérieur (clavier, écran, imprimante, disques durs…)
Pour exécuter le code situé en mémoire, un processeur utilise un certain nombre de registres
qui lui servent à stocker des informations tels que :
le pointeur de programme (Program Counter ou PC) : contient l'adresse de l'instruction
suivante à exécuter
le registre d'instruction (IR) : contient l'instruction en cours d'exécution
le pointeur de pile (Stack Pointer ou SP) : utilisé pour l'exécution de sous-programmes
L'exécution de code se fait donc en commençant par la lecture de l'instruction à l'adresse
contenu dans le PC depuis la mémoire, puis exécution de cette instruction (Rq : l'instruction
elle même peut lire des données en mémoire)
0x8000
load R1, 0x1000
0x8001
load R2, 0x1004
0x8002
add R1, R2, R3
0x8003
store R3, 0x1008
l'exécution de ce code entrelace la lecture en mémoire du code et des données :
1. lecture en 0x8000
2. lecture en 0x1000
3. lecture en 0x8001
4. lecture en 0x1004
5. lecture en 0x8002
6. lecture en 0x8003
7. écriture en 0x1008
Ce modèle d'exécution est dit "séquentiel". Les instructions sont exécutées dans l'ordre où
elles sont stockées en mémoire. Cependant, des déroutements sont possibles :
déroutement inconditionnel : instruction GOTO
appel de fonction (ou sous-programme) : instruction call
un appel de fonction entraine un déroutement à l'adresse passée en paramêtre à l'instruction
call, et signifie que le processeur doit exécuter les instructions qu'il trouvera à cette nouvelle
adresse, jusqu'à ce qu'il rencontre l'instruction "return". A ce moment là, il devra reprendre
l'exécution à l'instruction qui suit le "call" précédemment exécuté.
Comment le processeur supporte t'il l'appel de fonction ? par un mécanisme de pile. La pile
est gérée automatiquement par le processeur grâce à un registre : le pointeur de pile (SP =
Stack Pointer). Ce registre contient l'adresse mémoire du "haut de la pile". Une pile se
manipule via 2 instructions : push <registre> et pop <registre> qui permettent de "pousser" la
valeur contenue dans un registre sur la pile (i.e. copier cette valeur à l'adresse mémoire
contenue dans SP) ou "tirer" le sommet de la pile et le copier dans le registre indiqué. Ces
intructions décalent ensuite le pointeur de pile (remarquez que la pile “croit” en faisant
décroitre les adresses : on part du haut de la mémoire et on empile vers les adresses basses) :
L'instruction push <registre> se décompose ainsi en 2 actions :
store <registre>, (SP) copier le contenu de <registre> à l'adresse contenue dans SP
SP = SP-1
déplacer la position du sommet de la pile vers la case libre suivante
L'instruction pop <registre> se décompose elle aussi en 2 actions :
SP = SP+1
pointer sur la valeur au sommet de la pile
store (SP), <registre> copier la valeur au sommet de la pile dans <registre>
La pile est donc utilisée par le processeur de façon implicite pour exécuter les instructions call
(branchement à un sous-programme) et return (en fin du sous-programme) :
l'instruction call <addr> se décompose alors en 2 actions :
push PC
"pousser" l'adresse de retour sur la pile
load PC, <addr>
se brancher à l'adresse passée en argument
Après chargement de l'instruction call, mais avant son exécution, la structure des registres et
de la mémoire sera :
0X8001
0x8000
0x8001
call 0x4000
­­­­
call 0x4000 IR
0XFFFE
0xFFFF
YY
Après exécution de l'instruction call, on trouvera :
PC
SP
0X4000
0x8000
0x8001
call 0x4000
­­­­
0xFFFF
0x8001
YY
PC
call 0x4000 IR
0XFFFD
SP
l'instruction return consiste à effectuer :
pop PC
charger le pointeur d'instruction avec le contenu de la pile
Ce mécanisme d'empilement des adresses de retour permet de supporter des appels de
fonctions imbriquées. Exemple de fonctions imbriquées :
main()
{
f1(x,y)
{
...
call f1(a,b);
...
call f2(a,c);
...
f3(z)
{
....
call f3(x);
...
return;
....
return;
}
}
}
La pile est également utilisée pour la sauvegarde des registres dont le compilateur a besoin
pour faire exécuter l'algorithme de la fonction. Par exemple, si le compilateur décide d'utiliser
2 registres R1 et R2, il doit s'assurer que la valeur qu'ils contiennent sera bien conservée à la
“sortie” de la fonction. Cette sauvegarde est simplement effectuée en « poussant » ces
registres sur la pile en début de fonction. Les instructions push correspondantes sont insérées
par le compilateur :
f1()
{
push R1;
push R2;
...instructions de la fonction...
pop R2;
pop R1;
return;
}
Enfin, la pile est également utilisée par le compilateur comme allocateur de "mémoire locale".
En effet, une fonction peut avoir besoin d'un espacede mémoire temporaire pour les
traitements qu'elle doit réaliser.
Exemple : fonction qui échange le contenu de 2 variables
Exchg(int A, int B)
{
int temp,
temp = A ;
A=B;
B = temp ;
}
La variable temp est allouée dans la pile : le compilateur ajoute une instruction de décalage
du pointeur de pile (-1) à l'entrée de la fonction et il se servira du "trou" ainsi créé dans la pile
pour y stocker la variable "temp". Il a également ajouté l'instruction inverse à la fin de la
fonction (+1). Pour se souvenir de l'adresse de cette variable temporaire, le compilateur pourra
simplement recopier la valeur du pointeur de pile dans un registre préalablement sauvegardé.
Le code généré par le compilateur en début de fonction ressemblera à :
push R1
load R1,SP
sub SP,1,SP
...
sauvegarde du contenu du registre R1 sur la pile
chargement dans R1 de l'adresse de “temp” (sommet de la pile)
décalage du pointeur de pile (SP=SP-1)
bien entendu, la pile doit être remise dans son état initial en fin de fonction (avant l'exécution
de l'instruction return, qui doit retrouver l'adresse de retour au sommet). Le compilateur
ajoutera donc les instructions suivantes :
add SP,1,SP
pop R1
return
décalage du pointeur de pile (SP=SP+1)
restauration du contenu initial de R1
les Entrées/Sorties
à un certain moment, le programme voudra faire des E/S. ex : printf. Où se trouve le code
correspondant à cette fonction ?
dans les premiers ordinateurs, il est livré avec la machine sous forme d'une bibliothèque
associée au compilateur Fortran et il accède directement au matériel. Que va faire le
processeur pendant l'exécution de l'E/S ? rien d'utile (attente active)
Comment s'utilisaient ces ordinateurs ?
Les premiers ordinateurs n'ont quasiment aucun logiciel embarqué : le chargement de la
mémoire et des registres se fait par l'intermédiaire de "clés", qui sont des interrupteurs (0/1)
Rapidement, les outils utilisés dans la "mécanographie" sont adaptés pour le chargement des
données et des programmes en mémoire : les cartes perforées. Un code utilitaire "en mémoire
morte" de chargement de la mémoire à partir d'un lecteur de cartes perforées est ajouté par les
constructeurs.
session typique de compilation :
1) chargement du compilateur en mémoire (1ière passe)
2) chargement du code source en mémoire
3) exécution de la première passe de compilation
4) perforation du résultat intermédiaire
5) chargement du compilateur en mémoire (2ième passe)
6) chargement du code intermédiaire en mémoire
7) exécution de la deuxième passe de compilation
8) perforation du résultat final (programme exécutable)
9) chargement du programme
10) chargement des données
11) exécution du programme
12) perforation du resultat
Les machines étaient allouées aux utilisateurs pour "un certain temps", pendant lequel ils
faisaient tout...
pas optimisé => pupitreurs
Les pupitreurs : un "humain" optimise l'utilisation de la machine (ex : compilations en série),
mais travail manuel...
Puis invention du traitement par lots "BATCH" : Pour automatiser le travail des pupitreurs :
les "control cards" permettent de décrire le job à exécuter sur une carte spéciale.
l'enchaînement des travaux est piloté par un "moniteur" = ancetre des systèmes d'exploitation
modernes.
optimisation suivante = diminuer le temps perdu en attente active... Off Line Operations :
les E/S se font sur bandes magnétiques, qui sont traitées ensuite (ex impressions)
le job suivant est chargé en mémoire pendant l'exécution du job courant
Le chargement en mémoire de plusieurs travaux simultanément est assez complexe. il faut :
● être capable de charger en mémoire des programmes à des adresses non connues à
l'avance : mécanismes de code relogeable, d'adressage indirect...
● offrir des mécanismes de protection qui empêchent un programme "faux" d'aller écraser la
mémoire du programme d'à côté, ainsi que le code du système d'exploitation : mécanismes
de protection mémoire.
Pour optimiser encore le temps d'utilisation des processeurs : invention des interruptions. le
CPU peut être interrompu de façon asynchrone et se mettre à exécuter du code ailleurs en
mémoire (code du système d'exploitation). Ce code va sauvegarder le contexte du processus
interrompu, puis restaurer le contexte d'un autre job et lui "rendre la main". Ce mécanisme
permet de partager le CPU entre plusieurs programmes en cours d'exécution.
Ces systèmes en multiprogrammation sont toujours des systèmes "batch". les travaux sont
soumis dans une "liste" et exécutés sans interaction avec un utilisateur.
Viennent plus tard les systèmes interactifs :
time sharing : le passage d'un processus à l'autre est cadencé par une horloge "rapide"
les ordinateurs sont connectés à des terminaux = ligne série
des interpréteurs de commande "interactifs" sont ajoutés au systèmes.
D'autres fonctions sont introduites dans les OS:
protection mémoire = mémoire virtuelle + mode user/mode système
disque = accès direct aux données et programmes = systèmes de fichiers
identification des utilisateurs, avec attribution de droits plus ou moins importants
Définition d'un OS:
C'est un ensemble de fonctions (code) et de données qui servent à optimiser l'utilisation de la
machine, en terme de partage des ressources (CPU, mémoire, E/S) et de sécurité (chaque
programme s'exécute sans faire courrir de rique aux autres).
Autre définition : système de virtualisation du hardware qui permet à des processus différent
de s’exécuter sur la machine « comme si elle leur était entièrement dédiée »
Les principaux composants d’un OS sont :
la gestion des processus : création, exécution, ordonnancement
la gestion de la mémoire : partage de la ressource mémoire entre tous les processus
la communication entre les processus
la persistance des données : utilisation des disques / les systèmes de fichiers
les entrées/sorties
Le cours s’appuiera sur un OS de type UNIX – LINUX.
Créer un processus…
Téléchargement