diff --git a/gen.pl b/gen.pl index 3ff6584be45661dfdf4f70c0c5b4ffd352e8d0a6..2d2b6b42013620b60fecd3f340e569d32470d067 100755 --- a/gen.pl +++ b/gen.pl @@ -9,7 +9,7 @@ use IPC::Open2; use Digest::MD5 qw(md5_base64); my @hlwords = qw/immer nie jede jedem jedes muss keine alle nicht/; -my @markdown = qw/pandoc -f markdown+smart --html-q-tags --ascii/; +my @markdown = qw/pandoc -f markdown --html-q-tags --ascii/; my $question; my $single_choice; @@ -99,11 +99,11 @@ while (<>) { $question =~ s/\Q($source)\E//; chomp $source; } - } elsif (/^[|](.+)/) { + } elsif (/^[|](.*)/) { if ($last_option{"option"}) { - $last_option{"option"} .= $1; + $last_option{"option"} .= "$1\n"; } else { - $question .= $1; + $question .= "$1\n"; } } elsif (/^@(.*)/) { $media = $1; diff --git a/ss22.q b/ss22.q index 9031e326d550b76a9745f6565b3c8d7824dd79c2..469f5f2bb4f29821836fa8b902cb0dd04bb62400 100644 --- a/ss22.q +++ b/ss22.q @@ -62,5 +62,122 @@ der Ausführung auftreten, bspw. wenn je nach Rechenarchitektur ein [`syscall`](https://www.felixcloutier.com/x86/syscall) (x86_64) Befehl ausgeführt wird. +. + +0 Welche Aussage zum Thema Systemaufrufe ist richtig? (2022-07) +- Durch die Bereitstellung von Systemaufrufen, kann ein Benutzerprogramm das Betriebssystem um eigene Funktionen erweitern. +Nein, Systemaufrufe sind die Schnittstelle, die das Betriebssystem Benutzerprozessen zur Verfügung stellt, um privilegierte Operationen, kontrolliert ausführen zu lassen. Benutzerprozesse können (u. a. aus Sicherheitsgründen) das Betriebssystem nicht in dieser Hinsicht verändern. ++ Mit Hilfe von Systemaufrufen kann ein Benutzerprogramm privilegierte Operationen durch das Betriebssystem ausführen lassen, die es im normalen Ablauf nicht selbst ausführen dürfte. +Ja, möchte ein Programm z. B. eine Datei lesen, so sendet es nicht selbst z. B. NVMe-Befehle an den Speicher (privilegierte Operation, könnte zum Verlust aller Daten führen), sondern beauftragt das Betriebssystem (im Fall von POSIX mit `read(2)`), den Dateiinhalt zu lesen. +- Die Bearbeitung eines Systemaufrufs findet immer im selben Adressraum statt, aus dem heraus der Systemaufruf abgesetzt wurde. +Nein, bei der Bearbeitung eines Systemaufrufs im Systemkern (*kernel*) ist der *kernel address space*, der nicht für Benutzerprozesse sichtbar ist, verfügbar. +- Benutzerprogramme dürfen keine Systemaufrufe absetzen, diese sind dem Betriebssystem vorbehalten. +Nein, Benutzerprozesse verwenden Systemaufrufe, um Operationen auszuführen, für die sie selbst nicht die Privilegien besitzen. +. + + +0 Bei der Behandlung von Ausnahmen (Traps oder Interrupts) unterscheidet man zwei Bearbeitungsmodelle. Welche Aussage hierzu ist richtig? (2022-07) +- Nach dem Beendigungsmodell werden Interrupts bearbeitet. Gibt man z. B. CTRL-C unter UNIX über die Tastatur ein, wird ein Interrupt-Signal an den gerade laufenden Prozess gesendet und dieser dadurch beendet. +Nein, der Interrupt wird vom Betriebssystem abgefangen. Dieses kann dann einem Prozess ein Signal zustellen. Dieser muss jedoch nicht unbedingt der gerade laufende Prozess sein (Auch Hintergrundprozesse (*daemons*) können sich im Zustand "laufend" befinden). +- Das Beendigungsmodell sieht das Herunterfahren des Betriebssystems im Falle eines schwerwiegenden Fehlers vor. +Nein. Es reicht aus, nur den problematischen Prozess zu beenden. Das hier beschriebene Herunterfahren würde die Robustheit eines Betriebssystems senken. ++ Das Wiederaufnahmemodell ist für Interrupts und Traps gleichermaßen geeignet. +Ja. Auch bei Traps wie z. B. einem Seitenfehler, bei dem ein Prozess auf eine Seite zugreift, die gerade ausgelagert ist (*swapping*), ist das Weiterlaufen des Prozesses, nachdem die Seite wieder eingelagert wurde, sinnvoll. +- Interrupts sollten nach dem Beendigungsmodell behandelt werden, weil ein Zusammenhang zwischen dem unterbrochenen Prozess und dem Grund des Interrupts bestehen kann. +Nein, Interrupts sind unvorhersagbare Ereignisse wie z. B. das Eingehen eines Netzwerkpaketes oder das Drücken einer Taste auf der Tastatur. Der aktuell laufende Prozess sollte i. A. nicht direkt wegen einem derartigen Ereignis beendet werden, da dieser nicht unbedingt einen Bezug zu einer derartigen Eingabe hat. +. + +0 Welche Aussage zum Thema Programme und Prozesse ist richtig? (2022-07) ++ Ein Programm kann durch mehrere Prozesse gleichzeitig ausgeführt werden +Ja, teste z. B. + +~~~ +sleep 5 & +sleep 5 +~~~ + +Hier wird das Programm `sleep` gleichzeitig ausgeführt, weswegen dieser Befehl nur 5 Sekunden (statt 10) benötigt. +- In einem Prozess kann immer nur ein Programm ausgeführt werden. +Nein, nach POSIX kann mit `exec(3)` ein Prozess zu einem anderen Programm wechseln. +- Ein Prozess kann gleichzeitig mehrere verschiedene Programme ausführen. +Nein, ein Prozess ist *ein* Programm in Ausführung. Es kann zwar zu einem anderen Programm gewechselt werden, möchte man jedoch nebenläufig ein anderes Programm ausführen, muss man einen neuen Prozess erzeugen (`fork(2) + exec(3)` oder `posix_spawn(3)`) +- Der Compiler erzeugt aus mehreren Programmteilen (Module) einen Prozess. +Nein. Der Compiler erzeugt Programme (oder Objektdateien, die zu Programmen gebunden werden), ein Prozess wird aber erst zur Laufzeit vom Betriebssystem erstellt. +. + +0 Ein laufender Prozess wird in den Zustand bereit überführt. Welche Aussage passt zu diesem Vorgang? (2022-07) +- Es ist kein direkter Übergang von laufend nach bereit möglich. ++ Der Prozess wird durch einen anderen Prozess verdrängt, oder gibt die CPU freiwillig ab. +Ja, der Prozess könnte noch weiterrechnen, ist also *bereit*. +- Der Prozess wartet auf Daten von der Festplatte. +Nein, der Prozess ist dann *blockiert*, bis die Daten gelesen sind. +- Der Prozess wartet mit dem Systemaufruf `waitpid(3)` auf die Beendigung eines anderen Prozesses. +Nein, der Prozess ist dann *blockiert*, bis der andere Prozess beendet ist. Eine Ausnahme könnte hier der Aufruf mit dem Parameter `WNOHANG` darstellen, mit welchem der Systemaufruf sofort zurückgibt. +. + +0 Welche Seitennummer und welcher Offset gehören bei einstufiger Seitennummerierung und einer Seitengröße von 1024 (= 2¹⁰) Bytes zu folgender logischer Adresse: 0x0802? (2022-07) ++ Seitennummer 0x2, Offset 0x2 +Ja, `0x0802 = 0b0000 10|00 0000 0010` also ist die Seitennummer `0b00 0010 = 0x2` und der Offset `0b00 0000 0010 = 0x2` +- Seitennummer 0x8, Offset 0x8 +Nein. Hier sind beide Angaben falsch. +- Seitennummer 0x2, Offset 0x8 +Nein. Hier ist der Offset falsch. +- Seitennummer 0x8, Offset 0x2 +Nein. Da hier der Offset 10 Bit und kein Vielfaches von 4 hat, darf man nicht einfach die Hexadezimaldarstellung an der Grenze eines [*nibbles*](https://de.wikipedia.org/wiki/Nibble) (= Halbbyte, 4 Bit, ein Zeichen in Hexadezimaldarsetllung) zerlegen. Deswegen ist hier die Seitennummer falsch. +. + +0 Welche Aussage zu UNIX/Linux-Dateideskriptoren ist korrekt? (2022-07) ++ Nach dem Aufruf von `fork(2)` teilen sich Eltern und Kindprozess die den gemeinsamen Dateideskriptoren zu Grunde liegenden Kernel-Datenstrukturen. +Ja, vergleiche zum Beispiel "sister", bei der ein Kindprozess für die Bearbeitung einer Anfrage zuständig war und den Dateideskriptor verwendet, der im Elternprozess von `accept(3)` zurückgegeben wurde. +- Da Dateideskriptoren Zeiger auf Betriebssystem-Interne Strukturen sind, können diese zwischen Prozessen geteilt werden. +Der Dateideskriptor ist lediglich ein Wert, der vom Betriebssystem als eine Referenz auf einen Wert in einer pro Prozess im *kernel space* angelegte Datenstruktur interpretiert wird (z. B. ein Index in ein Array an Dateideskriptionen). Da diese nicht geteilt ist, ist auch das Teilen von Dateideskriptoren im Allgemeinen nicht zielführend. +- Der Dateideskriptor enthält die nötigen Metadaten einer Datei und ist auf der Festplatte gespeichert. +Nein, das wäre die "inode". Der Dateideskriptor ist eine Referenz (jedoch kein Pointer) auf eine Datenstruktur im Betriebssystem, die u. a. auch die Position des Prozesses in der Datei enthält. +- Das Flag `FD_CLOFORK` eines Dateideskriptors sorgt dafür, dass der Dateideskriptor bei einem Aufruf von `fork(2)` automatisch geschlossen wird. +Nein, dieses Flag existiert nicht. Angespielt wird hier auf `FD_CLOEXEC`, was die beschriebene Wirkung bei einem Aufruf von `exec(3)` zeigt. +. + +0 Welche Aussage über die Koordinierung von kritischen Abschnitten unter Unix ist richtig? (2022-07) ++ Für die Synchronisation zwischen dem Hauptprogramm und einer Signalbehandlungsfunktion sind Schlossvariablen (Locks) ungeeignet. +Ja. Signalbehandlung stellt asymmetrische Nebenläufigkeit dar. Der aktuelle Ausführungsstrang wird also unterbrochen, um zum *signal handler* zu springen. Wenn dieser nun versucht, ein *lock* zu sperren, das schon vom Hauptprogramm gesperrt wurde, so wartet dieser auf das Hauptprogramm, welches jedoch durch die Signalbehandlung unterbrochen ist. Es kommt zu einer Verklemmung. +- Ein Unix-Prozess kann durch das Sperren von Unterbrechungen (Interrupts) den Speicherzugriff in einem kritische Abschnitte [sic!] synchronisieren. +Nein. Einerseits ist das Sperren von Interrupts, die ja nicht notwendigerweise im Zusammenhang mit dem Prozess stehen müssen, eine privilegierte Operation, die dem Betriebssystem vorenthalten ist. Andererseits würde dies bei manchen Formen von Nebenläufigkeit nicht den gewünschten Effekt erzielen, da z. B. auf einem Multiprozessorsystem mehrere leichtgewichtige Prozesse (*kernel-level threads*) echt nebenläufig Speicherzugriffe durchführen können, ohne dass einer der Prozesse durch einen Interrupt verdrängt wird. +- In einem Unix-Prozess kann es keinen kritischen Abschnitt geben, da immer nur ein Aktivitätsträger pro Prozess aktiv ist. +Nein. Die Ressourcen eines schwergewichtigen Prozesses können zwischen verschiedenen leichtgewichtigen Prozessen geteilt sein, die nebenläufig einen Aktivitätsstrang ausführen. +- Kritische Abschnitte können unter Unix nur mit Semaphoren synchronisiert werden. +Nein. es gibt vielerlei Synchronisationsmechanismen für verschiedene Arten von Nebenläufigkeit, für kritische Abschnitte kann insbesondere auch `pthread_mutex` verwendet werden. +. +1 Welche der Aussagen zu folgendem Programmfragment sind richtig? (2022-07) +| +| +|~~~ +|static int a = 2022; +|void f1 (const int *y) { +| static int b; +| int c; +| char *d = malloc(0x802); +| void (*e)(const int *) = f1; +| y++; +| //... +|} +|~~~ +| + ++ `e` liegt im Stacksegment und zeigt in das Textsegment. +Ja. `e` ist ein Zeiger auf die Funktion `f1`. Funktionen liegen im Textsegment. Zudem ist `e` eine lokale Variable, liegt also im Stacksegment. +- `c` ist mit dem Wert 0 initialisiert. +Nein, nur Variablen der Speicherklasse `auto`, also globale Variablen und lokale `static`-Variablen werden auf 0 gesetzt, wenn sie nicht anderweitig initialisiert werden. `c` ist uninitialisiert. +- Die Anweisung `y++` führt zu einem Laufzeitfehler, da `y` konstant ist. +Nein. Das Ziel von `y` ist hier `const`, `y` selbst jedoch nicht. Pointerarithmetik auf `y` ist also erlaubt. Eine Merkregel hierfür lautet "`const` bezieht sich auf das Schlüsselwort links davon, außer es steht ganz links. Dann bezieht sich `const` auf das Schlüsselwort rechts davon." Hier bezöge sich const also auf `int`, nicht auf `*`. Siehe auch [hier](https://c-faq.com/decl/spiral.anderson.html) ++ `d` ist ein Zeiger, der in den Heap zeigt. +Ja. Mit `malloc(3)` wird Haldenspeicher allokiert. ++ `a` liegt im Datensegment. +Ja. `a` ist eine globale Variable. Globale Variablen liegen im Datensegment. `a` liegt im Datensegment. +? `y` liegt im Stacksegment. +Gemäß der x86_64-System V-ABI liegen Funktionsparameter wie `y` in einem Register, das ist jedoch nicht plattformunabhängig garantiert. Ich vermute trotzdem, die erwartete Antwort hier ist "ja". +- `b` liegt im Stacksegment. +Durch die Verwendung von `static` innerhalb einer Funktion erzeugt man eine Variable, die zwar nur in diesem Gültigkeitsbereich sichtbar ist, bei der jedoch Änderungen in einem Aufruf der Funktion in späteren Aufrufen sichtbar sind. Derartige Variablen liegen im Datensegment. +- Die Speicherstelle, auf die `d` zeigt, verliert beim Rücksprung aus der Funktion `f1()` ihre Gültigkeit. +Nein, da diese Speicherstelle im Heap liegt und - im Gegensatz zu Stackspeicher - erst durch den Aufruf von `free(3)` ungültig wird. . diff --git a/ws19.q b/ws19.q index f9d603088443a4db00feb1d7321d9de4254a96da..0cfecfd83b7d56ba03d31dce7800535746abf918 100644 --- a/ws19.q +++ b/ws19.q @@ -51,21 +51,22 @@ Nein, ein Zeiger wird immer noch als Wert an eine Funktion übergeben, d. h. es wird eine Kopie des Zeigers in das entsprechende Register oder auf den Stack abgelegt. Das ein Zeiger eine Referenz auf eine Speicherstelle *ist*, spielt hierbei keine Rolle. Beispiel: Würde die Aussage stimmen, so würde das folgende Programm "10" ausgeben. Es gibt jedoch "5" aus. - ```c - #include <stdio.h> - static int b = 10; - static void pointers_are_values_too(int* arg) { - arg = &b; // arg zeigt hier auf b, ptr in main bleibt aber unverändert. - } +~~~ +#include <stdio.h> +static int b = 10; - int main(void) { - int a = 5; - int* ptr = &a; // ptr zeigt auf a. - pointers_are_values_too(ptr); - printf("%d\n", *ptr); // ptr zeigt immer noch auf a. - } - ``` +static void pointers_are_values_too(int* arg) { + arg = &b; // arg zeigt hier auf b, ptr in main bleibt aber unverändert. +} + +int main(void) { + int a = 5; + int* ptr = &a; // ptr zeigt auf a. + pointers_are_values_too(ptr); + printf("%d\n", *ptr); // ptr zeigt immer noch auf a. +} +~~~ . 0 Welche Aussage zu Programmbibliotheken ist richtig? (2020-02)