SP2 Übungen - T02 und T05
Stand: 2.2. 15:19
Übungen und Codeschnipsel (Folien und Videos)
sisterrushmother, Evaluierung, KlausurvorbereitungKlausurvorbereitung
SP-Sachen von der FSI Informatik
Pad Juli 2016 (komplett) - Link zur Angabe Juli 2016
Pad Februar 2017 (nur Aufgabe 3) - Link zur Angabe Februar 2017
Pad Februar 2018 (komplett) - Link zur Angabe Februar 2018
> SP-Stuve-Pad SP1 <
>>> SP-Stuve-Pad SP2 <<<, das sehr viele Lösungsvorschläge enthält
Checkliste für Abgaben
- Ein
Makefileschreiben. - Aufgabenstellung *genau* lesen, auch die Hinweise. Spezifikation genau einhalten!
- Unnötige globale Variablen vermeiden.
- Globale Variablen und Funktionen, soweit möglich, als
staticdeklarieren. - Funktionen, die keine Argumente bekommen, in der Argumentliste mit
voidkennzeichnen. - Fehlerbehandlung! (Siehe
man-Pages), Korrekturrichtlinien - Jegliche durch
malloc,fopeno.ä. angeforderten Ressourcen wieder freigeben (siehevalgrind). - Beim Terminieren im Fehlerfall müssen die Ressourcen nicht wieder freigegeben werden.
- Mit
valgrindauf ungültige Speicherzugriffe prüfen. - Selber Testcases schreiben, besonders auf Randfälle prüfen!
Aufgabe 1: snail (einzeln)
- Abgabe T02: 8.11. 17:30
- Abgabe T05: 10.11. 17:30
- Rechtzeitig das Passwort für das SVN-Repository setzen.
- Doppelten Code vermeiden, Statuscode-Überprüfung in eine Funktion auslagern.
- Ihr könnt
ncverwenden, um euer Programm zu debuggen, indem ihr mitnc -l 2345in die Rolle des Mailservers schlüpft (faui03und Port2345dementsprechend anpassen!) - Die
faui03akzeptiert nur Mails aus dem Uni-Netz. - snail-Proxy
Aufgabe 2: sister (Gruppe)
- Abgabe T02: 22.11. 17:30
- Abgabe T05: 24.11. 17:30
- Webserver mit dem
wwwpathaus dem Beispiel starten und testen, ob die Grafiken dargestellt werden - Die Module können einzeln getestet werden, wenn sie mit den anderen Teilen der Referenzimplementierung zusammen gelinkt werden
- Fehlerbehandlung beim Schreiben auf die Netzwerkverbindung ist hier nicht notwendig (da
SIGPIPEden Prozess im Fehlerfall terminiert und einzelne Anfragen mitforkin eigenen Prozessen abgearbeitet werden) - Endian-Konvertierung:
man byteorder - (PSA) Es gibt doch keinen Proxy für den HTTP-Traffic (wie für SMTP bei der snail), weil:
- Sich der Proxy gegenüber dem Browser anders verhalten könnte, als die
sister - Würde nicht so viel bringen, da der Netzwerkverkehr nur aus der Anfrage und der Antwort besteht
- Es wäre sehr unübersichtlich, wenn die Anfrage-Header des Browsers und der Antwort-Body im Terminal landen
- Bilder im Terminal anzeigen (im Sinne von
cat $bilddatei) ist nicht so cool - Man müsste beide Richtungen trennen, da sonst die Ausgaben "vermischt" werden (nach der Anfragezeile senden beide u.U. "gleichzeitig")
- Sich der Proxy gegenüber dem Browser anders verhalten könnte, als die
Aufgabe 3: rush (Gruppe)
- Abgabe T02: 6.12. 17:30
- Abgabe T05: 8.12. 17:30
- Die Zusammenfassung "Signale" lesen - wenn Sachen unklar sind, gerne nachfragen! :)
- Mehrfach verwendeten Code in Funktionen auslagern
- Fehler, die nicht auftreten können (z.B. falsche Funktionsparameter bei garantiert gültigen Parametern), müssen auch nicht behandelt werden
- Ganz genau auf Race-Conditions zwischen Signalen und Hauptprogramm achten achten!
- Definierten Startzustand für die verwendeten Signalmasken und -behandlungen herstellen (die sind erstmal undefiniert!)
- Von der
rushgestartete Kindprozesse sollen eine "sinnvolle" Umgebung bekommen (im Sinne der Vererbung von Signalmasken und -Behandlungen)
Aufgabe 4: jbuffer (einzeln)
- Abgabe T02: 7.1. 17:30
- Abgabe T05: 11.1. 17:30
- Das
Makefilewird länger als bei den anderen Aufgaben, plant hierfür bitte genügend Zeit ein - Keine Ausgaben und kein
exitin den "Bibliotheksfunktionen" (und sinnvoll Fehler behandeln) - Auf korrekte Synchronisierung mit Semaphoren und
atomic_compare_exchange_strongachten - Wo braucht ihr
volatileoder_Atomic? - Strengere Compilerflags wie
-Wconversion(siehe unten) können interessant sein
Aufgabe 5: mother (Gruppe)
- Abgabe T02: 24.1. 17:30
- Abgabe T05: 26.1. 17:30
- Netzwerkdinge fehlerbehandeln, weil
SIGPIPEden Prozess nicht terminieren darf! - Was ist der Unterschied zwischen
exitund_exit? - Es geht einfacher, als
opendir/readdir- schaut euch da mal die manpages an - Statt
dupkönnt ihrfcntlmitF_DUPFD_CLOEXECverwenden, um das Close-on-exec Flag atomar zu setzen (dupwürde noch ein zusätzlichesfcntlerfordern und ist damit nicht atomar). - Die Race-Condition bei
acceptkönnt ihr ignorieren - die bekäme man nur mitaccept4weg, das ist allerdings nicht POSIX-konform.
Zusätzliche Compilerflags
Warum noch mehr Compilerflags? Wenn euch der Compiler durch genauere Prüfung schon vor Fehlern warnt, gebt ihr sie nicht mit ab. 😉
>> Wichtig: Der Code muss mit den "offiziellen" Flags kompilieren! <<
-std=c11 -D_XOPEN_SOURCE=700 -pedantic -Werror -Wall -g -Wextra -Wconversion -Wshadow -Wformat=2 -Wno-unused
Erklärung:
-g: Ermöglicht besseres Debugging mit valgrind oder gdb.
-Wextra: Schaltet noch mehr Checks an
-Wconversion: Weist auf implizite Casts hin, bei denen man Wertebereiche prüfen und ggf. abbrechen sollte
-Wshadow: Warnt bei überschriebenen Variablen (in Schleifen z.B.)
-Wformat=2: Erkennt kaputte/fehlende Format-Strings bei printf etc.
-Wno-unused: Fehler bei unbenutzten Funktionen/Variablen/… ignorieren
Legende:
- Immer anschalten
- (Erstmal) weglassen, wenn ihr unverständliche Fehlermeldungen bekommt
- Code muss auch ohne dieses Flag kompilieren, vor Abgabe sicherstellen!
Erkennen von Speicherlecks, Zugriffsfehlern usw.
-g verwenden, um Debug-Informationen mit ins fertige Programm zu schreiben. Nur so bekommt ihr Zeilennummern bei den Fehlerausgaben.valgrind --leak-check=full --show-reachable=yes --track-origins=yes ./liloZusammenfassung "Signale"
Ignorieren vs Blockieren - oder auch: Behandlung vs Maskierung
Die Behandlung eines Signals (SIG_IGN, ..., eigene Behandlung) gilt prozessweit für alle Threads. Ignorierte Signale werden verworfen, solange die Behandlung aktiv ist.
Meistens wird die Behandlung einmal zum Programmstart definiert - nachträgliche Änderungen sind eher ungewöhnlich.
Bei SIGCHLD hat (explizites) Ignorieren zur Folge, dass das Betriebssystem keine Zombieprozesse mehr anlegt.
Welche Signale überhaupt zur Behandlung zugelassen werden, definiert die aktive, threadspezifische Signalmaske.
Sie definiert für jeden Thread, welche der Signale erlaubt oder blockiert werden.
Blockierte Signale werden (in mehrfädigen Programmen) entweder von einem anderen Thread bearbeitet, der das Signal erlaubt hat - oder verzögert (wenn kein solcher Thread existiert oder das Signal explizit an diesen Thread und nicht an den Prozess gerichtet ist). Von blockierten Signalen wird (pro Signal) je ein Vorkommen gepuffert.
Einrichtung der Signalbehandlung
Die Behandlungfunktion für Signale wird vom Elternprozess geerbt und ist daher zu Programmstart nicht definiert und muss explizit gesetzt werden. Das gilt allerdings nur fürSIG_IGN und SIG_DFL. Warum? Wenn mit exec ein anderes Programm geladen wird, existiert die "alte" Behandlungsfunktion nicht mehr im neuen Programm und die Signalbehandlung wird auf das Standardverhalten zurückgesetzt. ¯\_(ツ)_/¯
static void handler(int signum) { (void) signum; int errno_backup = errno; // Die Signalbehandlung sollte *möglichst kurz* gehalten werden! // Falls die errno verändert werden könnte, vorher // sichern und danach wiederherstellen. errno = errno_backup; } int main(void) { struct sigaction action = { .sa_handler = handler, .sa_flags = SA_RESTART, }; sigemptyset(&action.sa_mask); sigaction(SIGINT, &action, NULL); // [...] }
sa_mask gibt an, welche Signale zusätzlich zu den aktuell schon blockierten Signalen außerdem während der Ausführund der Behandlung noch blockiert werden sollen.
In der Maske gesetzte Signale werden hierbei zeitweise der aktiven, threadspezifischen Signalmaske hinzugefügt. Der ursprüngliche Zusand der aktiven Signalmaske wird nach der Behandlung automatisch wiederhergestellt.
Nicht gesetzte Signale in sa_mask sind "don't care"s. Sie werden nicht verändert - es ist daher nicht möglich, aktuell blockierte Signale per sa_mask freizuschalten!
Bearbeiten der aktiven, threadspezifischen Signalmaske
Die aktive, threadspezifische Signalmaske ist, wie die Behandlungsfunktion, vom Elternprozess geerbt und nicht definiert. Daher muss sie ebenfalls zu Programmstart auf einen definierten Wert gesetzt werden, wenn Signale empfangen werden sollen oder explizit nicht erwünscht sind. Zum Bearbeiten der aktiven, threadspezifischen Signalmaske können Sets vom Typsigset_t verwendet werden.
Diese Sets enthalten nur eine Liste an Signalen - was diese Liste aussagt hängt davon ab, wie sie genutzt wird.
Mit sigprocmask können- alle gesetzten Signale blockiert werden - nicht gesetzte Signale = don't care, werden nicht verändert (
SIG_BLOCK) - alle gesetzten Signale erlaubt werden (nicht gesetzt = don't care, werden nicht angefasst) (
SIG_UNBLOCK) - die aktive Signalmaske ersetzt werden - gesetzte Signale werden blockiert, nicht gesetzte erlaubt (
SIG_SETMASK)
sigprocmask die bis dahin aktive Maske speichern. Das kann nützlich sein, um sie nach einem kritischen Abschnitt wiederherstellen zu können. Blockierte Signale werden in diesem Set gesetzt, erlaubte Signale gelöscht.
Signale mit zuvor undefiniertem Zustand werden beim Zurücksetzen auch wieder auf den alten (unbekannten) Wert gesetzt.
Ein Beispiel dazu gibt's weiter unten.
In Programmen mit mehreren Threads muss statt sigprocmask die Funktion pthread_sigmask verwendet werden.
Mit sigsuspend(A) kann auf erlaubte Signale gewartet werden - dabei wird A die neue aktive Signalmaske (SIG_SETMASK). Nur in A erlaubte/nicht gesetzte Signale werden zugestellt und können den Thread aufwecken. Nachdem der Thread aufgeweckt wurde, wird die alte Maske wiederhergestellt.
Erstellen und Bearbeiten von Sets
Signal-Sets können (z.B. auf dem Stack) mitsigset_t mask; deklariert werden.
Zur Intialisierung müssen entweder sigemptyset oder sigfillset verwendet werden.
Mit sigemptyset wird das Set geleert und kein Signal ist "gesetzt" - sigfillset füllt das Set, sodass alle Signale "gesetzt" sind.
Sobald ein leeres oder volles Set erzeugt wurde, kann es mit sigaddset und sigdelset weiter modifiziert werden.
Mit sigaddset kann ein Signal "gesetzt" werden, mit sigdelset wird es "gelöscht".
Da laut Spezifikation nicht festgelegt ist, ob gesetzte Signale eine 1 oder 0 in der Bitmaske sind, darf zur Initialisierung kein memset verwendet werden und alle Änderungen müssen über die gerade beschriebenen Funktionen durchgeführt werden.
Beispiel zum Blockieren von SIGCHLD
int main(void) { sigset_t old; sigset_t mask; sigemptyset(&mask); sigaddset(&mask, SIGCHLD); sigprocmask(SIG_BLOCK, &mask, &old); // Kritischer Abschnitt - hier kann kein SIGCHLD auftreten. sigprocmask(SIG_SETMASK, &old, NULL); // Hier ist SIGCHLD -wie vorher- undefiniert! // SIGCHLD kann erlaubt oder auch blockiert sein. sigprocmask(SIG_UNBLOCK, &mask, NULL); // Hier ist SIGCHLD auf jeden Fall erlaubt. // [...] }
SIGCHLD "gesetzt".
Der Aufruf von sigprocmask blockiert alle gesetzten Signale - also in dem Beispiel nur SIGCHLD, alle anderen Signale bleiben unverändert.
Nach dem kritischen Abschnitt wird die alte Signalmaske wiederhergestellt. Ob SIGCHLD erlaubt ist, ist unbekannt, da keine sinnvolle Initialisierung stattgefunden hat. Erst nach dem SIG_UNBLOCK Aufruf kann man sich drauf verlassen, dass SIGCHLD zugestellt werden kann.
Kontakt und Sonstiges
Bei Fragen zur Übung, zu euren Übungsaufgaben und deren Korrekturen (Korrekturrichtlinien) oder zu sonstigen SP-Themen könnt ihr mich gerne kontaktieren.
Mail: milan.stephan@fau.de
Discord: nudelsalat#8505
Rechnerübung: Mittwoch 12:15-15:45 Uhr - zwei Übungen hintereinander mit einer Pause in der Mitte (Terminplan)
StudOn Kurs mit Forum (offiziell)
FSI-Forum (SP-Unterforum) (inoffiziell)
IRC-Guide, Jahrgangschannel #faui2k20 und #sp *
RocketChat der FSI Informatik, Jahrgangschannel #faui2k20 und #2_sp1/#3_sp2
>> FAU Informatik Discord Server << (inoffiziell und auch für andere Studiengänge, die SP hören)
Andere SP-Seiten: