diff --git a/ss22.q b/ss22.q index dcc902803b44c03b1fcc6e116964fc2a0c5e619b..469f5f2bb4f29821836fa8b902cb0dd04bb62400 100644 --- a/ss22.q +++ b/ss22.q @@ -66,7 +66,7 @@ 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. Benutzerprozesse können (u. a. aus Sicherheitsgründen) das Betriebssystem nicht in dieser Hinsicht verändern. +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. @@ -78,62 +78,68 @@ Nein, Benutzerprozesse verwenden Systemaufrufe, um Operationen auszuführen, fü 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 dem Prozess, der gerade das aktuelle Terminal belegt, ein Signal zustellen. Dieser muss jedoch nicht unbedingt der gerade laufende Prozess sein (Auch Hintergrundprozesse (*daemons*) können sich im Zustand "laufend" befinden). +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 wegen einem derartigen Ereignis beendet werden, da dieser nicht unbedingt einen Bezug zu einer derartigen Eingabe hat. +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. +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, auf POSIX kann mit `exec(3)` ein Prozess zu einem anderen Programm wechseln. +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(3) + exec(3)` oder `posix_spawn(3)`) +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. Einerseits erzeugt der Compiler Objektdateien, die der Linker zu einem Programm bindet, andererseits wird ein Prozess weder vom Compiler noch vom Linker, sondern vom Betriebssystem zur Laufzeit erzeugt. +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. -Nein. + 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. +- 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 `0b0000 10 = 0x2` und der Offset `0b00 0000 0010 = 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 falsch. +Nein. Hier sind beide Angaben falsch. - Seitennummer 0x2, Offset 0x8 -Nein. Hier ist der Offset offensichtlich falsch. +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* (= Halbbyte, 4 Bit, ein Zeichen in Hexadezimaldarsetllung) zerlegen. Deswegen ist hier die Seitennummer falsch. +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. ++ 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 Index in die Dateideskriptortabelle pro Prozess im *kernel space*. Da diese nicht geteilt ist, ist auch das Teilen von Dateideskriptoren im Allgemeinen nicht zielführend. +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. +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 (*deadlock*). +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. @@ -145,7 +151,7 @@ Nein. es gibt vielerlei Synchronisationsmechanismen für verschiedene Arten von 1 Welche der Aussagen zu folgendem Programmfragment sind richtig? (2022-07) | | -|~~~~~ +|~~~ |static int a = 2022; |void f1 (const int *y) { | static int b; @@ -155,23 +161,23 @@ Nein. es gibt vielerlei Synchronisationsmechanismen für verschiedene Arten von | 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, denn nur globale Variablen werden mit 0 initialisiert, wenn sie nicht anderweitig initialisiert werden. `c` ist uninitialisiert. +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. -Ja. 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 `*`. +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 Heapspeicher allokiert. +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-POSIX-ABI liegen Funktionsparameter wie `y` in einem Register, das ist jedoch nicht plattformunabhängig garantiert. Ich vermute trotzdem, die erwartete Antwort hier ist "ja". +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, jedoch die Semantik einer globalen Variable besitzt. Demnach liegt `b` im Datensegment. +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. .