Processus
Prérequis
Nous allons devoir étudier les processus dans un environnement Linux. Vous allez donc devoir vous connecter sur un serveur Linux du lycée en ssh. Lancez le PowerShell Windows pour utiliser ssh et saisissez la commande suivante :
ssh votre_prenom@192.168.255.19
Remplacez votre_prenom
par votre prénom.
Si on vous demande de confirmer la connexion répondez « yes ».
Votre mot de passe est votre date de naissance sur six chiffres.
Présentation
Un processus est un programme en cours d'exécution. Un processus est identifié par son PID : Process IDentifiant. Un processus a généralement un processus père identifié par son PPID : Parent PID. Découvrons tout ceci sur votre machine.
ps
La commande Unix permettant de lister les processus est ps
.
1) Essayez en tapant ps
dans la console.
Vous devez alors obtenir quelque-chose comme ce qui suit :
PID TTY TIME CMD
172223 pts/1 00:00:00 bash
212663 pts/1 00:00:00 ps
Ce n'est pas très intéressant car ps
n'affiche par défaut que les processus de votre terminal.
Il est possible de donner des options à ps
pour changer son comportement.
Par exemple l'option -e
lui dit d'afficher tous les processus.
2) Essayez la commande ci-dessous :
ps -e
Il doit maintenant y avoir beaucoup plus de processus affichés !
Trop d'informations sont affichées, nous allons modifier l'affichage (avec l'option -o
) pour avoir seulement les informations qui nous intéressent : le PID, le PPID, l'utilisateur et la commande.
3) Essayez la commande ci-dessous :
ps -e -o pid,ppid,euser,comm
Au démarrage de l'ordinateur, le noyau Linux est chargé et il lance deux processus : systemd
et kthreadd
.
kthreadd
, avec le PID = 2 s'occupe principalement de gérer le matériel alors que systemd
avec le PID = 1 (aussi appelé init
) gère tout le reste (réseau, serveur graphique, tâches de fond, logiciels…).
C'est pour ça que ce sont les deux seuls processus à avoir un PPID de 0 : il n'ont pas vraiment de parent.
systemd
sera donc l'ancètre de tous les processus que vous créerez sur l'ordinateur.
Vous avez maintenant toutes les informations nécessaires pour répondre aux questions suivantes.
4) Quel est le parent du processus mysqld
?
5) Quel est la chaïne de processus entre systemd
et ps
que vous avez dû lancer pour répondre à cette question ?
6) Quel est la chaîne de processus entre systemd
et sshd
qui gère les connexions ssh ?
Dans le but de connaître le nombre de processus nous allons simplement compter le nombre de lignes retournées par ps grâce à la commande suivante :
ps -e -o pid=,ppid=,comm= | wc -l
7) Combien de processus sont actifs sur votre machine ?
Autres commandes
pstree
Il existe la commande pstree
qui permet d'afficher les processus sous forme d'arbre.
8) Testez la commande suivante :
pstree
Il est égalementy possible d'afficher le PID.
9) Testez la commande suivante :
pstree -p -T
top et htop
top
et sa version plus esthétique htop
permettent de voir (entre autre) les processus classés par leur utilisation du processeur.
10) Lancez htop
dans un terminal et notez les trois processus avec le plus d'utilisation du processeur.
htop
affiche également les threads il peut donc être difficile de trouver un processus.
Il est possible de filtrer les processus affichés en appuyant sur F4 et en saisissant un critère.
11) Toujours dans htop
, affichez seulement les processus sshd
et donnez leur nombre.
kill et killall
Il est possible de demander à un processus de se terminer grâce à la commande kill
.
Pour cela, il faut connaître son PID et lui envoyer la commande :
kill PID
On dit alors qu'on a tué le processus. Cette commande demande gentillement au processus de se terminer. Il est possible de forcer l'arrêt d'un processus en lui envoyant un signal plus « radical ». Cela peut servir lorsqu'un processus est planté :
kill -9 PID
12) Ouvrez un deuxième PowerShell à coté du premier, connectez-vous au serveur et lancez top
.
Déterminez alors son PID.
13) Dans l'autre console, tuez alors le processus de top
et remarquez la fermeture du programme.
14) Essayez maintenant de tuer le processus cron
ou systemd
. Que se passe-t-il ? Pourquoi ?
La commande killall a le même fonctionement que kill sauf qu'elle demande le nom du processus en entrée.
Lancez encore une fois top
dans une console et lancer la commande suivante dans l'autre console pour le fermer :
killall top
Cette commande permet de tuer un processus sans connaitre son PID mais surtout elle permet de tuer tous les processus ayant le même nom. Cela peut être très utile lorsqu'un logiciel a planté.
Processus et Python
PID et PPID
Voici un script simple prog1.py
affichant son propre PID et celui de son parent :
import os
import time
def identifierProcessus():
print('Voici des infos sur le processus instancié par le programme prog1.py')
print('PID :', os.getpid())
print('PPID :', os.getppid())
identifierProcessus()
time.sleep(20)
Pour simplifier les choses, les scripts sont placés dans mon répertoire sur le serveur.
Vous n'avez donc pas à les recopier.
Dans les prochaines questions, il faudra lancer les scripts à partir de la console avec une commande comme celle-ci (par exemple pour prog1.py
) :
python3 /home/tbeline/prog1.py
15) En utilisant deux consoles, lancer ce script et vérifiez les informations données avec ps
.
16) Qui est le père de ce script ?
Fork
Il existe une commande Linux qui permet de dupliquer un processus : fork
.
Pour l'utiliser dans Python, on utilisera la commande os.fork()
.
Cette commande est très particulière car elle crée un processus fils identique au père.
Elle crée donc un script identique au premier qui va s'éxécuter en parallèle.
Le seul moyen de les différencier est la valeur de retour qui vaut 0 pour le fils et le PID du fils pour le père.
17) Exécutez dans la console l'exemple prog2.py
ci-dessous pour tenter de comprendre.
import os
import time
def pereFils():
print("je suis le père")
# Création du fils
newpid = os.fork()
# os.fork() renvoie 0 pour le fils et le PID du fils pour le père
if newpid == -1:
print("Erreur de création")
elif newpid == 0: # dans le fils
for loop in range(8):
print("Dans le fils", os.getpid(), os.getppid())
time.sleep(2)
else: #newpid>0 -> dans le père
for loop in range(3):
print("Dans le père", os.getpid(), os.getppid())
time.sleep(2)
pereFils()
18) Faite un pstree
pendant l'exécution du script pour visualiser les processus.
Dans la suite, vous allez devoir modifier les scripts. Or, il n'est pas possible de modifier un fichier qui n'est pas dans votre répertoire personnel sous Linux. Vous allez donc devoir copier le script et le modifier avec un éditeur de texte en ligne de commande…
Pour copier, il faut saisir la commande suivante :
cp /home/tbeline/prog2.py ~/
Pour modifier le script, nous allons utiliser l'éditeur de texte nano
qui est relativement simple.
Il va falloir vous adapter mais ça devrait aller.
Pour ouvrir le fichier :
nano ~/prog2.py
Une fois le fichier ouvert vous devez utiliser uniquement le clavier.
Pour sauvegarder il faut faire Ctrl + O
et pour quitter Ctrl + X
.
Attention pour exécuter le script il faudra ensuite utiliser la commande :
python3 ~/prog1.py
19) Modifiez le script pour qu'il affiche « fin du processus père » quand le père se termine et « fin du processus fils » quand le fils se termine.
20) Quel processus devient le parent du fils lorsque son père se termine ?
Ce phénomène est normal, lorsque le père d'un processus se termine, le processus est « adopté » par systemd
.
21) Faite un pstree
pendant l'exécution du script pour visualiser ce phénomène.
(Allongez éventuellement les boucle pour avoir plus de temps)
22) Modifiez ce script pour avoir un arbre de processus comme celui ci-dessous. Chaque processus devra afficher cinq fois « dans le … » et attendre deux secondes entre chaque affichage.
père─┬─fils 1───petit fils
└─fils 2
Wait
Il est possible de demander à un processus d'atendre que ces fils soient terminés avec la commande wait()
.
Pour l'utiliser dans Python, on utilisera la commande os.wait()
.
On dispose du programme prog3.py
suivant :
import os
import time
def pereFils():
#Création du fils
newpid = os.fork()
if newpid == -1:
print("Erreur de création")
elif newpid == 0: # dans le fils
for i in range(0, 5):
time.sleep(2)
else: # newpid > 0 -> dans le père
childProcExitInfo = os.wait()
pereFils()
On veut qu'il affiche ceci :
Je suis le père
Le père attend que le fils ait terminé
Dans le fils
Le fils écrit 0
Le fils écrit 1
Le fils écrit 2
Le fils écrit 3
Le fils écrit 4
Le processus fils se termine
Le processus fils a terminé
Le père se termine après que le fils ait terminé
23) Compléter le programme prog3.py
ci-dessus avec les instructions suivantes pour qu'il affiche ce qui est demandé.
print("Le processus fils a terminé")
print("Le père se termine après que le fils ait terminé")
print("Je suis le père")
print("Dans le fils")
print("Le processus fils se termine")
print("Le père attend que le fils ait terminé")
print("Le fils écrit",i)
Ordonnancement
Présentation
L'ordonnanceur est un composant du noyau d'un système d'exploitation qui détermine l'ordre d'exécution des processus sur les processeur d'un ordinateur. En effet, un processeur ne peut effectuer qu'une tâche à la fois. Avec plusieurs processeurs ou cœurs, il est possible d'effectuer plusieurs tâche simultanément, mais il y aura en général plus de processus que de processeurs ou cœurs. Il faut donc choisir à chaque instant quel processus va utiliser tel processeur : c'est l'ordonnancement. C'est un domaine assez complexe dont nous n'allons voir que certaines manifestations simples.
L'ordonnancement donne l'illusion que toutes les tâches sont exécutées en parrallèle alors qu'elles sont exécutées les unes à la suite des autres sur des temps très courts. L'ordonnanceur attribue du temps processeur aux processus. Ainsi si nous avons 10 processus avec la même priorité pour un seul processeur, chacun des processus va avoir droit, par exemple, à 1 ms toutes les 10 ms :
Priorité des processus
Nous venons de parler intuitivement de priorité de processus. C'est une notion centrale de l'ordonnancement. Ainsi de manière logique, ce sont les processus avec la plus grande priorité qui auront le plus de temps processeur.
Sous Linux, pour la plupart des processus, la priorité est un nombre entre 0 et 39. Attention ! plus le nombre est petit, plus le processus est prioritaire.
24) Lancez une commande htop
et observez la colonne PRI
qui correspond à la priorité.
Sous Linux, on peut changer soi-même la priorité d'un processus en agissant sur nice
.
C'est la colonne NI
dans htop
.
Il existe une relation très simple entre la valeur de nice et la priorité.
25) Essayez de deviner la relation mathématique entre PRI
et NI
en observant leurs valeurs dans htop
. (Ne faites pas attention aux colonnes avec une priorité négative)
Les processus ont donc une valeur de nice qui varie entre -20 et 19. Les utilisateurs normaux (comme vous) ne peuvent utiliser que des valeurs entre 0 et 19, les autres valeurs (les plus prioritaires) étant réservées à root. Nous allons voir maintenant comment donner une valeur de nice à un processus.
26) Dans une deuxième console lancez la commande suivante et notez la valeur de nice pour le processus top.
top
La plupart des processus ont une valeur de nice à 0. Cela veut dire que leur priorité est à 20. On peut donc choisir de donner une valeur plus élevée à nice pour que le processus soit moins prioritaire (par exemple pour un long calcul si on ne veut pas utiliser toutes les ressources du système). Pour cela, on utilise la syntaxe suivante :
nice -n valeur commande
27) Relancez top
avec un nice à 10.
Vérifiez cette valeur dans htop
.
Comme votre machine n'est pas très solicitée, des valeurs de nice différentes ne changent pas grand chose car il y a plein de temps processeur disponible. Pour voir les effets de nice, nous allons utiliser un script Python qui va faire travailler votre ordinateur :
import os
newpid = os.fork()
if newpid == 0: # Dans le fils
newpid = os.fork()
newpid = os.fork()
newpid = os.fork()
#else: # Pour le père
# os.nice(5)
print(os.getpid(), ":", os.nice(0))
i = 0
while i < 100000000:
i += 1
Ce script lance 9 processus qui comptent jusqu'à 100 millions. Seul le prof lancera ce script pour que ça ne soit pas trop le bazar sur le serveur.
Pour la suite, il faut avoir une console avec htop
.
28) Demandez au prof de lancer le script et relevez la charge cpu des processus dans htop
.
Demandez au prof de lancer le script en activant les deux lignes qui changent la valeur de nice pour le processus père.
29) Relevez la charge cpu moyenne des processus fils et celle du père.
30) Quel processus se termine en dernier ?
Demandez alors au prof de modifier le script pour donner une valeur de nice à 19 au père.
31) Relevez encore une fois la charge cpu moyenne des processus fils et celle du père.
32) Que constatez-vous ? Est-ce en accord avec le fonctionnement de nice ?
Interblocage
Présentation
Un interblocage se produit lorsque deux processus (ou plus) s'attendent mutuellement. Cela peut arriver lorsqu'un processus attend une ressource occupée par un autre processus, qui attend une ressource occupée par ce premier processus… Le schéma ci-dessous explique simplement cette situation :
Cas pratique
Il n'est possible d'avoir un interblocable qu'avec au moins deux processus. Nous allons utiliser ici les threads plutôt que fork. Nous utiliserons également des verrous (lock) pour permettre aux threads de s'attendre. Voici le code à utiliser :
import time
from threading import Lock, Thread
class myThread (Thread):
def __init__(self, id, nom, verrou1, verrou2):
Thread.__init__(self)
self.id = id
self.nom = nom
def run(self):
print ("Démarrage de " + self.nom)
if self.id == 1:
print("Aquisition du verrou 1 par le thread 1")
verrou1.acquire()
if self.id == 2:
print("Aquisition du verrou 2 par le thread 2")
verrou2.acquire()
time.sleep(2)
if self.id == 1:
print("Attente du verrou 2 par le thread 1")
verrou2.acquire()
print("Aquisition du verrou 2 par le thread 1")
print("Libération du verrou 1 par le thread 1")
verrou1.release()
if self.id == 2:
print("Attente du verrou 1 par le thread 2")
verrou1.acquire()
print("Aquisition du verrou 1 par le thread 2")
print("Libération du verrou 2 par le thread 2")
verrou2.release()
print ("Sortie de " + self.nom)
# Création des deux verrous
verrou1 = Lock()
verrou2 = Lock()
# Création des threads
thread1 = myThread(1, "Thread-1", verrou1, verrou2)
thread2 = myThread(2, "Thread-2", verrou1, verrou2)
# Démarrage des threads
thread1.start()
#thread2.start()
print("Sortie du thread principal")
while True:
pass
Ici, le deuxième thread n'est pas démarré, le Thread 1 doit donc se terminer.
Vous pouvez utiliser ce code dans Thonny comme d'habitude, vous pouvez donc vous déconnecter du serveur.
33) Lancez ce script et vérifiez que le thread se termine bien.
34) Après avoir décommenté la ligne de démarrage du thread 2, relancez ce script et constatez l'interblocage.
Une fois qu'on atteint un interblocage, il est inévitable de devoir arrêter un processus pour sortir de l'interblocage. En général, on cherchera à éviter et prévenir en amont les interblocages en gérant intelligement les ressources, par exemple en utilisant l'algorithme du banquier.
Sources et compléments
- Les processus
- le chapitre sur les processus d'un wikibook sur GNU-Linux.
- Processus Linux
- le chapitre de François Goffinet sur les processus.
- L'ordonnancement
- Page wikipedia sur l'ordonnancement.
- L'interblocage
- Page wikipedia sur l'interblocage.