This shows you the differences between two versions of the page.
sde2:laboratoare:05_rust [2023/04/03 14:02] cristiana.andrei created |
sde2:laboratoare:05_rust [2023/04/04 06:21] (current) cristiana.andrei [Objectifs] |
||
---|---|---|---|
Line 1: | Line 1: | ||
====== TP 05 - Communication Entre Processus ====== | ====== TP 05 - Communication Entre Processus ====== | ||
+ | |||
+ | <note warning> | ||
+ | Vous devez **accepter** le assignment d'ici et travailler avec ce repository: [[https://classroom.github.com/a/lhtkAUIt|Lab5]] | ||
+ | </note> | ||
+ | |||
===== Objectifs ===== | ===== Objectifs ===== | ||
Le but de ce TP est d'apprendre à utiliser | Le but de ce TP est d'apprendre à utiliser | ||
* La pipe | * La pipe | ||
- | * Utiliser les signaux | ||
- | |||
===== Pipe ===== | ===== Pipe ===== | ||
Une **pipe** est une forme de //redirection// (transfert de la sortie standard vers une autre destination) qui est utilisée dans Linux et d'autres systèmes d'exploitation de type Unix pour envoyer la sortie d'une commande/programme/processus à une autre commande/programme/processus pour un traitement ultérieur . | Une **pipe** est une forme de //redirection// (transfert de la sortie standard vers une autre destination) qui est utilisée dans Linux et d'autres systèmes d'exploitation de type Unix pour envoyer la sortie d'une commande/programme/processus à une autre commande/programme/processus pour un traitement ultérieur . | ||
+ | |||
+ | Le canal est un mécanisme de communication à sens unique entre deux processus. Dans la plupart des implémentations UNIX, un canal apparaît comme une zone mémoire d'une certaine taille dans l'espace du noyau. Les processus qui communiquent via un canal anonyme doivent avoir un certain degré de parenté; généralement, un processus qui crée un pipe sera alors appelé une fork, et le pipe sera utilisé pour la communication parent-enfant. Dans tous les cas, les processus qui communiquent via des canaux anonymes ne peuvent pas être créés par différents utilisateurs du système. | ||
Pipe est utilisé pour combiner deux commandes ou plus, et dans ce cas, la sortie d'une commande agit comme entrée d'une autre commande, et la sortie de cette commande peut servir d'entrée pour la commande suivante et ainsi de suite. Il peut également être visualisé comme une connexion temporaire entre deux ou plusieurs commandes/programmes/processus. | Pipe est utilisé pour combiner deux commandes ou plus, et dans ce cas, la sortie d'une commande agit comme entrée d'une autre commande, et la sortie de cette commande peut servir d'entrée pour la commande suivante et ainsi de suite. Il peut également être visualisé comme une connexion temporaire entre deux ou plusieurs commandes/programmes/processus. | ||
Line 16: | Line 21: | ||
</note> | </note> | ||
- | ===== Signaux en Rust ===== | + | <code c> |
- | Les processus tels que les applications en ligne de commande doivent **réagir** aux signaux envoyés par le système d'exploitation. L'exemple le plus courant est probablement Ctrl + C, le signal qui indique généralement à un processus de se terminer. Pour gérer les signaux dans les programmes Rust, vous devez considérer comment vous pouvez recevoir ces signaux ainsi que comment vous pouvez y réagir. | + | |
- | <note> | + | use nix::unistd::pipe; |
- | Un signal est une interruption logicielle dans le flux d'exécution de processus normal. | + | |
- | </note> | + | |
- | L'ensemble des types de signaux est fini; le système d'exploitation conserve, pour chaque processus, un tableau d'actions choisi par lui, pour chaque type de signal. À tout moment, ces actions sont bien déterminées. Au début du processus, la table d'actions est initialisée avec les valeurs par défaut. Le mode de traitement du signal n'est pas décidé lors de la réception du signal par le processus, mais il est automatiquement choisi dans le tableau. Les signaux sont synchrones / asynchrones avec le flux d'exécution de processus qui reçoit le signal si l'événement qui provoque l'envoi du signal est synchrone / asynchrone avec le flux d'exécution de processus. | + | fn main() { |
- | === Emission et réception de signaux === | + | let (fdw0, fdw1) = pipe().unwrap(); |
- | Lorsqu'un signal est généré, il passe dans un état en attente. Normalement, il reste dans cet état pendant très peu de temps et est ensuite envoyé au processus de destination. Cependant, si ce type de signal est actuellement verrouillé, il pourrait rester indéfiniment en état de veille jusqu'à ce que les signaux de ce type soient déverrouillés. Une fois ce type de signal déverrouillé, il sera envoyé immédiatement. | + | |
- | Lorsque le signal a été reçu, immédiatement ou tard, l'action spécifiée pour ce signal est exécutée. Pour certains signaux, tels que '' SIGKILL '' et '' SIGSTOP '', l'action est ** fixe ** (le processus est terminé), mais, pour la plupart des signaux, le programme peut choisir de: | + | |
- | * **ignore** le signal | + | |
- | * spécifier une fonction de type **handler** | + | |
- | * accepte ** l'action par défaut ** pour ce type de signal. | + | |
- | === Types de signaux standard === | + | } |
- | Cette section présente les noms des différents types de signaux standard et décrit le type d'événements qu'ils indiquent. | + | |
- | <note> Chaque nom de signal est une ** définition de macro ** qui représente, en fait, un entier positif (le numéro de ce type de signal).</note> | + | |
- | Un programme ne doit jamais faire d'hypothèses sur le code numérique d'un type particulier de signal, mais plutôt s'y référer toujours par son nom. En effet, un nombre pour un type de signal peut ** varier ** d'un système à l'autre, mais leurs noms sont standard. Pour la liste complète des signaux pris en charge par un système, vous pouvez exécuter sur la ligne de commande: | + | |
- | <code bash> | + | |
- | $ kill -l | + | |
- | + | ||
- | 1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL | + | |
- | 5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE | + | |
- | 9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2 | + | |
- | 13) SIGPIPE 14) SIGALRM 15) SIGTERM 17) SIGCHLD | + | |
- | 18) SIGCONT 19) SIGSTOP 20) SIGTSTP 21) SIGTTIN | + | |
- | 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ | + | |
- | 26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO | + | |
- | 30) SIGPWR 31) SIGSYS 33) SIGRTMIN 34) SIGRTMIN+1 | + | |
- | 35) SIGRTMIN+2 36) SIGRTMIN+3 37) SIGRTMIN+4 38) SIGRTMIN+5 | + | |
- | 39) SIGRTMIN+6 40) SIGRTMIN+7 41) SIGRTMIN+8 42) SIGRTMIN+9 | + | |
- | 43) SIGRTMIN+10 44) SIGRTMIN+11 45) SIGRTMIN+12 46) SIGRTMIN+13 | + | |
- | 47) SIGRTMIN+14 48) SIGRTMIN+15 49) SIGRTMAX-15 50) SIGRTMAX-14 | + | |
- | 51) SIGRTMAX-13 52) SIGRTMAX-12 53) SIGRTMAX-11 54) SIGRTMAX-10 | + | |
- | 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7 58) SIGRTMAX-6 | + | |
- | 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2 | + | |
- | 63) SIGRTMAX-1 64) SIGRTMAX | + | |
</code> | </code> | ||
- | Les noms de signaux sont définis dans l'en-tête '' signal.h '' sous Unix. En général, les signaux ont des rôles prédéfinis, mais ils peuvent être remplacés par le programmeur. | ||
- | Les plus connus sont les signaux suivants: | ||
- | * ''SIGINT'' - transmis lorsque la combinaison est enfoncée ''CTRL+C''; | ||
- | * ''SIGQUIT ''- transmis lorsque la combinaison est enfoncée ''CTRL+\''; | ||
- | * ''SIGSEGV'' - transmis lors de l'accès à un emplacement de mémoire invalide, etc. | ||
- | * ''SIGKILL'' - ** ne peut pas ** être ignoré ou remplacé. La transmission de ce signal a pour effet de mettre fin au processus, quel que soit le contexte. | ||
- | <note important> | + | Le tuple '' (fdw0, fdw1) '' contient après l'exécution de la fonction 2 descripteurs de fichiers: |
- | Pour gerer les signaux en Rust on utilise [[https://docs.rs/nix/latest/nix/sys/signal/fn.signal.html|nix::sys::signal::signal]]. | + | * '' fdw0 '', ouvert à la lecture; |
- | </note> | + | * '' fdw1 '', ouvert pour l'écriture; |
+ | La sortie de '' fdw1 '' est considérée comme une entrée pour '' fdw0 ''.. | ||
- | ===== Tuer des processus ===== | + | ** Remarques**: |
- | Si un processus Linux ne répond plus ou consomme trop de ressources, vous devrez peut-être le tuer. | + | * la lecture / écriture depuis / vers les canaux est atomique si vous ne lisez / écrivez pas plus de '' PIPE_BUF '' ((limite globale fixée par défaut sur Linux à 4096 octets)) octets. |
- | + | * la lecture / écriture depuis / vers les pipes se fait à l'aide des fonctions '' stdin().read_line() '' / '' stdout().write () ''. | |
- | La plupart des processus ont leurs propres méthodes d'arrêt. Malheureusement, les processus peuvent mal fonctionner et ne pas se laisser arrêter. Si un processus d'arrière-plan en cours d'exécution ne répond pas, il devient nécessaire d'utiliser une commande pour le tuer. | + | |
- | + | ||
- | En Rust, on utilise la fonction ''kill'' qui force le processus fils à se fermer. Si l'enfant est déjà sorti, une erreur InvalidInput est renvoyée. Cela équivaut à envoyer un **SIGKILL** sur les plates-formes Unix. | + | |
- | + | ||
- | Exemple: | + | |
- | <code c> | + | |
- | use std::process::Command; | + | |
- | + | ||
- | let mut command = Command::new("yes"); | + | |
- | if let Ok(mut child) = command.spawn() { | + | |
- | child.kill().expect("command wasn't running"); | + | |
- | } else { | + | |
- | println!("yes command didn't start"); | + | |
- | } | + | |
- | </code> | + | |
+ | La plupart des applications qui utilisent des pipes ferment dans chacun des processus l'extrémité du pipe ** inutilisé ** en communication unidirectionnelle. Si l'un des descripteurs est fermé, les règles s'appliquent: | ||
+ | * une lecture à partir d'un canal pour lequel le ** descripteur d'écriture ** a été fermé, après que toutes les données ont été lues, renverra '' 0 '', indiquant la fin du fichier. Le descripteur d'écriture peut être dupliqué afin que plusieurs processus puissent écrire dans le canal. Habituellement, dans le cas des canaux anonymes, il n'y a que deux processus, l'un qui écrit et l'autre qui lit, tandis que dans le cas des fichiers de canal nommé (FIFO), il peut y avoir plusieurs processus qui écrivent des données. | ||
+ | * une écriture dans un tube pour lequel le descripteur ** read ** a été fermé provoque la génération du signal '' SIGPIPE ''. Si le signal est capturé et revient de la routine de traitement, la fonction système '' écriture '' renvoie une erreur et la variable '' errno '' a la valeur '' EPIPE ''. | ||
+ | <note important> L'erreur ** la plus courante **, lorsque vous travaillez avec des pipes, vient de la négligence du fait que ''EOF'' n'est pas envoyé à travers les pipes (la lecture des pipes ne se termine pas) à moins que toutes les ** extrémités ne soient fermées**. ** TOUS ** les processus qui ont ouvert le descripteur de pipe (dans le cas d'une fork, assurez-vous de fermer les extrémités du pipe dans le processus parent).</note> | ||
===== Sugestions ===== | ===== Sugestions ===== | ||
Line 107: | Line 66: | ||
===== Bibliographie ===== | ===== Bibliographie ===== | ||
* pipe [[https://docs.rs/nix/latest/nix/unistd/fn.pipe.html]] | * pipe [[https://docs.rs/nix/latest/nix/unistd/fn.pipe.html]] | ||
- | * kill [[https://docs.rs/nix/latest/nix/sys/signal/fn.kill.html]] | ||
- | * signal [[https://docs.rs/nix/latest/nix/sys/signal/fn.signal.html]] | ||
===== Sujets ===== | ===== Sujets ===== | ||
- | - Écrivez un programme qui crée un tuyau et des bifurcations. Du parent, lisez à partir du clavier un message et envoyez-le par le canal à l'enfant. | + | - Écrivez un programme qui crée un pipe et un processus (processus enfant en utlisant [[https://docs.rs/nix/0.26.2/nix/unistd/fn.fork.html|fork]]). Du parent, lisez à partir du clavier un message et envoyez-le par le canal à l'enfant. |
- Inversez le message dans l'enfant et envoyez-le au parent. | - Inversez le message dans l'enfant et envoyez-le au parent. | ||
- | - Clonez le repository [[https://github.com/UPB-FILS-SdE2/Solutions/tree/main|Solutions]] et compilez ex2. Exécutez-le et utilisez la commande shell kill pour lui envoyer des signaux. | + | - Écrivez un programme qui crée un pipe et un processus (un processus enfant en utlisant [[https://docs.rs/nix/0.26.1/nix/unistd/fn.fork.html|fork]]). Le processus enfant redirige son ecran vers le pipe et exécute ls -l. Le processus parent lit la sortie de l'enfant à partir du pipe et l'affiche. |
- | - Modifiez-le pour qu'à chaque fois qu'il reçoit un signal, il imprime une lettre différente : a - SIGHUP, b - SIGINT, n - SIQUIT, s - SIGTRAP, espace - SIGFPE. Écrivez un autre programme en Rust qui lui envoie des signaux pour qu'il imprime ''bannanas bannanas''. Le deuxième programme reçoit le pid de destination à partir d'un argument de ligne de commande. | + | - Écrivez un programme qui crée un pipe et 2 processus (deux processus enfant en utlisant [[https://docs.rs/nix/0.26.1/nix/unistd/fn.fork.html|fork]] doix fois). Le premier enfant exécute ls -l et le deuxième enfant exécute grep src. Le parent connecte l'affichage du premier enfant au clavier du deuxième enfant avec un pipe. |
- | - Créez un programme qui fait fork et entre dans une boucle infinie (avec un sleep). Son enfant attend quelques secondes puis sort. Écrivez un message à l'écran lorsque l'enfant sort. Utilisez SIGCHLD. | + | |
- | + | ||
- | ===== Solutions ===== | + | |
- | [[https://github.com/UPB-FILS-SdE2/Solutions/tree/main/tp5|Solutions]] | + |