TP 07 - Processus 2

Liens vers des sections utiles

Vue d'ensemble des concepts

Un processus est un programme en cours d'exécution. Les processus sont l'unité primitive à travers laquelle le système d'exploitation alloue des ressources aux utilisateurs. Chaque processus a un espace d'adressage et un ou plusieurs threads. Plusieurs processus peuvent exécuter le même programme, mais deux processus sont complètement indépendants.

Les informations de processus sont conservées dans une structure appelée “Bloc de contrôle de processus” (PCB), une pour chaque processus du système. Parmi les informations les plus importantes contenues par PCB, on trouve:

  • PID - identifiant du processus
  • Espace d'adressage
  • registres généraux, PC (programme de comptage), SP (indicateur de pile)
  • ouvrir la table
    • liste des bloqués, ignorés ou en attente d'envoi au processus
    • Gestionnaires de signaux
  • informations sur les systèmes de fichiers (répertoire racine, répertoire actuel)

Lors du lancement d'un programme, un processus sera créé dans le système d'exploitation pour allouer les ressources nécessaires à l'exécution du programme. Chaque système d'exploitation fournit des appels système permettant de travailler avec des processus: création, fin, attente de fin. En même temps, il y a des appels à la duplication de descripteurs de ressources entre processus, ou à la fermeture de ces descripteurs.

Les processus peuvent avoir une organisation:

  • hierarchical - par exemple sous Linux - il existe une arborescence dans laquelle la racine est le processus init (pid = 1).
  • non hiérarchique - par exemple sous Windows.

Généralement, un processus s'exécute dans un environnement spécifié par un ensemble de variables d'environnement . Une variable d'environnement est une paire 'NOM = valeur'. Un processus peut vérifier ou définir la valeur d'une variable d'environnement via une série d'appels de bibliothèque ( Linux, Windows).

Les canaux (canaux de communication) sont des mécanismes primitifs de communication entre processus. Un canal peut contenir une quantité limitée de données. L'accès à ces données est de type FIFO (les données sont écrites à une extrémité du canal pour être lues à l'autre extrémité). Le système d'exploitation garantit la synchronisation entre les opérations de lecture et d'écriture aux deux extrémités ( Linux, Windows).

Il existe deux types de tuyaux:

  • pipes anonymous : ne peuvent être utilisés que par des processus associés (un processus parent et un ou deux enfants) car ils ne sont accessibles que par héritage. Ces canaux n'existent plus une fois que les processus ont terminé leur exécution.
  • Name pipes: ont un support physique - ils existent sous forme de fichiers de droits d'accès. Par conséquent, ils existeront indépendamment du processus qui les crée et peuvent être utilisés par des processus indépendants.

Processus sous Linux

Le lancement d'un programme implique les étapes suivantes:

  • Un nouveau processus “fork” est créé - le processus enfant dispose d'une copie des ressources du processus parent.
  • Si on souhaite remplacer l'image du processus enfant, on peut la modifier en appelant une fonction de la famille exec* .

Créer un processus Linux

Sous UNIX, un processus est créé à l’aide de l’appel système fork:

os.fork();

L'effet est la création d'un nouveau processus (le processus enfant), une copie du “fork” (le processus parent). Le processus enfant reçoit un nouvel identificateur de processus ( PID ) du système d'exploitation.

Cette fonction est appelée une fois et retourne (en cas de succès) deux fois:

  • Le parent retournera le pid du processus créé (enfant).
  • Dans le processus enfant, l'appel retournera 0.

Pour connaître le «PID» du processus actuel et du processus parent, appelez les fonctions ci-dessous.

La fonction getpid renvoie le PID du processus appelant:

os.getpid()

La fonction getppid renvoie le “PID” du processus parent du processus appelant:

os.getppid()

Remplacer une image de processus sous Linux

La famille de fonctions exec exécutera un nouveau programme, remplaçant l'image du processus en cours par celle d'un fichier (exécutable). Cela signifie:

  • L'espace adresse de processus sera remplacé par un nouveau créé spécifiquement pour l'exécution de fichier.
  • Le 'PC' (le compteur de programme), le 'SP' (l'indicateur de pile) et les registres généraux seront réinitialisés.
  • Les blocs de signaux ignorés et verrouillés sont réglés sur les valeurs par défaut, de même que les gestionnaires de signaux.
  • PID et les descripteurs de fichier qui n'ont pas activé l'indicateur CLOSE_ON_EXEC restent inchangés (par défaut, CLOSE_ON_EXEC n'est pas défini).
os.execl(path, arg1, arg2, ...); 
os.execv(path, args);
os.execlp(file, arg1, arg2, ...);

Exemple d'utilisation des fonctions ci-dessus:

os.execl("/bin/ls", "ls", "-la")
 
args = ["ls", "-la"]
os.execv("/bin/ls", args)
 
os.execlp("ls", "ls", "-la")

Le premier argument est le nom du programme. Le dernier argument de la liste de paramètres doit être NULL, que la liste soit sous la forme d'un vecteur (execv *) ou en tant qu'argument variable (execl *).

execl et execv ne recherchent pas le programme donné en tant que paramètre dans PATH , il doit donc être accompagné du chemin complet. Les versions execlp et execvp recherchent le programme dans PATH .

Toutes les fonctions exec * sont implémentées par l'appel système execve.

En attente de terminer un processus sous Linux

La famille de fonctions wait suspend l'exécution du processus appelant jusqu'à ce que le ou les processus spécifiés dans les arguments se soient terminés ou aient été arrêtés (“SIGSTOP”).

os.waitpid(pid, options)

L’état du processus peut être trouvé en examinant status avec des macrodefines telles que WEXITSTATUS, qui renvoie le code d'erreur avec lequel le processus attendu a été achevé, en évaluant le plus insignifiant 8 bits.

Il existe une version simplifiée qui attend la fin d’un processus enfant. Les séquences de code suivantes sont équivalentes:

(pid, status, info) = wait3(0)                   |  status = waitpid(-1,0)

Terminer un processus Linux

Pour terminer le processus en cours, Linux fournit l'appel système sys.exit.

import sys
sys.exit(status)

Apeluri exit

Apeluri exit

Dans un programme Python , il existe 2 manières d'appeler cet appel système:

1. Appeler sys.exit (recommandé pour la plupart des cas ):

sys.exit(status)

2. Appeler os._exit de la bibliothèque standard os (recommandé dasn des processus enfant, apres un appel os.fork()):

os._exit(status)

Un processus dont le parent est terminé s'appelle processus orphelin . Ce processus est automatiquement adopté par le processus init , mais il porte toujours le nom orphelin car le processus qui l'a créé à l'origine n'existe plus.

Un processus terminé dont le parent n'a pas encore lu le statut de la terminaison est appelé processus zombie . Le processus entre dans un état final et les informations continuent d'exister dans la table des processus afin de donner au parent la possibilité de vérifier le code avec lequel le processus s'est terminé. Lorsque le parent appelle l'attente, les informations sur le processus disparaissent. Tout processus enfant passera par le statut de zombie à la fin.

Pour terminer un autre processus dans le système, un signal sera envoyé à ce processus via l'appel système kill. Plus de détails sur 'tuer' et les signaux dans signal lab.

Exemple (mon_système)

 
import os
 
def my_system(command):
    try:
        pid = os.fork()
	if pid == -1:
	    # error forking
	    return os.EXIT_FAILURE
	elif pid == 0:
	    # child process
	    os.execvp(command, args)
	    # only if exec failed */
	    os._exit(os.EXIT_FAILURE)
	else:
	    # parent process
	    pass
 
        #only parent process gets here
	status = os.waitpid(pid, 0)
	if (os.WIFEXITED(status)):
	    print("Child {} terminated normally, with code {}".format(pid, os.WEXITSTATUS(status)))
	    return status
    except Exception as e:
    	print ("Error: {}".format (e))
 
my_system("ls")

Copier les descripteurs de fichier

dup duplique le descripteur de fichier oldfd et renvoie le nouveau descripteur de fichier, ou -1 en cas d'erreur:

os.dup(oldfd)

dup2 duplique le descripteur de fichier oldfd dans le descripteur de fichier 'newfd'; si newfd existe, il sera fermé en premier. Retourne le nouveau descripteur de fichier, ou -1 en cas d'erreur:

os.dup2(oldfd, newfd)

Les descripteurs de fichiers sont en réalité des indices dans le tableau de fichiers ouverts. Le tableau est rempli par des structures avec des informations sur les fichiers. La duplication d'un descripteur de fichier signifie la duplication de l'entrée dans la table de fichiers ouverte (c'est-à-dire que 2 pointeurs situés à des positions différentes de la table pointeront sur la même structure de fichiers associée au fichier). Pour cette raison, toutes les informations associées à un fichier (verrou, curseur, drapeau) sont partagées par les deux descripteurs de fichier. Cela signifie que les opérations qui modifient ces informations sur l'un des descripteurs de fichier (par exemple, “lseek”) sont également visibles pour l'autre fichier de descripteur (dupliqué).

L'indicateur os.CLOSE_ON_EXEC n'est pas partagé (cet indicateur n'est pas conservé dans la structure ci-dessus).

Héritage des descripteurs de fichier après les opérations fork / exec

Les descripteurs de fichier du processus parent sont hérités du processus enfant après l'appel 'fork'. Après un appel “exec”, les descripteurs de fichier sont conservés, à l'exception de ceux pui possedentl'indicateur “CLOSE_ON_EXEC ”.

Variables d'environnement sous Linux

Dans un programme, il est possible d'accéder aux variables d'environnement en accedant la structure os.environ qui contient toutes les variables sous la forme <clé, valeur>, ou la clé représente le nom de la variable.

getenv renvoie la valeur de la variable d'environnement nommée name ou une valeur défaut s'il n'y a pas de variable d'environnement avec ce nom. Si la valeur prédéfinie n'est pas précisée, la fonction va retoruner None:

os.getenv(key, defaultValue)

Pour attribuer une valeur a une variable d'environnement, on utilise la structure environ:

os.environ['API_USER'] = 'username'

unsetenv supprime la variable nommée name de l'environnement:

os.unsetenv(name)

Exercices

Pour résoudre le labo, veuillez cloner repository. Si vous l'avez déjà clonné, veuillez lancer git pull .

Pour résoudre les exercices, utilisez la machine virtuelle avec l'interface graphique téléchargée dans le TP précédent.

Exercice 1 - Système (1p)

Allez dans le répertoire 1-system . Le programme my_system.py exécute une commande envoyée en tant que paramètre à l'aide de la fonction de bibliothèque system. Le fonctionnement de system est le suivant:

  • créer un nouveau processus avec fork
  • Le processus enfant s'exécute en utilisant execve, le programme sh avec les arguments -c commande, pendant que le processus parent attend la fin de l'enfant.

Lancez le programme en donnant une commande comme paramètre.

  • Exemple:
     python3 my_system.py pwd 

Comment pouvez-vous envoyer plus de paramètres à une commande? (par exemple: ls -la )

Pour voir le nombre d'appels de système execve sont réalisés, exécutez:

 strace -e execve,clone -ff -o output ./my_system ls 
  • Attention! Il n'y a pas de virgule dans l'argument execve,clone
  • L'argument -ff accompagné de -o output génère un fichier de sortie pour chaque processus.
    • Voir la page de manuel strace

Consultez la section Remplacement d’une image de processus sous Linux et du execve.

Remplacez le système par execlp . La fonction reçoit comme parametre une commande et une liste d'arguments (séparés par virgule). Suivez la ligne avec TODO 1.

Exercice 2 - Paramètres (2p)

Allez dans le répertoire 2-parameters .

Exécutez le programme parameters.py en utilisant la commande 'python3 parameters.py'. Qu'est-ce que le programme parameters.py fait ?

Résolvez l'exercice dans le fichier program.c.

2a. système (1p)

Utilisez la fonction system pour exécuter le programme paramètres avec certains paramètres.

Pour exécuter un programme à partir du répertoire en cours, préfixez le programme avec python3

2b. execlp (1p)

Remplacez la fonction system par execlp . Pourquoi le texte n'est plus affiché par print après execl ?

Suivez les lignes avec TODO 2.

Exercice 3 - Exécuter (4p)

Allez dans le répertoire 3-run .

3a - fork, exec (1p)

Utilisez les fonctions fork et execl pour exécuter la commande ls -l dans run.py.

Suivez les lignes avec TODO 1.

3b - waitpid (1p)

Assurez-vous que le texte “ls a été exécuté” apparaît après la fin du programme ls . (Indice: waitpid)

Suivez les lignes avec TODO 2.

3c - statut de sortie (1p)

Déplacez et modifiez la ligne avec TODO 3 afin que le code de sortie de ls soit affiché. (Indice: WEXITSTATUS)

3d - waitpid (1p)

Exécutez le programme exitcode.py et affichez le code de sortie. Modifiez le programme exitcode.py afin qu'il renvoie un autre code de sortie.

Suivez les lignes avec TODO 4.

Exercice 4 - orphelin (0.5p)

Allez dans le répertoire 4-orphan et inspectez la source orphan.py .

Donnez des dorits d'exécution sur le fichier ( chmod u+x ) et exécutez le programme a l'aide de la commande:

 ./orphan.py 

Si vous ne pouvez pas exécuter le fichier orphan.py, lancez dans le terminal la commande which python3 et remplacez la chemin de la premiere ligne du fichier par celle affichée dans le terminal.

Ouvrez un autre terminal et lancez la commande:

 watch -d '(ps -al | grep -e orphan -e PID)' 

Notez que pour le processus indiqué par l'exécutable 'orphelin' (colonne CMD), le pid du processus parent (colonne 'PPID') devient 1 car le processus est adopté par init à la fin du processus. son parent. Pourquoi y a-t-il deux processus orphelins?

Exercice 5 - zombie (0.5p)

Allez dans le répertoire 5-zombie et inspectez la source zombie.py .

Donnez des droits d'exécution sur le fichier ( chmod u+x ) puis exécutez-le en utilisant la commande suivante:

 ./zombie.py 

Si vous ne pouvez pas exécuter le fichier zombie.py, lancez dans le terminal la commande which python3 et remplacez la chemin de la premiere ligne du fichier par celle affichée dans le terminal.

Ouvrez un autre terminal et lancez la commande:

 watch -d '(ps -al | grep -e orphan -e PID)' 

Notez que pour le processus indiqué par l'exécutable zombie, la colonne 'CMD' devient 'zombie' '<obsolète>. Qu'est-ce qui se passe vraiment?

Modifiez le fichier zombie.py afin que le processus ne devienne pas un zombie (indice: waitpid).

Suivez les lignes avec TODO 1.

Exercice 6 - Tiny-Shell (3p)

Allez dans le répertoire 6-tiny .

Les points suivants sont ont comme but l'implémentation d'un shell minimal prenant en charge l'exécution d'une seule commande externe avec plusieurs arguments et redirections. Le shell doit fournir du support pour l'utilisation et la définition des variables environnementales.

Note: Pour quitter le petit shell, utilisez exit ou CTRL + D .

6a. Exécution d'une commande simple (1p)

Créez un nouveau processus pour exécuter une commande simple.

La fonction simple_cmd reçoit comme argument un vecteur de chaînes contenant la commande et ses paramètres.

Voir l'exemple mon_système et suivez les commentaires avec TODO 1 dans le code. Pour tester, vous pouvez utiliser les commandes:

 ./tiny
> pwd
> ls -al
> exit 

6b. Ajouter du support pour la définition et le développement des variables d’environnement (1p)

Vous devez remplir les fonctions 'set_var' et 'expand'; ils sont déjà appelées lorsque la ligne de commande est analysée. Les erreurs doivent être vérifiées dans ces fonctions.

  • Suivre les commentaires avec TODO 2 dans le code.
  • Lisez la variable d'environnement Variables d'environnement Linux.
  • Pour tester, vous pouvez utiliser les commandes suivantes:
     ./tiny
    > echo $HOME
    > name=Makefile 
    > echo $name  

6c. Rediriger la sortie standard (1p)

Remplissez le champ do_redirect pour que tiny-shell prenne en charge la redirection de la sortie d'une commande (stdout) dans un fichier.

Si le fichier spécifié par filename n'existe pas, il sera créé. S'il existe, il doit être tronqué.

Lisez la section Copie de descripteurs de fichiers et suivez les commentaires avec le mot “TODO 3” dans le code. Pour tester, vous pouvez utiliser les commandes:

 ./tiny 
> ls -al > out
> cat out
> pwd > out
> cat out

Solutions

Ressources utiles

sde2/laboratoare/04.txt · Last modified: 2021/04/20 22:58 by ioana_maria.culic
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0