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
  • strace : affiche les appels noyaux
    • ex: malloc est décomposé en sous-procédures

NB :

ltrace echo : ne fonctionne pas, car echo est un “shell built-in” : ne fait pas appel à un nouveau processus (en dehors du shell) que ltrace peut observer.

Quand on fait man command :

  • Section 1 : Applications de bas niveau
    • affiché dans la man page : command(1)
  • 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.

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 :

  1. récupérer la valeur de x dans un registre
  2. l’incrémenter
  3. 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)

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 attente
  • Z+ : zombie
  • R+ : processus en train de tourner
  • T : 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écuter cmd2

cmd1 && cmd2 :

exécuter cmd1, et si le code de sortie est 0 (réussite), exécuter cmd2

Leave a comment