Aller au contenu

Processus et ordonnancement dans le système d'exploitation

1) Observer les processus en cours d'exécution

Dans la ligne de commande linux, tapez la commande htop.

Celle-ci vous permet de voir les processus en cours d'exécution sur la machine en temps réel, avec notamment :

  • PID : le Processus ID soit numéro du processus. Tous les processus en cours on un numéro unique qui sert à les identifier et ne change pas au cours de l'exécution.
  • USER : l'utilisateur qui a lancé ce processus
  • S : l'état du processus (en général R pour running, actif, et S pour sleep, endormi)
  • CPU% : le pourcentage de calcul d'un processeur utilisé
  • MEM% : le pourcentage de la RAM utilisée
  • TIME+ : le temps depuis que le processus est démarré
  • Command : la commande qui a servi à lancer le processus

Avec F5 on peut voir les processus sous forme d'arbre : en effet, tous les processus sont démarrés par d'autres processus.

Question : Quel est le processus à la racine de cet arbre ?

L'appel système (signal envoyé par le programme au système d'exploitation) de base qui permet de créer un processus est fork.

Exercice : Dans la liste des processus, repérez le PID de la commande htop que vous venez de lancer. Ouvrez un autre terminal, et entrez la commande kill suivie de ce numéro. Que se passe-t-il ?

Observer python avec htop

Lancez la commande 'python3' dans un terminal.

Dans un autre terminal à côté, lancez htop et cherchez python (avec '/' puis tapez python) pour surligner la ligne qui correspond à python.

Exécutez les lignes de code suivantes une par une en observant à chaque fois la consommation de mémoire de python :

L = [0] * 10**4

L = [0] * 10**6

L = [0] * 10**7

L = [0] * 10**8

([0] * x crée une liste de x zéros)

Puis les commandes suivantes en observant la consommation de CPU de python:

n=0
for i in range(10**4): n+=1

for i in range(10**6): n+=1

for i in range(10**7): n+=1

for i in range(10**8): n+=1

(on a le droit de mettre le n+=1 sur la même ligne, mais il faut appuyer deux fois sur entrée dans la console)

Question : Combien de coeurs du processeur python utilise-t-il ?

2) Création de processus avec fork

Dans VSCodium ou Thonny, copiez ce code et exécutez-le:

import os

resultat = os.fork()

print("fork() a renvoyé la valeur",resultat)

Que se passe-t-il ? Que fait la fonction fork, et qu'est-ce qu'elle renvoie ?

Exercice : changez ce programme pour que le processus pour lequel fork() a renvoyé 0 affiche "Je suis l'enfant", et l'autre "Je suis le parent".

3) Observer l'ordonnancement

Vous avez vu avec htop qu'il y a beaucoup plus de processus lancés sur l'ordinateur que de coeurs du processeur.

Le travail de l'ordonnanceur qui fait partie du système d'exploitation est de répartir l'exécution sur les coeurs du processeur de chacun de ces processus. Ceux-ci s'exécutent donc par petites séquences avec des pauses plus ou moins longues entre deux selon la charge du système.

Téléchargez, ouvrez avec VSCodium, lisez et exécutez ce script python.

Ce code utilise la bibliothèque Thread pour lancer deux threads (des processus légers qui peuvent partager de la mémoire), l'un qui affiche un million de fois "A" et l'autre un million de fois "B".

Observez le résultat : linux laisse chaque thread s'exécuter un moment, puis passe à l'autre.

4) Ordonnancement et mémoire partagée : la programmation concurrente ⚠

Téléchargez, ouvrez avec VSCodium, lisez et exécutez ce script python.

À votre avis, quelle devrait être la valeur de la variable compteur à la fin ? (le mot-clé global dans les fonctions permet aux fonctions de changer une variable qui n'est pas définie dans la fonction).

Que se passe-t-il, ou plutôt, qu'est-ce qui n'est pas normal dans ce qu'il se passe ? (selon la version de python et le noyau linux, peut-être que rien d'anormal ne se passe)

Pour mieux comprendre, ouvrez une console python et tapez successivement :

import dis
code = compile("compteur = compteur + 1","","exec")
dis.dis(code)

Cela permet de voir que fait python en interne pour exécuter la ligne "compteur = compteur + 1".

5) Les verrous

Les verrous sont fournis par le système d'exploitation et permettent de s'assurer que des processus ou des threads ne font pas certaines choses en même temps. Par exemple accéder et modifier de la mémoire partagée.

Le module threading de python permet de créer un verrou avec Lock(). Cela crée un objet qui possède une méthode acquire pour prendre le verrou et release pour le libérer. Le système d'exploitation s'assure qu'un seul thread peut prendre le verrou à la fois, et un thread qui veut prendre un verrou déjà pris est mis en attente.

Exercice (créer un interblocage)

Téléchargez ce script, qui crée et exécute deux threads, un qui exécute la fonction programme1 et l'autre la fonction programme2.

Complétez ce programme pour que chacune des fonctions prenne les deux verrous juste avant chaque print, et les libère juste après, en faisant en sorte que l'ordre dans lequel les verrous sont pris soit différent entre les deux threads.

Exécutez le programme. Que se passe-t-il ?

Changez ensuite l'ordre dans lequel les verrous sont pris et réessayez.

6) Changer la priorité des processus sur linux

Linux utilise comme algorithme d'ordonnancement le CFS: Completely Fair Scheduler qui traite équitablement chaque processus : s'il y a n processus en train de tourner, chacun devrait avoir accès à une fraction 1/n du temps de calcul disponible.

On peut ajuster ce mécanisme en donnant une valeur de niceness (gentillesse) entre -20 et 19 aux processus. Plus elle est élevée, plus le processus est "gentil" envers les autres et leur laisse plus de temps de CPU. Par défaut les processus ont une niceness de 0, les valeurs négatives (plus aggressives) ne peuvent être mises qu'avec les droits du superutilisateur. Les utilisatrices classiques peuvent donc mettre des valeurs de 0 à 19.

Pour tester cela, créer un petit fichier inf.py qui contient cette boucle infinie:

compteur = 0
while True:
    compteur = compteur + 1

Et dans deux terminaux ouverts dans le dossier où est présent ce fichier, lancer deux fois ce programme avec la commande :

taskset --cpu-list 0 python3 inf.py

La commande taskset permet de choisir sur quel(s) coeur(s) du CPU le programme tournera (ici seulement le coeur 0).

Ouvrez ensuite htop et filtrez les résultats (F4) en tapant "python3 inf.py" comme motif pour ne voir que les deux programmes lancés.

1) Quelle proportion du CPU utilise chaque programme par défaut ?

2) Cliquez sur un des programmes et augmentez sa valeur de niceness en utilisant la touche F8, de 1 en 1 en regardant l'évolution de la répartition du CPU entre les deux programmes.

3) Terminez les programmes avec Ctrl + C.

Trouvez des ordonnancements qui mènent aux deadlocks

Dans ce jeu (en anglais), vous avez à chaque fois deux programmes qui s'exécutent de manière concurrente.

Votre rôle est celui de l'ordonnanceur : vous devez trouver l'ordre d'exécution qui va causer un bug.

  • Soit parce que les deux programmes sont en même temps dans une section critique où il ne devrait y en avoir qu'un à la fois;

  • Soit parce que vous avez atteint un interblocage (deadlock) : aucun des programmes ne peut plus avancer dans son exécution;

https://deadlockempire.github.io