|
|
8 years ago | |
|---|---|---|
| .. | ||
| src | 8 years ago | |
| tests | 8 years ago | |
| Cargo.toml | 8 years ago | |
| README.md | 8 years ago | |
Ziel dieser Aufgabe ist es, mittels des externen Crates nix einige systemnahe Funktionen zur Prozesserzeugung und -synchronisierung kennen zu lernen.
Über optionale Aufrufparameter werden die verschiedenen Verhaltensweisen Ihres Programms getrickert.
Um möglichst direkt die Systemfunktionen zu nutzen, stellt das nix Crate eine einheitliche Schnittstelle dar. Insbesondere der Umgang mit der Fehlerbehandlung ist unter Rust und der nix Crate um einiges komfortabler und sicherer. Dazu stellt die nix Crate über den Result Typ die Informationen über den aufgerufenen Systemcall zur Verfügung. Rust Datentypen werden in der nix Crate überall dort benutzt wo dies Sinn macht. So werden z.B. Slices als Standard benutzt, wenn Bereiche eines Puffers weitergegeben werden müssen. Dieser Standard soll nun wiederum in C++ im C++17 Standard einfließen.
In dieser Aufgabe interessieren wir uns vor allem für das nix Modul
nix::unistd und nix::sys::wait. Darüber hinaus werden auch weitere Rust
Standard-Library Methoden benutzt, sowie die externe Crate procinfo
eingebunden.
In Ihrer Lösung dürfen nur diese beiden externen Crates eingebunden werden.
Ihr Programm wird sich entsprechend der übergebenen Optionen unterschiedlich verhalten:
pub fn run_zombie()pub fn run_childs(start_pid: i32, arg:
&str) -> Result<(), String>Details zu den einzelnen Funktionen erhalten Sie bei den entsprechenden Aufgaben dazu.
[dependencies] in
Cargo.toml hinzu und benutzen in Ihrem Root Modul main.rs die
entsprechende extern Anweisung.pub fn run_zombie()Wird das Programm ohne einen Parameter aufgerufen, erstellen wir als ‘Belohnung’
für diesen ‘müden’ Programmaufruf einen Zombie! Dies geschieht im Modul
zombie/mod.rs über die dortige Funktion pub fn run_zombie(). Innerhalb
dieser Funktion behandeln Sie evtl. auftretende Fehler direkt mit
Programmabbruch. Dies gilt jedoch nur für diesen Aufgabenpunkt!
Was ist ein Zombieprozess?
“Wenn ein Prozess einen neuen Prozess startet (mittels Forking), wird der alte ‘Elternprozess’ und der neue ‘Kindprozess’ genannt. Wenn der Kindprozess beendet wird, kann der Elternprozess vom Betriebssystem erfragen, auf welche Art der Kindprozess beendet wurde: erfolgreich, mit Fehler, abgestürzt, abgebrochen, etc.
Um diese Abfrage zu ermöglichen, bleibt ein Prozess, selbst nachdem er beendet wurde, in der Prozesstabelle stehen, bis der Elternprozess diese Abfrage durchführt – egal ob diese Information gebraucht wird oder nicht. Bis dahin hat der Kindprozess den Zustand Zombie. In diesem Zustand belegt der Prozess selbst keinen Arbeitsspeicher mehr (bis auf den platzmäßig vernachlässigbaren Eintrag in der Prozesstabelle des Kernels) und verbraucht auch keine Rechenzeit, jedoch behält er seine PID, die (noch) nicht für andere Prozesse wiederverwendet werden kann.” (Quelle Wikipedia)
Die Funktion run_zombie() erstellen Sie im Modul zombie/mod.rs. Wenn Sie Ihr
Programm ohne Parameter aufrufen, erhalten Sie bei korrekter Zombie
‘Generierung’ folgende Ausgabe:
PID TTY STAT TIME COMMAND
27524 pts/1 Ss 0:00 -bash
27531 pts/1 S 0:00 zsh
27935 pts/1 S+ 0:00 cargo run
27936 pts/1 S+ 0:00 target/debug/task1
27962 pts/1 Z+ 0:00 [task1] \<defunct\>
27963 pts/1 R+ 0:00 ps t
Diese Ausgabe wird über das Programm ps t generiert, welches in Ihrem Programm dann geschickterweise aufgerufen wird, wenn Sie den im vorherigen Text beschriebenen Zustand durch Ihr Programm selbst hergestellt haben. Den Zombizustand bekommen Sie als Z dargestellt, siehe auch man ps.
Tipp: Um Ihr Programm einen Moment ‘schlafen’ zu lassen, stellt Ihnen Rust die Funktion thread::sleep zur Verfügung. Ein Prozess besteht aus mindestens einem Thread (Main-Thread). Und da Sie keine weiteren Threads erstellen, lässt diese Funktion Ihren Prozess (bestehend aus einem Main-Thread) schlafen.
Um ein anderes Programm aus Ihrem Programm heraus starten zu lassen stellen
Ihnen die nix Crate und die std Bibliothek verschiedene Funktionen bereit:
Module nix::unistd der nix Crate: die Funktion execv(), execve() und execvp()
Module std::process der Standardbibliothek: Machen Sie sich mit der Benutzung von Module std::process grob vertraut, insbesondere die Methoden des Typs Command:
Je nachdem für welche Bibliothek Sie sich entscheiden, experimentieren Sie zu Beginn ein wenig mit den Code Beispielen der Dokumentation.
Tipp: Das Command Interface der Standardbibliothek ist einfacher zu verwenden.
pub fn run_childs(start_pid: i32, arg: &str) -> Result<(), String>Wird ein Parameter (arg) beim Aufruf Ihres Programms mit angegeben,
spezifiziert dieser Parameter die Anzahl der zu erzeugenden Kindprozesse.
Wichtig dabei ist, dass alle zu erstellenden Kindprozesse voneinander abhängen.
Rufen Sie im letzten erstellten Kindprozess Ihre bereits erstellte pstree()
Funktion mit der übergebenen PID des 1. Elternprozesses (start_pid) auf, so
können Sie aufgrund der ausgegebenen Liste erkennen, ob alle Kindprozess
voneinander abhängen. Ihr pstree Modul stellen Sie in child/pstree.rs
bereit, da dieses nur im Modul child/mod.rs benutzt werden muss.
Die Pid aus dem nix Crate ist vom Typ
Pid, welches ein Alias ist. Lesen Sie dazu die Dokumentation des nix Crates und benutzen Sie eine geeignete Rust Funktion (einer Trait), um diesen Typ als i32 an Ihre Funktionrun_childs()weiterzugeben. Sie müssen in der pstree Funktion dazu nichts anpassen!
> ./target/debug/task1 4
...
task1(28207)---task1(28233)---task1(28234)---task1(28235)---task1(28236)
...
Implementieren Sie die Funktion pub fn run_childs(start_pid: i32, arg: &str) ->
Result<(), String> im Modul child/mod.rs. Alle dazu nötigen Hilfsfunktionen
werden entweder in child/mod.rs oder entsprechenden Modulen im child/
Verzeichnis zur Verfügung gestellt. Evtl. auftretende Fehler werden an das Root
Modul (main.rs) zurückgegeben und dort behandelt. Bei dieser Teilaufgabe
müssen alle auftretenden Fehler entsprechend an das Root-Modul zurückgegeben
werden. Treten Fehler auf, so darf die Fehlermeldung, generiert im Root-Modul,
dazu max. 1 Zeile lang sein und der Exit-Code des Programms muss ‘1’ sein.
Parsen Sie den übergebenen Parameter mit der parse() Funktion in einen u8
Typ! Es ist wichtig, dass Sie im weiteren die Anzahl der Kindprozesse über eine
u8 Variable steuern!
Jedes Child soll eine Ausgabe machen, sowie jeder Parent, wenn sich der Child beendet hat:
Eltern: I am <pid> and my child is <child>. After I waited for <waitstatuspid>, it sent me status <status>
> ./target/debug/task1 4
hello, I am child (pid:28233)
hello, I am child (pid:28234)
hello, I am child (pid:28235)
hello, I am child (pid:28236)
task1(28207)---task1(28233)---task1(28234)---task1(28235)---task1(28236)
I am 28235 and my child is 28236. After I waited for 28236, it sent me status 0
I am 28234 and my child is 28235. After I waited for 28235, it sent me status 0
I am 28233 and my child is 28234. After I waited for 28234, it sent me status 0
I am 28207 and my child is 28233. After I waited for 28233, it sent me status 0
Leerzeile wichtig nach der Ausgabe aller Kinder!
Für das tests/ Verzeichnis steht wieder eine output.bats Datei zur Verfügung. Ausserdem erstellen Sie bitte eigene Unit Tests in einer eigenen zu erstellenden unit_tests.rs Datei in Ihrem Crate Root Verzeichnis (src/).
Erstellen Sie für alle Module und Funktionen eine kurze aber aussagekräftige Dokumentation, und vergessen Sie nicht wichtige Passagen auch im Code zu kommentieren. Als Tutoren sollte es uns möglich sein, schnell Ihre genialen Lösungen nachvollziehen zu können.
Haben Sie die Aufgaben komplett bearbeitet, so sollten sich folgende Dateien in Ihrem HW (Homework) Verzeichnis befinden:
.
├── Cargo.lock
├── Cargo.toml
├── README.md
├── src
│ ├── child
│ │ ├── mod.rs
│ │ └── pstree.rs
│ ├── main.rs
│ └── zombie
│ └── mod.rs
└── tests
└── output.bats
4 directories, 8 files