Grundlagen: Betriebssysteme und Systemsoftware - GBS Tutoring ...
←
→
Transkription von Seiteninhalten
Wenn Ihr Browser die Seite nicht korrekt rendert, bitte, lesen Sie den Inhalt der Seite unten
Lehrstuhl für Connected Mobility Fakultät für Informatik Technische Universität München Grundlagen: Betriebssysteme und Systemsoftware IN0009, WiSe 2021/22 Hausaufgabe 4 12.01.2022 13:00 Uhr über Artemis (https://artemis.ase.in.tum.de) Hinweis: Obgleich die Abgabe für Hausaufgabe 4 erst am 12.01.2022 um 13:00 Uhr fällig ist, werden wir ab dem 23.12.2021 keine Fragen dazu auf Zulip oder über sonstige Kontaktmöglichkeiten mehr beantworten! Es wird am 23.11.2021 auch keine Programmierfragestunde geben. Falls es zu Problemen mit Artemis oder der Rechnerhalle über Weihnachten kommt können wir nicht garantieren, dass diese direkt behoben werden. Auch die GBS-Übungsleitung möchte sich über Weihnachten frei nehmen. Die Aufgaben Buddy-Algorithmus CSV und Clock-Algorithmus WEB sollen der Überprüfung dienen, ob sie die beiden Algorithmen verstanden haben, bevor Sie diese implementieren. Die beiden Aufgaben geben keine Punkte für den Notenbonus. Aufgabe 1 Completely Fair Scheduling mit Red-Black-Tree (G) Der Linux Kernel setzt als Schedulingverfahren Completely Fair Scheduling (CFS) ein. Dabei werden Prozesse anhand ihrer schon zugeteilten Rechenzeit ausgewählt. Der Prozess mit der bisher kürzesten Rechenzeit wird dabei als nächstes ausgewählt und vom Dispatcher an die CPU gebunden. Scheduling sollte einen möglichst kleinen Overhead mit sich bringen, um kaum kostbare Rechenzeit zu verschwenden. Die aufwendigste Operation bei CFS ist das Finden des Prozesses mit der kleinsten Rechenzeit. Um den Aufwand zu reduzieren, werden die Prozesse in einer Datenstruktur gespeichert, welche die Prozesse ordnet. Denkbar wäre eine sortierte Liste, aber dabei ist das Einfügen von neuen Elementen zu aufwendig (O(n)). Eine andere Datenstruktur sind Bäume. Sie haben in einer der vergangenen Hausaufgaben den unbalancierten Binärbaum kennengelernt. Durchschnitt- lich werden bei diesem zum Suchen, Einfügen und Löschen O(log(n)) Operationen benötigt. Im Worst-Case hingegen O(n). Dieser Fall tritt auf, wenn bereits vorsortierte Werte in den Baum eingefügt werden. In diesem Fall entartet der Binärbaum zur verketteten Liste. Um dies zu verhindern, werden selbstbalancierende Bäume eingesetzt, die ihre Struktur selbst anpassen, um eine möglichst gleichbleibende hohe Performanz zu erzielen. Selbstbalancierende Bäume gehören zu den wichtigsten Datenstrukturen in der Informatik. Es gibt viele verschiedene Arten. Dabei sind insbesondere AVL und Red-Black-Trees (Rot-Schwarz-Bäume) zu nennen. CFS nutzt einen RB-Tree als Datenstruktur. Ein weiterer populärer Einsatzort von RB-Trees ist die C++ Standardbibliothek, die damit Datenstrukturen wie std::set umsetzt. In den folgenden Teilaufgaben werden Sie einen RB-Tree selber anhand der vorgegebenen Funktionsprototypen entwickeln und ihn für eine vereinfachte Version von CFS einsetzen. • Implementieren Sie die Funktion newNode. Diese erstellt dynamisch einen neuen Knoten vom Typ struct node (siehe unten) und initialisiert den Wert val mit dem übergebenen Wert. Alle Pointer sollen auf NULL gesetzt werden, und die Farbe des Knotens muss auf rot (vgl. Enumeration Color) gesetzt werden. Abschließend wird ein Pointer auf den neu erstellten Knoten zurückgegeben. struct node { int val ; bool color ; struct node * left ; struct node * right ; struct node * parent ; }; Noch Fragen? Dann schaut doch mal im Zulip-Stream vorbei: https://zulip.in.tum.de All Rights Reserved, Do Not Distribute! 1
Lehrstuhl für Connected Mobility Fakultät für Informatik Technische Universität München A rotateLeft B rotateRight B 3 1 A 1 2 2 3 Abbildung 1: Operationen rotateLeft und rotateRight Der Datentyp bool kann an dieser Stelle nur verwendet werden, wenn stdbool.h eingebunden wurde. Vor C99 gab es keinen bool Datentyp in C. Seit C99 gibt es den Typ _Bool. Um diesen angenehmer nutzen zu können, wird in stdbool.h folgende Definition durchgeführt: # define bool _Bool Des Weiteren sind true und false keine Keywords, sondern auch nur Definitionen: # define true 1 # define false 0 Daher können Sie zur Zuweisung der Farbe auch die vorgegebene Enumeration Color benutzen. Die Elemente einer Enumeration werden vom Compiler durchnummeriert und entsprechen damit in diesem Fall auch 0 und 1. • Zum Implementieren der Einfügefunktion benötigen Sie zwei Hilfsfunktionen: struct node * rotateLeft ( struct node * root , struct node * n ) ; struct node * rotateRight ( struct node * root , struct node * n ) ; Diese sollen die in Abbildung 1 dargestellte Operation durchführen. Der Parameter n ist der Knoten, über den die Drehung stattfindet. In Abbildung 1 entspricht n dem Knoten A im linken Baum und dem Knoten B im rechten Baum. • Implementieren Sie die Funktion insertRB. Gehen Sie dazu in zwei Schritten vor: 1. Verwenden Sie die Einfügefunktion des einfachen Binärbaumes, um den neuen Knoten in den Baum einzufügen. Sie können Ihre eigene Funktion oder die Variante aus der Musterlösung verwenden. Beachten Sie hierbei, dass das Attribut parent richtig gesetzt werden muss. 2. Durch das Einfügen kann es nötig werden, den Baum neu zu balancieren. Implementieren Sie dazu unter zur Hilfenahme der in der vorherigen Aufgabe implementierten Operationen die Neubalan- cierung. Es bietet sich an, diese in eine neue Funktion auszulagern. Die genaue Funktionsweise der Neubalancierung ist hinreichend in der Fachliteratur und diversen Webseiten beschrieben. Wir möchten Ihnen an dieser Stelle insbesondere folgende Webangebote empfehlen: – Implementierung RB-Tree im Linux Kernel: https://github.com/torvalds/linux/blob/master/ lib/rbtree.c – https://www.cs.auckland.ac.nz/software/AlgAnim/red_black.html, hier insbesonders das Bei- spiel zur Einfügeoperation: https://www.cs.auckland.ac.nz/software/AlgAnim/red_black_op. html Sollten Sie während Ihrer Recherche auf verschiedene Varianten stoßen, können Sie frei wählen. Wichtig ist nur, dass diese einen gültigen RB-Tree (=Invarianten eines RB-Tree sind erfüllt) erzeugt (siehe Hinweis). • Die Operation zum Entfernen eines Knotens ist sehr lang und komplex, daher soll diese nicht näher betrachtet werden. Sollten Sie die Löschoperation für Ihre Implementierung der folgenden Aufgabe benötigen, können Sie unter Angabe der Quelle eine bestehende Implementierung verwenden. • Implementieren Sie die Funktion cfs. Diese erhält einen Pointer auf eine Liste von struct process und eine Zeitdauer als Übergabeparameter. Diese Funktion soll nun das CFS Scheduling simulieren: Noch Fragen? Dann schaut doch mal im Zulip-Stream vorbei: https://zulip.in.tum.de All Rights Reserved, Do Not Distribute! 2
Lehrstuhl für Connected Mobility Fakultät für Informatik Technische Universität München – Erweitern Sie die node Struktur um einen Zeiger data auf die process Struktur, die von diesem Knoten repräsentiert wird. – Fügen Sie die Prozesse in den von Ihnen implementierten RB-Tree ein. Das Attribut val jedes Knotens soll dabei auf den Wert der PID gesetzt werden (dies passiert im realen CFS nicht, aber ist hier sinnvoll, um einen zufälligen Zeitpunkt zu simulieren). – Implementieren Sie eine Schleife, die über die Anzahl der jeweiligen Zeitschritte iteriert. In jedem Zeitschritt soll folgendes passieren: * Wählen Sie den Knoten mit der kleinsten bisherigen Rechenzeit aus (Knoten mit kleinstem Wert in val). * Geben Sie die Rechenzeit (val) und PID in folgendem Format, gefolgt von einem Zeilenumbruch, aus: Time : ␣ ␣ PID : ␣ * Erhöhen Sie den Wert der bisherigen Rechenzeit um 10, und sorgen Sie dafür, dass Ihr Baum valide bleibt (Knoten muss vielleicht an einer anderen Stelle eingefügt werden). * Um verschiedene Prioritäten zu simulieren, haben die Prozesse verschiedene "Decay"Faktoren. Die Idee ist, dass die bisherige Rechenzeit mancher Prozesse verringert wird, um den Prozess schneller wieder rechnen zu lassen. Nutzen Sie den Wert decay in struct process und subtra- hieren Sie diesen von der jeweiligen bisherigen Rechenzeit im zugehörigen Knoten. Achten Sie auch hier darauf, dass Ihr Baum valide bleibt. – Nach der Simulation des Schedulings geben Sie einen Pointer auf den RB-Baum zurück. Hinweise und Bemerkungen: • Nutzen Sie für die Anordnung der Knoten dieselbe Invariante wie bei der Binary Tree Aufgabe (Der linke Kindknoten muss einen kleineren oder gleichen Wert, wohingegen der rechte Kindknoten nur einen größeren Wert als sein Elternknoten haben darf). • Wir haben Ihre Anmerkungen zur Binary Tree Aufgabe ernst genommen und das Testverfahren geändert. Anstatt Ihren Baum mit einer Referenzimplementierung zu vergleichen prüfen wir nun, ob es sich bei Ihrem Baum um einen validen RB-Tree handelt. Somit gibt es mehrere richtige Lösungen. • Wir hätten Ihnen gerne eine graphische Repräsentation Ihres Baums zur Verfügung gestellt, aber dies war mit Artemis leider nicht möglich (Ausgabelänge ist zu begrenzt). • Bitte implementieren Sie die geforderten Funktionalitäten an der dafür vorgesehenen Stelle. Nur so können die Tests unabhängig voneinander durchgeführt werden. Insbesondere werden jegliche Änderungen an der Datei main.c nicht vom Tester berücksichtigt. Noch Fragen? Dann schaut doch mal im Zulip-Stream vorbei: https://zulip.in.tum.de All Rights Reserved, Do Not Distribute! 3
Lehrstuhl für Connected Mobility Fakultät für Informatik Technische Universität München Aufgabe 2 Dämonendomäne (G) In dieser Aufgabe werden Sie mit Hilfe von systemd einen Daemon erstellen, der Texte in lower bzw. upper case konvertieren kann. Zur Kommunikation sollen named pipes verwendet werden 1 . Ein Beispiel: $ cd /tmp/ $ ls -la drwxrwxrwt 12 root root 4096 Dec 9 16:27 . drwxr-xr-x 22 root root 4096 Sep 10 15:53 .. prw-r--r-- 1 root root 0 Dec 9 16:27 echo_in prw-r--r-- 1 root root 0 Dec 9 16:27 echoed $ echo "Hallo Welt" >> echo_in $ cat echoed HALLO WELT $ echo -e "Abc Def Ghi jkL 123 \t ^.°" >> echo_in $ cat echoed ABC DEF GHI JKL 123 ^.° Standardmäßig soll die Ausgabe wie dargestellt in upper case erfolgen. Durch die Verwendung von signals 2 soll dieses Verhalten gesteuert werden können: Empfängt der Daemon-Prozess (im Folgenden ebenfalls echoed genannt) SIGUSR2, sollen künftige Eingaben zu lower case konvertiert werden. Analog forciert der Empfang von SIGUSR1 die Konversion zu upper case. Es sollen ausschließlich Zeichen verändert werden, die Buchstaben in ASCII darstellen: $ pkill -SIGUSR2 echoed $ echo "Hallo Welt" >> echo_in $ cat echoed hallo welt $ echo -e "Abc Def Ghi jkL 123 \t ^.°" >> echo_in $ cat echoed abc def ghi jkl 123 ^.° $ pkill -SIGUSR1 echoed $ echo "Hallo Welt" >> echo_in $ cat echoed HALLO WELT $ pkill -SIGUSR1 echoed $ echo "Hallo Welt" >> echo_in $ cat echoed HALLO WELT $ echo "Hallo Welt" >> echo_in $ cat echoed HALLO WELT 1 siehe man 3 mkfifo (Linux) 2 siehe z.B. kill -l oder man signal Noch Fragen? Dann schaut doch mal im Zulip-Stream vorbei: https://zulip.in.tum.de All Rights Reserved, Do Not Distribute! 4
Lehrstuhl für Connected Mobility Fakultät für Informatik Technische Universität München Hintergrundwissen Daemons: Wie aus der Vorlesung bekannt, handelt es sich bei Daemons um (Hintergrund-) Prozesse, die oft nur bei bestimmten Ereignissen (z.B. Empfang eines Netzwerkpakets) aktiv werden. Um dies sichtbar zu machen, ist es Konvention, den Prozessnamen ein d nachzustellen (cupsd, netbiosd, xfce4-volumed, . . . ). Um zum Daemon zu werden, muss ein Prozess forken, seine Standardfiledeskriptoren schließen und ggf. erneut forken. Er wird somit nach Beendigung des Elternprozesses vom init Prozess adoptiert und ist nicht mehr mit einem Terminal verbunden. Dieser klassische Weg steht auch heute noch offen, für diese Aufgabe möchten wir aber eine komfortablere Methode anwenden: Seit einigen Jahren hat sich systemd bei den bekannten Linuxdistributionen als Standard zur Systeminitialisie- rung etabliert (Default bei Fedora seit 2010, Arch und Debian seit 2012, RHEL seit 2014. . . ). Zur Prozessverwaltung werden zahlreiche Funktionen wie z.B. das Monitoring von Dateien bereitgestellt, die somit nicht mehr in jedem Daemon selbst implementiert werden müssen. Soll ein Daemon beispielsweise erst gestartet werden, sobald eine bestimmte Datei existiert, kann dies in einer path unit konfiguriert werden. Eigenschaften des gestarteten Dienstes werden in der zugehörigen service unit spezifiziert: $ cat /lib/systemd/system/example.path [Path] PathExists=/tmp/activation_file [Unit] Unit=example.service $ cat /lib/systemd/system/example.service [Unit] Description=A simple systemd service unit Documentation=man:example(7) [Service] ExecStart=/usr/bin/exampled Type=oneshot Restart=no [Install] WantedBy=multi-user.target Das Type Attribut der [Service] Section ist hierbei besonders wichtig. Ist es, wie hier dargestellt, oneshot oder simple, so muss sich der exampled Prozess nicht mehr selber um fork und das Schließen der Filedeskriptoren kümmern. Die [Install] Section ermöglicht das automatische Starten des Dienstes, sobald das System initialisiert ist. Gehen Sie nun wie folgt vor: • Schreiben Sie ein C-Programm, das zunächst mittles mkfifo eine named pipe /tmp/echo_in und eine named pipe /tmp/echoed erzeugt. Fortan bleibe der Prozess in einer Dauerwarteschleife, ohne sich zu beenden. Sobald echo_in verändert wird (also von einem beliebigen Prozess neuer Inhalt hineingeschrie- ben wurde), soll das Programm wieder aktiv werden und den (neuen) Inhalt (in lower bzw. upper case konvertiert) in /tmp/echoed schreiben. Anschließend wird wieder auf neuen Input gewartet. Sie können davon ausgehen, dass die Signale SIGUSR1 und SIGUSR2, die bei Ankunft instantan bearbeitet werden, erst eine geraume Zeit nach der letzten Änderung von echo_in gesendet werden, um eine Änderung inmitten der Ausführung Ihrer Konvertierungsfunktion zu vermeiden. Noch Fragen? Dann schaut doch mal im Zulip-Stream vorbei: https://zulip.in.tum.de All Rights Reserved, Do Not Distribute! 5
Lehrstuhl für Connected Mobility Fakultät für Informatik Technische Universität München • Machen Sie sich mit der path unit von systemd vertraut. Wie können Sie damit Ihrem Daemon signalisie- ren, dass neuer Input vorhanden ist? Wie können Sie dabei die Warteschleife des Daemons unterbrechen? Erstellen Sie hierfür eine Einheit echoed_activate.path Dieser Mechanismus kann leider nicht im Testsystem verwendet werden. Hier bietet es sich an, dass der Warteschleife ein blockierendes read auf die Pipe zu Grunde liegt (wenn ein Prozess in /tmp/echo_in schreiben möchte blockiert dieser so lange, bis Ihr Daemon von der Pipe liest, da hierfür sonst vom Dateisystem ein Puffer bereitgestellt werden müsste). • Testen Sie Ihr Programm zunächst lokal und unabhängig von systemd. Wird wirklich nur der neue Input ausgegeben? Ist das casing korrekt? Funktioniert das Signalhandling auch, wenn z.B. mehrfach hintereinander das gleiche Signal ankommt? Ist sichergestellt, dass der Daemon nicht terminiert und immer wieder aus den Ruhephasen reaktiviert werden kann? Eine Sonderfallbehandlung, wenn die Pipes zu Beginn bereits existieren, ist nicht verlangt, Sie können davon ausgehen, dass sie noch nicht existieren. • Testen Sie nun das Zusammenspiel mit systemd. Mittels loginctl enable-linger $(whoami) können Sie ermöglichen, dass eine Instanz von systemd lediglich mit Ihren Benutzerrechten und nicht wie standard- mäßig als root läuft. Der Befehl systemctl --user start echoed_activate.path aktiviert nun die path unit, die in /home/$(whoami)/.config/systemd/user/ liegen muss. Hinweise zur Bewertung: Aus technischen Gründen können wir auf dem Testserver leider keine (echten) systemd-services nutzen, weshalb ausschließlich Ihr C-Programm bewertet wird. Zum lokalen Testen ist das Erstellen der systemd units allerdings äußerst wertvoll. Achten Sie darauf, dass das Programm nach Bearbeitung der Signale SIGUSR1 und SIGUSR2 nicht beendet wird. Noch Fragen? Dann schaut doch mal im Zulip-Stream vorbei: https://zulip.in.tum.de All Rights Reserved, Do Not Distribute! 6
Lehrstuhl für Connected Mobility Fakultät für Informatik Technische Universität München Aufgabe 3 Clock-Algorithmus WEB (E) Lernziele: • Funktionsweise des Clock-Algorithmus. • In der kommenden Hausaufgabe wird es Ihre Aufgabe sein, das hier erworbene Wissen zu nutzen, um den Clock-Algorithmus selbst zu implementieren. Aufgabenstellung: Auf der Webseite https://paging.tum.sexy finden Sie ein von uns entwickeltes Tool zur Simulation des Clock- Algorithmus. Wählen Sie dort unter ”Algorithmus auswählen” ”Clock” aus. Probieren Sie sich dort etwas herum und lösen Sie einige Aufgaben. Über den Knopf ”Zufällig” können Sie sich beliebige konfigurationen erzeugen lassen. Beispielkonfiguration: • https://vmott42.in.tum.de/paging?iscsv=true&strategy=clock&frames=6&csv=2,2,4,5,3,3,6,5,4,3, 4,6&rw=r,r,w,r,w,r,r,r,w,r,w,r Noch Fragen? Dann schaut doch mal im Zulip-Stream vorbei: https://zulip.in.tum.de All Rights Reserved, Do Not Distribute! 7
Lehrstuhl für Connected Mobility Fakultät für Informatik Technische Universität München Aufgabe 4 Buddy-Algorithmus CSV (E) Lernziele: • Funktionsweise des Buddy-Algorithmus. • In der kommenden Hausaufgabe wird es Ihre Aufgabe sein, das hier erworbene Wissen zu nutzen, um den Buddy-Algorithmus selbst zu implementieren. Aufgabenstellung: Nehmen Sie an, es stehen für diese Aufgabe 1024 MiB Speicher zur Verfügung. Die Verwaltung dieses Speichers soll mithilfe des Buddy-Algorithmus stattfinden. Es erfolgen die unten zu sehenden Allokationen und Freigaben, welche von Ihnen Schritt für Schritt durchgeführt werden sollen. Für die Dokumentation ihrer Ergebnisse sind CSV-Dateien vorgesehen. Dazu geben wir Ihnen für jeden einzelnen Schritt eine eigene CSV-Datei vor. Die Dateien sind entsprechend der Nummer des jeweiligen Schrittes benannt (für Schritt x: buddy_.csv). Dabei stellt die Datei buddy_0.csv die Belegung des initial freien Speichers von 1024 MiB dar. Bitte geben Sie innerhalb der CSV-Dateien alle Speicherbereiche an, die durch die Unterteilung des Speichers entstanden sind, unabhängig davon, ob sie belegt sind oder nicht. Halten Sie sich bezüglich der Formatierung der CSV-Dateien an folgendes Beispiel: Start Size State 0 256 b 256 256 f 512 512 f Das Beispiel beschreibt einen Speicherbereich, der in drei Teile aufgespalten wurde. Der erst Bereich beginnt bei Adresse 0, hat die Größe 256 MiB und ist belegt. Der zweite Bereich beginnt bei Adresse 256, ist 256 MiB groß und frei. Der dritte Bereich beginnt bei Adresse 512, hat die Größe 512 MiB und ist ebenfalls frei. Allokationen und Freigaben: 1. Allokation: 100 MiB 2. Allokation: 200 MiB 3. Allokation: 256 MiB 4. Freigabe: 200 MiB (aus 2.) 5. Freigabe: 100 MiB (aus 1.) 6. Freigabe: 256 MiB (aus 3.) Noch Fragen? Dann schaut doch mal im Zulip-Stream vorbei: https://zulip.in.tum.de All Rights Reserved, Do Not Distribute! 8
Sie können auch lesen