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:
malloc
est décomposé en sous-procédures
- ex:
NB :
ltrace echo
: ne fonctionne pas, carecho
est un “shell built-in” : ne fait pas appel à un nouveau processus (en dehors du shell) queltrace
peut observer.
Quand on fait man command
:
- Section 1 : Applications de bas niveau
- affiché dans la
man
page :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
x
dans 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
ls
depuis 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