Aller au contenu

TNSI : TD Processus en Python : Le module os⚓︎

Danger

  • ⚠ Ce TD est spécifique à Unix/Linux ⚠
  • Référence Ultime de ce TD : Le module os de la Librairie Standard.
  • Les traductions automatiques en Français 🇫🇷 sont quelquefois douteuses (malheureusement).. ne pas hésiter à lire la source (en Anglais 🇬🇧)
  • Les programmes Python doivent être exécutés en ligne de commande, dans un Terminal

Lire des Informations sur les Processus⚓︎

Le code suivant, à sauvegarder, modifier et tester dans un fichier identifier.py :

# identifier.py (A sauvegarder sous ce nom-là)
import os  
os.getpid()        # PID du processus courant
os.getppid()       # PPID : PID du Père
os.getuid()        # UID : User ID = ID du propriétaire réel (celui qui l'exécute)
os.geteuid()       # Effecive UID = UID du propriétaire effectif (celui qui l'a créé)
os.getgid()         # groupe réel (Group ID)
os.getegid()        # groupe effectif (groupe de celui qui l'a créé)

os.setuid(uid)      # change le propriétaire du processus
  1. Copier-coller ce code, sauvegardez-le (en tant que identifier.py), modifier-le, exécutez-le.
  2. Afficher en détaillant tous les PIDs obtenus dans un Terminal (en ajoutant des print)
  3. Placer ces méthodes dans une fonction identifierProcessus.py, et configurez le module identifier.py avec un if __name__ == main

Créer un processus Fils avec os.fork() sur Linux⚓︎

newpid = os.fork() crée un processus fils du processus courant, et renvoie un nombre entier (ici stocké dans la varaible newpid) qui prend des valeurs différentes selon que l'on se trouve dans le processus fils ou dans le père :

Valeur de
newpid = os.fork()
(un entier)
Signification
Quel Processus est
en cours d'exécution ?
<0 Problème de Création
0 Processus Fils
>0
en fait = au PID du fils,
lorsqu'on se trouve
à l'intérieur du Père
Processus Père

  1. Création d’un processus fils dans le fichier creation.py :
    a. Analysez le programme suivant, et commentez-le (càd ajouter des commentaires), en particulier dans les if elif else :
    # creation.py
    import os
    import time
    
    def pereFils():
          print("je suis le père")  
          newpid = os.fork()
    
          if newpid == -1:
              print("Erreur de création")
          elif newpid == 0:
              print("Dans le fils")
              time.sleep(2)
          else:
              print("Dans le père")
              time.sleep(2)
    
    if __name__=="__main__":
        pereFils()
    
    b. Modifier le programme pour qu'il affiche 3 fois « dans le père » et 5 fois « dans le fils », dans le Terminal, en utilisant une boucle for dans chacun des cas.
    c. Modifier le programme pour qu'il affiche 3 fois « dans le père » et 5 fois « dans le fils », dans le Terminal, en mettant une seule boucle for à la fin de la fonction.
  2. Création d’un processus zombie. Exécuter le programme suivant, nommé zombie.py :
    # zombie.py
    import os
    import time
    
    def pereFils():
          #Variables du père, qui seront dupliquées dans le fils
          #Création du fils
          newpid = os.fork()
    
          if newpid == -1:
              print("Erreur de création")
          elif newpid == 0: # dans le fils
              #Variables modifiées par le fils donc dans son propre espace mémoire
              a=1
              print('{0} {1}'.format('PID du fils ',os.getpid()))
              time.sleep(0.2)
          else: #newpid>0 -> dans le père
              print('{0} {1}'.format('PID du père ',os.getpid()))
              time.sleep(30)
    
    if __name__=="__main__":
        pereFils()
    
  1. Dans une autre console, taper $ ps -eo pid,ppid,stat,command. Quel est l’état du processus fils. Pourquoi ?

Le fils est dans l'état Zombie Z+
Le + signifie qu'il fait partie des processus du premier plan.

44078    2052 Sl   xfce4-terminal
197090   44078 Ss   bash
197099  197090 S+   man ps
228454       2 I    [kworker/18:1-events]
228525       2 I    [kworker/13:2]
228534       2 I    [kworker/u40:2-events_unbound]
235612   31479 S+   python zombie.py
235613  235612 Z+   [python] <defunct>
235681    2280 S    /bin/bash /usr/lib/code/out/vs/base/node/cpuUsage.sh 2331 42789
235685  235681 S    sleep 1
235686  159336 R+   ps -eo pid,ppid,stat,command

Le module signal⚓︎

Le module signal de la librairie Standard (cf Référence module signal) permet de travailler avec les différents Signaux Linux (liste ici):

Exp

import signal
monSignal = signal.SIGTERM      # représente le Signal SIGTERM
                                # stocké dans la variable monSignal

Interruption d’un Processus avec os.kill()⚓︎

La méthode os.kill(pid, sig) envoie le signal sig au processus identifié de manière unique par son pid.

On se donne le programme suivant, à copier-coller et sauvegarder sous tuerProcessus.py.

# tuerProcessus.py
import os
import time
import signal

def pereFils():
      #Variables du père, qui seront dupliquées dans le fils
      #Création du fils
      newpid = os.fork()

      if newpid == -1:
          print("Erreur de création")

      elif newpid == 0: # dans le fils
          #Variables modifiées par le fils donc dans son propre espace mémoire
          a=1             
          print('{0} {1}'.format('PID du fils ',os.getpid()))
          time.sleep(0.2)

      else: #newpid>0 -> dans le père
          print('{0} {1}'.format('PID du père ',os.getpid()))
          time.sleep(30)

if __name__=="__main__":
    pereFils()
  1. Lancer tuerProcessus.py, observer la liste des processus avec la commande ps en affichant les pid et ppid.
  2. Que se passe t-il pour le père ?
  3. Que se passe t-il pour le fils ?

Signaux avec os.wait() et os.waitid()⚓︎

La méthode wait()⚓︎

Référence : Librairie Standard + https://linux.die.net/man/2/wait ($ man 2 wait) La méthode wait() du module os de Python est bloquante et attend qu'un processus fils soit terminé, elle ne reçoit aucun argument en entrée, puis renvoie en sortie un tuple contenant :

  • le PID du fils
  • son état/statut de sortie : Il s'agit d'un nombre entier de 16 bits tel que :
    • L'octet de poids faible est le numéro de signal qui a tué le processus,
    • L'octet de poids fort est le statut de sortie, si le numéro de signal vaut 0.
    • Le bit de poids fort de l'octet de poids faible est mis à 1 si un (fichier système) core file a été produit.
  • ou bien, renvoie -1 si le père n'a pas de fils

  • Exécuter le programme suivant, sous le nom de fichier attend.py (attention : PAS wait.py sinon conflit avec la librairie standard):

    # attend.py (Sauvegarder sous ce nom)
    import os
    import time
    
    def pereFils():
        newpid = os.fork()
        if newpid == -1:
            print("Erreur de création")
    
        elif newpid == 0: # dans le fils
            for i in range(0, 5):
                print("J'écris %d"%(i))
                time.sleep(2)
        else: #newpid>0 -> dans le père
            childProcExitInfo = os.wait()  
    
    if __name__=="__main__":
        pereFils()
    

    Compléter le programme avec les instructions suivantes :

    print("Le processus fils %d a terminé"%(childProcExitInfo[0]))
    print("Le père %d se termine après que le fils ait terminé"%(os.getpid()))
    print("Le processus fils de PID %d termine" %os.getpid())
    print("Dans le fils")
    print("je suis le père")  
    print("Le père attend que le fils ait terminé")
    

    de sorte obtenir l’affichage en sortie suivant :

    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 de PID 956 termine
    Le processus fils 956 a terminé
    Le père 955 se termine après que le file ait terminé
    

La méthode waitid()⚓︎

Référence : Librairie Standard + https://linux.die.net/man/3/waitid ($ man 3 waitid) La méthode waitid(idtype, id, options) du module os est bloquante et attend la fin d'un ou plusieurs processus fils. Cette méthode os.waitid()

  • accepte en entrée 4 arguments :

    • idtype peut prendre 3 valeurs constantes P_PID, P_PGID, P_ALL, ou P_PIDFD sur Linux, qui indiquent comment l'argument id doit être interprété, càd quel(s) sont le/les processus à attendre :

      Signal signification
      P_PID N'attendre que le processus fils indiqué
      P_PGID Attendre Tout le groupe Unix du processus fils
      P_ALL Attendre Tous les processus fils
      l'argument id sera alors ignoré
      P_PIDFD id désigne ici un Descripteur de Fichier
      qui fait référence au processus

    • id est le PID du processus qui doit être attendu.

    • infop le nom de la variable contenant le statut de retour
    • options précise une ou plusieurs constantes suivantes, séparées par un OR (| en Unix), et qui servent à indiquer quels sont les types de signaux à attendre pour mettre fin au processus fils :

    Type de Signal
    Plus d'infos sur
    $ man 3 waitid
    Signification : Quel signal attendre du fils?
    os.WEXITED Attend les processus fils qui ont terminé via un exit
    os.WSTOPPED Attend chaque processus fils ayant été stoppé après réception d'un signal
    et dont le statut :
    • ou bien n'a pas été rapporté depuis sa fin
    • ou bien n'a été rapporté que par des appels à waitid() avec un drapeau WNOWAIT
    os.WCONTINUED Attend chaque processus fils ayant repris (continué) et dont le statut :
    • ou bien n'a pas été rapporté depuis sa reprise
      après un stop du job control
    • ou bien n'a été rapporté que par des appels à waitid() avec un drapeau WNOWAIT
    Options
    Facultatives
    \(\,\)
    os.WNOHANG ne pas attendre si pas de statut.
    Renvoyer/Revenir instantanément
    os.WNOWAIT Empêche le processus renvoyé dans infop d'être en attente
    L'état du processus ne devrait pas être modifié
    Le processus pourra être de nouveau attendu
    après la fin de cet appel

    Exemple : option = os.WSTOPPED | os.WEXITED

  • Renvoie en sortie un objet modélisant une structure de données POSIX (posix.waitid_result), une sorte wrapper de tuple de plusieurs entiers, décrivant le statut du processus sous forme des plusieurs attributs suivants, dans cet ordre (accessibles via la syntaxe POO en Python) : si_pid, si_uid, si_signo, sig_status, si_code ou bien None si WNOHANG a été spécifiée et qu'aucun processus enfant n'est dans un état d'attente :

    Valeur
    du Statut
    Renvoyé
    Dans quelle situation?
    -1 En cas d'erreur
    et errno contient alors le numéro de l'erreur
    0 Au cas où waitid() soit en retour lié à
    un changement d'état de l'un de ses enfants
    />ou bien, pour tout processus identifié
    par idtype et id
    dans le cas où WNOHANG a été spécifié
    et que le statut n'est pas disponible

    Exp

    idtype = os.P_ALL
    id = pid
    option = os.WSTOPPED | os.WEXITED
    statut = os.waitid(idtype, id, option)
    print(statut.sig_pid)
    # print(statut[0])
    

Exemples de Signaux

Signal Signification
os.WIFEXITED(statut) Vrai si le processus fils
identifié par son statut de sortie
s'est terminé normalement
càd par l'utilisation de sys.exit()
ou os._exit(), par retour de main()
(par défaut)
os.WEXITSTATUS(statut) Fournit le statut de retour du
fils s'il s'est terminé correctement
A n'employer que si WIFEXITED() est Vrai
os.WIFSIGNALED Vrai si le fils s'est terminé
à cause d'un signal
os.WTERMSIG Fournit le n° du Signal ayant provoqué
la fin du processus
os.WIFSTOPPED Vrai si le processus est Stoppé
(si waitid avec WUNTRACED)
os.WSTOPSIG() Fournit le n° du signal ayant stoppé
le processus
A n'utiliser que si WIFSTOPPED est Vrai
  1. Exécuter le programme suivant sous le nom de fichier attendId.py (attention : PAS waitid.py sinon conflit avec la librairie standard), puis modifier-le de sorte que:

  2. un nouveau fils soit créé après if pid>0 : et

  3. de sorte que le père attende la fin d’exécution des 2 fils avant de se terminer

    # attendId.py (Sauvegarder sous ce nom)
    import os  
    newpid = os.fork() 
    
    if newpid>0 :  # dans le père
        # Spécifier idtype
        idtype = os.P_PID
    
        # Spécifier id
        id = newpid
    
        # Spécifier option
        option = os.WEXITED
    
        status = os.waitid(idtype, id, option)
    
        print("\n\nDans le parent--")
    
        print("Statut du fils:")
        print(status)
    
    else :
        print("Dans le premier enfant-") 
        print("Process ID:", os.getpid()) 
        print("Hello ! Geeks") 
        print("Exiting..")