Cours d'informatique pour le lycée

Processus

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 un terminal (Tilix).

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 firefox (ou le navigateur que vous utilisez) ?

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 Xorg qui gère le serveur graphique ?

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 python 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 dans le menu la calculatrice et déterminez son PID (le nom du processus est gnome-calculator)

13) Tuez alors le processus de la calculatrice et remarquez la fermeture du programme.

14) Essayez maintenant de tuer le processus Xorg. 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. Ouvrez encore une fois la calculatrice et lancer la commande suivante pour la fermer :

killall gnome-calculator

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 ne pas compliquer les choses, il faudra dans ce chapitre, lancer les scripts à partir de la console avec une commande comme celle-ci pour prog1.py :

python3 prog1.py

15) En utilisant une double fenêtre dans Tilix, 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. Il faudra appuyer sur « Ctrl + c » pour interrompre le processus à la fin.

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.

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 :

Proiorité 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 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 un terminal lancez la commande suivante et notez la valeur de nice pour le processus gnome-2048.

gnome-2048

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 gnome-2048 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. Il vous faudra peut-être réduire ce nombre pour que les processus se terminent par eux-même.

Pour la suite, il est recommandé d'avoir deux fenêtres de terminal côte à côte. L'une avec htop et l'autre pour lancer les commandes.

28) Lancez ce script en ligne de commande et relevez la charge cpu des processus dans htop.

Décommentez maintenant les deux lignes qui changent la valeur de nice pour le processus père.

29) Relancez ce script en ligne de commande et relevez la charge cpu moyenne des processus fils et celle du père.

30) Quel processus se termine en dernier ?

Modifiez alors le script pour donner une valeur de nice à 19 au père.

31) Relancez encore une fois ce script en ligne de commande et relevez 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.

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.