This shows you the differences between two versions of the page.
sde2:laboratoare:03_rust [2023/03/19 18:34] cristiana.andrei |
sde2:laboratoare:03_rust [2023/03/20 23:45] (current) cristiana.andrei |
||
---|---|---|---|
Line 1: | Line 1: | ||
- | ====== TP 04 - Processus et le type Box ====== | + | ====== TP 03 - Fichiers et descripterus de fichiers ====== |
+ | |||
+ | ===== Assignment ===== | ||
+ | <note warning> | ||
+ | Vous devez **accepter** le assignment d'ici et travailler avec ce repository: [[https://classroom.github.com/a/7L73IFBY|Lab3]] | ||
+ | </note> | ||
===== Objectifs ===== | ===== Objectifs ===== | ||
Le but de ce TP est d'apprendre à utiliser | Le but de ce TP est d'apprendre à utiliser | ||
- | * Le type //Box// | ||
* Utiliser des descripterus de fichers | * Utiliser des descripterus de fichers | ||
- | * Utiliser dup2 | + | * Utiliser dup2 |
- | * Utiliser fork et exec | + | |
- | ===== Le type Box ===== | + | ===== Linux Standard Streams ===== |
+ | Sous Linux, ''stdin'' est le flux d'entrée standard. Cela accepte le texte comme entrée. La sortie texte de la commande au shell est transmise via le flux ''stdout'' (sortie standard). Les messages d'erreur de la commande sont envoyés via le flux ''stderr'' (erreur standard). | ||
- | === Pointeurs === | + | Vous pouvez donc voir qu'il existe deux flux de sortie, ''stdout'' et ''stderr'', et un flux d'entrée, ''stdin''. Étant donné que les messages d'erreur et la sortie normale ont chacun leur propre conduit pour les acheminer vers la fenêtre du terminal, ils peuvent être traités indépendamment les uns des autres. |
- | Un **pointeur** est un concept général pour une variable qui contient une adresse en mémoire. Cette adresse fait référence à, ou "pointe vers", d'autres données. Le type de pointeur le plus courant dans Rust est une **référence**. | + | |
- | Les références sont indiquées par le symbole **&** et empruntent la valeur vers laquelle elles pointent. Ils n'ont pas de capacités spéciales autres que la référence aux données et n'ont pas de surcharge. | + | Les flux sous Linux, comme presque tout le reste, sont traités comme s'ils étaient des **fichiers**. Vous pouvez lire du texte à partir d'un fichier et vous pouvez écrire du texte dans un fichier. Ces deux actions impliquent un flux de données. Ainsi, le concept de traitement d'un flux de données en tant que fichier n'est pas si exagéré. |
- | + | ||
- | Les **smart pointers**, d'autre part, sont des structures de données qui agissent comme un pointeur mais qui ont également des métadonnées et des capacités supplémentaires. Le concept de smart pointer n'est pas propre à Rust : les pointeurs intelligents sont originaires de C++ et existent également dans d'autres langages. | + | |
<note> | <note> | ||
- | Rust, avec son concept de propriété et d'emprunt, a une différence supplémentaire entre les références et les pointeurs intelligents : alors que les références **n'empruntent que des données**, dans de nombreux cas, les smart pointers **possèdent** les données vers lesquelles ils pointent. | + | Chaque fichier associé à un processus se voit attribuer un **numéro unique** pour l'identifier. C'est ce qu'on appelle le **descripteur de fichier**. Chaque fois qu'une action doit être effectuée sur un fichier, le descripteur de fichier est utilisé pour identifier le fichier. |
</note> | </note> | ||
- | === Box<T> === | + | Ces valeurs sont toujours utilisées pour ''stdin'', ''stdout'' et ''stderr'' : |
- | Le smart pointer le plus simple est une **box**, dont le type est écrit Box<T>. Les boxes vous permettent de stocker des données sur le tas plutôt que sur la **pile**. Ce qui reste sur la pile est le pointeur vers les données du **tas**. | + | * 0: stdin |
- | + | * 1: stdout | |
- | Les boxes n'ont pas de surcharge de performances, à part le stockage de leurs données sur le tas plutôt que sur la pile. Mais ils n'ont pas beaucoup de capacités supplémentaires non plus. Vous les utiliserez le plus souvent dans ces situations : | + | * 2: stderr |
- | * Lorsque vous avez un type dont la taille ne peut pas être connue au moment de la compilation et que vous souhaitez utiliser une valeur de ce type dans un contexte qui nécessite une taille exacte | + | ===== Les appels système dup et dup2 ===== |
- | * Lorsque vous avez une grande quantité de données et que vous souhaitez transférer la propriété, mais assurez-vous que les données ne seront pas copiées lorsque vous le ferez | + | |
- | * Lorsque vous voulez posséder une valeur et que vous vous souciez seulement qu'il s'agisse d'un type qui implémente un trait particulier plutôt que d'être d'un type spécifique | + | |
- | + | ||
- | <code c> | + | |
- | fn main() { | + | |
- | let b = Box::new(5); | + | |
- | println!("b = {}", b); | + | |
- | } | + | |
- | </code> | + | |
- | + | ||
- | Nous définissons la variable b comme ayant la valeur d'une Box qui pointe vers la valeur 5, qui est allouée sur le tas. Ce programme imprimera b = 5; dans ce cas, nous pouvons accéder aux données dans la boîte de la même manière que nous le ferions si ces données étaient sur la pile. Comme toute valeur possédée, lorsqu'une boîte sort de la portée, comme le fait b à la fin de main, elle sera désallouée. La désallocation se produit à la fois pour la boîte (stockée sur la pile) et les données vers lesquelles elle pointe (stockées sur le tas). | + | |
- | + | ||
- | ===== Processus ===== | + | |
- | + | ||
- | Lancer une application signifie allouer des ressources système (processeur, mémoire, périphériques d'entrée / sortie) pour exécuter l'application. Une application qui s'exécute, c'est-à-dire utilise des ressources système pour exécuter du code et traiter des données, est appelée un processus. Lorsque nous démarrons une application, un ** processus ** est créé; lorsque nous arrêtons l'application, ou lorsqu'elle termine son exécution, nous entendons la fin de l'exécution du processus. | + | |
- | + | ||
- | Le processus démarre à partir d'un fichier exécutable contenant le code de l'application (instructions) et les données. Le fichier exécutable est également appelé ** image du processus **. Le fichier exécutable est un programme. Nous disons que le processus est un programme en cours d'exécution. | + | |
- | + | ||
- | == Identifier un processus == | + | |
- | + | ||
- | ''PID'' (// Process Id //) est l'attribut essentiel du processus, un index qui identifie le processus au niveau du système. Un processus est identifié par PID et non par nom d'exécutable (CMD). Nous pouvons créer plusieurs processus à partir du même exécutable, chaque processus ayant son propre PID. | + | |
- | + | ||
- | ==== Fork ==== | + | |
- | La fonction **fork()** crée un nouveau processus enfant dupliquant le processus parent. Après avoir appelé l'appel système fork (avec succès), deux processus seront créés qui sont identiques à l'exception de leur pid et de la valeur de retour de cette fonction. | + | |
- | + | ||
- | <code c> | + | |
- | use nix::{sys::wait::waitpid,unistd::{fork, ForkResult, write}}; | + | |
- | + | ||
- | match unsafe{fork()} { | + | |
- | Ok(ForkResult::Parent { child, .. }) => { | + | |
- | println!("Continuing execution in parent process, new child has pid: {}", child); | + | |
- | waitpid(child, None).unwrap(); | + | |
- | } | + | |
- | Ok(ForkResult::Child) => { | + | |
- | // Unsafe to use `println!` (or `unwrap`) here. See Safety. | + | |
- | write(libc::STDOUT_FILENO, "I'm a new child process\n".as_bytes()).ok(); | + | |
- | unsafe { libc::_exit(0) }; | + | |
- | } | + | |
- | Err(_) => println!("Fork failed"), | + | |
- | } | + | |
- | </code> | + | |
- | + | ||
- | ===== Fonctions Unix ====== | + | |
- | ==== Les appels système dup et dup2 ==== | + | |
=== dup === | === dup === | ||
Line 84: | Line 41: | ||
</note> | </note> | ||
- | ==== execvp() ==== | + | === Redirections === |
- | La fonction execvp() **remplace** l'image de processus actuelle par une nouvelle image de processus spécifiée par file. La nouvelle image est construite à partir d'un **fichier exécutable** standard appelé le nouveau fichier d'image de processus. Aucun retour n'est effectué car l'image de processus appelante est remplacée par la nouvelle image de processus. | + | Quand vous voulez rediriger stdout vers un fichier vous pouvez le faire en ouvrant un fichier à l'aide de l'appel système **open()**, puis en dupliquant ce descripteur de fichier sur //stdout// en utilisant **dup2()**, de sorte que la sortie standard soit maintenant représentée par le fichier ouvert et non par la console. |
- | + | ||
- | <note> | + | |
- | Ici, vous pouvez lire comment [[https://docs.rs/nix/0.23.1/nix/unistd/fn.execvp.html|execvp]] est utilisé dans la Rust. | + | |
- | </note> | + | |
- | + | ||
- | ==== waitpid() ==== | + | |
- | L'appel système waitpid() **suspend l'exécution** du processus appelant jusqu'à ce qu'un enfant spécifié par l'argument pid ait changé d'état. Par défaut, waitpid() attend uniquement les enfants terminés, mais ce comportement est modifiable via l'argument options, comme décrit ci-dessous. | + | |
- | + | ||
- | <note> | + | |
- | Details sur [[https://docs.rs/nix/0.23.1/nix/sys/wait/fn.waitpid.html|waitpid]] en Rust. | + | |
- | </note> | + | |
- | + | ||
- | **Une liste d'autres fonctions Unix peut être trouvée dans la section Bibliographie**. | + | |
===== Bibliographie ===== | ===== Bibliographie ===== | ||
- | * Understanding ownership [[https://doc.rust-lang.org/book/ch04-00-understanding-ownership.html|en]], [[https://jimskapt.github.io/rust-book-fr/ch04-00-understanding-ownership.html|fr]]- | ||
- | * Using Box<T> to Point to Data on the Heap [[https://doc.rust-lang.org/book/ch15-01-box.html|en]], [[https://jimskapt.github.io/rust-book-fr/ch15-01-box.html|fr]] | ||
- | * fork [[https://docs.rs/nix/0.23.1/nix/unistd/fn.fork.html]] | ||
- | * Unix functions [[https://docs.rs/nix/0.23.1/nix/unistd/index.html]] | ||
* OpenOptions [[https://doc.rust-lang.org/std/fs/struct.OpenOptions.html]] | * OpenOptions [[https://doc.rust-lang.org/std/fs/struct.OpenOptions.html]] | ||
- | + | * Stdin https://doc.rust-lang.org/std/io/fn.stdin.html | |
- | ===== Sugestions ===== | + | * Stdout https://doc.rust-lang.org/std/io/fn.stdout.html |
- | <note> | + | * dup et dup2 https://docs.rs/nix/0.23.1/nix/unistd/fn.dup.html https://docs.rs/nix/0.23.1/nix/unistd/fn.dup2.html |
- | Transformer un String en CString peut être fait en utilisant | + | |
- | + | ||
- | <code rust> | + | |
- | CString::new("ls").unwrap() | + | |
- | </code> | + | |
- | </note> | + | |
- | + | ||
- | <note> | + | |
- | <code rust> | + | |
- | // utilisé pour trouver le descripteur de fichier | + | |
- | use std::os::unix::io::AsRawFd; | + | |
- | </code> | + | |
- | </note> | + | |
===== Sujets ===== | ===== Sujets ===== | ||
- | - Écrivez un programme qui alloue dynamiquement un String à l'aide de Box. Lisez un texte au clavier et ajoutez-le aux String allouée. Imprimez le String. | + | - Lisez votre nom à partir du clavier en utilisant [[https://doc.rust-lang.org/std/io/fn.stdin.html|stdin]]. Imprimer le nom en utilisant [[https://doc.rust-lang.org/std/io/fn.stdout.html|stdout]].//N'utilisez pas le println!// |
- | - Créez un programme qui utilise fork pour créer un processus enfant. | + | - Imprimer le contenu d'un fichier reçu en tant qu'argument de ligne de commande sur [[https://doc.rust-lang.org/std/io/fn.stdout.html|stdout]]. //N'utilisez pas le println!// |
- | - dans le processus parent, imprimez son PID (des parents) et le PID de l'enfant. Dans le processus enfant, imprimez le PID du parent et son PID. | + | - Lisez 3 chiffres sur le clavier en utilisant //stdin//. Écrivez le maximum de ces 3 nombres dans le fichier maximum.txt. |
- | - Dans le processus enfant, attendez quelques secondes et imprimez ensuite un message. | + | - Copiez le contenu d'un fichier donné comme argument depuis la ligne de commande dans un fichier nommé copy.txt |
- | - Dans le processus parent, affichez "En attente de la sortie du processus enfant", attendez qu'il se termine (waitpid) et imprimez un message avant de quitter. | + | - Ouvrez 2 instances du même fichier (ouvrez le fichier 2 fois). Lisez 10 octets de chaque instance et imprimez-les. Ouvrez une nouvelle instance du fichier et utilisez **dup()** pour faire une copie du descripteur de fichier de cette nouvelle instance. Lisez à partir des deux descripteurs de fichiers et voyez les différences entre la première situation et celle-ci. |
- | - Dans le processus enfant, exécutez la commande "ls -l". Utilisez execvp. Le premier argument doit être identique à la commande. | + | - Écrivez la date d'aujourd'hui par **redirection** de stdout dans un fichier appelé output.txt en utilisant la fonction [[https://doc.rust-lang.org/std/io/fn.stdout.html|write_all]]. |
- | - Redirigez la sortie du processus enfant (fd 1) vers un fichier appelé output.txt. Ouvrez le fichier en écriture et utilisez dup2. | + | - Lisez 5 noms de filles à partir du clavier en utilisant [[https://doc.rust-lang.org/std/io/fn.stdin.html|stdin]] et ecrivez-les dans un fichier appelee //names.txt// en utilisant stdout. Executez encore une fois le programme et Lisez 5 noms de garçons à partir du clavier. Il faut **ajouter** les noms de garçons aux noms des filles deja existantes dans //names.txt//, pas d'écraser le fichier! //Hint: utilisez [[https://doc.rust-lang.org/std/fs/struct.OpenOptions.html|OpenOptions]] pour ajouter au fichier // |
- | - N'écrasez pas le fichier output.txt, ajoutez-y des données (fs :: OpenOptions) | + |