Cours 5 : Systèmes d’exploitation
Système d’exploitation
Architecture :
Programme utilisateur
-------------------------------
bibliothèques : |
- iconv |
- ls |
- write | (fonctionnalité basse)
- printf | : plus haut niveau, puis fait un appel à write
-------
| noyau |
-------
NB: Sous UNIX, les noms de bibliothèques système ont le même nom qu’en C, car UNIX est quasiment complètement codé en C.
en bash :
ltrace: affiche les appels bibliothèques- ex: appel de
malloc
- ex: appel de
strace: affiche les appels noyaux- ex:
mallocest décomposé en sous-procédures
- ex:
NB :
ltrace echo: ne fonctionne pas, carechoest un “shell built-in” : ne fait pas appel à un nouveau processus (en dehors du shell) queltracepeut observer.
Quand on fait man command :
- Section 1 : Applications de bas niveau
- affiché dans la
manpage :command(1)
- affiché dans la
- Section 2 : Biliothèque système
- Section 3 : Autres appels de bibliothèque (ex:
printf)
POSIX
Norme (valable pour Mac et Linux) qui spécifie les appels bibliothèque/noyau : en donnant
- la spécification des fonctions
- leur comportement
- etc…
“C99” : standard de C en 1999.
Statique VS Dynamique
gcc -static fichier.c -o fichier : compile le fichier test en version “standalone”
ldd fichier : affiche les dépendances par rapport aux bibliothèques du fichier fichier
VS :
- compilation en mode dynamique : fichiers beaucoup plus légers, partage des ressources bibliothèque entre les différents fichiers.
- compilation en mode statique :
- on est sûr que tout utilisateur aura les mêmes bibliothèques sur n’importe quel OS, puisqu’elles sont incluses dans le fichier (plus lourd, mais tout est sur place).
- OU : si on veut être sûr que les appels des bibliothèques font bien ce qu’on voulait faire chez tout le monde (important pour la sécurité).
NB : bibliothèques sont gardées en mémoire pour gérer plusieurs exécutions de fichiers dynamiques (en faisant pointer les adresses des programmes vers les bonnes adresses des fonctions de la bibliothèque
Processus
- Processus :
- Une instance d’un programme exécuté.
ps : n’affiche que les processus lancés dans le terminal courant.
top : affichage dynamique : quels processus sont les plus actifs.
pstree : affiche l’arborescence des processus actifs.
- ex: si on y voit
4*{processus}= 4 threads tournent dans ce processus
- Deamon :
-
processus qui a une tâche spécifique (rempli un certain service).
Ex :
processus loop
void main(){
for (;;){};
}
: occupe 100% du CPU (vu dans top)
Pourquoi on peut lancer plusieurs processus loop comme le précédent qui occupent 100% du CPU ?
Car il y a plusieurs coeurs.
Les pourcentages de top sont pour un seul coeur.
pstree : affiche arborescence des processus
ex: un terminal lance plusieurs shell : on voit l’arborescence terminal > bash, bash, bash
fork : duplique le processus actuel
- le père obtient une valeur non nulle
- le fils obtient une valeur nulle
kill num : envoie un signal au processus de pid num, et le tue
Si on tue un processus : devient un zombie : Z+ dans ps au (au lieu de S+ : processus actif)
Zombie : il lui reste quelque chose à faire/dire :
- ex : son résultat, valeur de retour
Ex :
1)
int main(){
// ...
return 0;
}
ou
int main(){
// ...
exit(5);
}
zombie : retournera 0 / 5
2) Si un processus fils a été invoqué avec fork() : revoie une valeur au père, en zombie.
getpid():-
affiche son propre pid
getppid():-
affiche son le pid du père
⟶
avec un wait dans le père, on récupère la valeur rendue par le fils à sa mort (ex: par un exit), et le fils zombie disparaît.
-
wait(pointeur): attend jusqu’à ce qu’il y ait un zombie fils du père, récupère son code de sortie, et le stocke dans*pointeur.- NB :
wait: seulement de père à fils.
- NB :
Pourquoi chaque fils a un identifiant différent ? ⟶ pour que le père puisse les distinguer
à l’intérieur du shell : combinaisons entre wait, fork et exec
getpid:-
pour obtenir son propre idetifiant
getppid:-
pour obtenir l’identifiant du père
Les variables locales du père ne sont pas transmises au fils.
VS :
Modèle des threads : plusieurs exécutions concurrentes dans un même programme.
Interruption d’horloge : peut intervenir à n’importe quel moment.
En fait, quand on lance un processus (ex: un programme C compilé), il se peut que, pendant l’exécution du printf, l’ordonnanceur commande une interruption d’horloge.
Même pendant x++, puisque c’est 3 opérations assembleur :
- récupérer la valeur de
xdans un registre - l’incrémenter
- la remettre dans le registre
NB : les interruptions assembleur ne sont disponibles qu’en mode noyau (car on peut bloquer l’ordinateur).
- Sémaphore :
-
une sorte de compteur ayant deux opérations d’exclusion mutuelle (si on a une ressource à laquelle on veut accéder par plusieurs processus en même temps):
- regarder le compteur
- incrémenter/décrémenter le compteur
Sémaphores ⟶ gérés directement par le noyau, pour que les opération précédentes soient atomiques (normalement : ce n’est pas le cas).
États :
- actif
- passif
- zombie
- …
Variables d’environnement : ex :
- le PATH
- variable de dossier actuel
- les processus fils héritent de cette variable (si on fait
lsdepuis ces fils ⟶ ils se servent de celle-ci)
- les processus fils héritent de cette variable (si on fait
chdir : “change directory” : change le dossier (l’équivalent de cd dans le shell)
exit:-
le processus devient zombie, on ne garde que sont identifiant (identifiant que le père peut récupérer).
dans ps :
S+: le processus est en attenteZ+: zombieR+: processus en train de tournerT: processus “stoppé” (avec Ctrl+Z par exemple)
exec:-
remplace le code du processus par un programme sur le disque
Shell basique :
int main(int argc, char **argv){
if (fork()){
wait(NULL);
printf("[parent] child terminated! \n");
}
else{
printf("[child] waiting for you to type! \n");
getchar();
execlp("ls","custom_command","-l",NULL);
printf("Error, `ls` not found in PATH! \n");
}
}
⇓
dans un shell, si on tape custom_command : ça fait l’effet de ls -l
cmd1; cmd2 ⟶ le père exécute fork, puis wait, puis fork, puis wait.
Dans le shell :
- si une commande réussit, elle renvoie
0
⟹ c’est exactement le contraire de la sémantique adoptée en C ! (où 0 == faux, n≠0 == vrai)
Pourquoi ?
- car : comme ça, on peut distinguer les différents codes d’erreur, selon l’entier renvoyé
cmd1 || cmd2:-
exécuter
cmd1, et si le code de sortie est différent de 0 (échec), exécutercmd2 cmd1 && cmd2:-
exécuter
cmd1, et si le code de sortie est 0 (réussite), exécutercmd2
Leave a comment