Friedrich-Alexander-Universität UnivisSuche FAU-Logo
Techn. Fakultät Willkommen am Department Informatik FAU-Logo
Logo I4
Lehrstuhl für Informatik 4
Betriebssysteme
 
  Vorlesung
    - UnivIS-Infos
    - Inhalt
    - Folien
 
  Übungen
    - UnivIS-Infos
    - Inhalt
    - Mailingliste
    - Ergänzendes Material
    - Terminübersicht
    - Aufgaben
       * Umgebung
       * Typische Fehler
       * Aufgabe 1
          Dokumentation
       * Aufgabe 2
          Dokumentation
       * Aufgabe 3
          Dokumentation
       * Aufgabe 4
          Dokumentation
       * Aufgabe 5
          Dokumentation
       * Aufgabe 6
          Dokumentation
       * Aufgabe 7
          Dokumentation
 
  Evaluation
Department Informatik  >  Informatik 4  >  Lehre  >  WS 2010/11  >  Betriebssysteme  >  Ãœbungen  >  Aufgaben  >  Typische Fehler

Übungen zu BS - Typische Fehler


Zu Hause läuft mein Programm doch!

Race Conditions

Symptome

Auf einem schnellen Pentium läuft euer System tadellos, selbst im Langzeittest, aber auf alten Rechnern stürzt es früher oder später ab.

Ursache

Ihr habt nicht alle kritischen Abschnitte geschützt. Es gibt also mindestens eine Stelle in eurem Programm, an der eine Unterbrechung für Fehler in den Datenstrukturen sorgt. Nun ist es nur eine Frage der Wahrscheinlichkeit, ob und wann die Unterbrechung genau dort auftritt. Diese Wahrscheinlichkeit ist umso größer, je weniger Anweisungen zwischen zwei Unterbrechungen ausgeführt werden können. Bei kurzen Timer-Intervallen und bei langsamen Prozessoren ist die Fehlerwahrscheinlichkeit also größer als bei langen Timer-Intervallen und schnellen Prozessoren.

Lösung

Überlegt noch mal ganz genau, zwischen welchen Anweisungen eine Unterbrechung für die Datenstrukturen eures Systems gefährlich werden könnte und schützt diese.


Fehlender Koprozessor beim i386

Symptome

Auf einem i486 oder Pentium läuft euer System problemlos, auf einem alten i386er stürzt es immer an der selben Stelle mit einem "unexpected interrupt" ab.

Ursache

Systeme mit i386-Prozessor besitzen nicht grundsätzlich einen mathematischen Koprozessor und können daher nicht mit Gleitkommazahlen (float, double) rechnen. Alle Maschinenbefehle, die derartige Berechnungen ausführen, lösen auf diesen Systemen also eine Exception (Nr. 7) aus.

Lösung

Wenn ihr nicht gerade Lust habt, eine Koprozessor-Emulation zu schreiben oder zu portieren und als Exception-Handler bei OOStuBS anzubinden, solltet ihr auf hohe Mathematik verzichten. Für die Lösung der Aufgaben 1 bis 6 braucht ihr solche Anweisungen garantiert nicht. Bei Aufgabe 7 müsst ihr euch schlimmstenfalls eben einschränken. Mit Ganzzahl-Arithmetik lassen sich auch feine Programme schreiben und viele Probleme lösen.


Warum stürzt der Rechner immer ab?

Zu große lokale Variablen (Stacks!)

Symptome

Euer Programm stürzt irgendwann ab, liefert einen "unexpected interrupt", bootet den Rechner oder erzeugt andere unerwünschte Ergebnisse. Möglicherweise tritt der Fehler nicht auf, wenn ihr irgendwo scheinbar belanglose Anweisungen einfügt, einen Anwendungsprozess mehr oder weniger erzeugt usw.

Ursache

Ihr legt zu große lokale Variablen in main () oder in einem eurer Anwendungsprozesse an.

Erklärung

Das Hauptprogramm main () von OOStuBS bekommt von uns bei der Systeminitialisierung einen Stack der Größe 4 KB zugewiesen (siehe Startup-Code in startup.asm). Zum Anlegen kleiner lokaler Variablen ist das vollkommen ausreichend, nicht aber, um die Anwendungsprozesse mit eigenen Stacks zu versorgen.
Das folgende Stück Code ist also falsch!
int main () {
    char stack [4096];
    Application appl (stack+4096);
    ...
}
Hier werden auf dem initialen Stack ein 4 KB großes Feld und ein Application-Objekt angelegt, was zusammengenommen bereits größer als die verfügbaren 4 KB ist. Da aber OOStuBS keine Überprüfung der Stackgrenzen vornimmt und auch sonst keine Schutzkonzepte implementiert, wird mit den lokalen Variablen Speicher überschrieben, der main() überhaupt nicht zur Verfügung steht. Das Ergebnis ist, dass globale Variablen wie z. B. die Interrupt-Vektortabelle überschrieben werden. Das kann unbemerkt bleiben, z. B. wenn nur Interruptvektoren zerstört werden, die nie benötigt werden, es kann aber auch zu Abstürzen oder anderen Fehlern führen. Das passiert insbesondere dann, wenn ihr entweder euren eigenen Code überschreibt oder wenn durch den Fehler unsinnige Werte als Adressen von Funktionen interpretiert werden (siehe auch Unexpected Interrupt).

Lösung

Legt große Variablen wie die Stacks der Anwendungsprozesse immer global an. Dann sorgt nämlich der Compiler dafür, dass der Speicherplatz für sie zur Verfügung steht. Wer will, kann das Schlüsselwort static verwenden, um anzuzeigen, dass die entsprechende Variable nur in der Datei referenziert werden soll, in der sie deklariert wurde.

static char stack [4096];

int main () {
    Application appl (stack+4096);
    ...
}
Außerdem müsst ihr aufpassen, dass ihr den Zeiger auf das obere Ende des Stacks ("top of stack") übergebt, ansonsten wird es wieder zu ungewünschtem Verhalten kommen.


Unexpected Interrupt

Symptome

Euer Programm stürzt mit der Meldung "unexpected interrupt - processor halted" ab, obwohl ihr Timer- und Tastaturinterrupts korrekt behandelt und sonst keine Interrupts erlaubt sind.

Ursache

Wahrscheinlich schreibt ihr in eurem Programm irgendwo an eine Stelle im Speicher, die euch nicht gehört, oder ihr ruft eine Methode auf einem Objekt auf, das nicht existiert. Der erste Fall tritt zum Beispiel auf, wenn ihr mehr lokale Variablen anlegt, als der Stack verkraften kann (siehe Zu große lokale Variablen (Stacks!)). Der zweite Fall tritt beispielsweise auf, wenn ihr Queue::dequeue () verwendet und nicht prüft, ob das Ergebnis überhaupt ein gültiges Objekt ist.

Entrant* next;
next = (Entrant*) readylist.dequeue ();
dispatch (*next);
Wenn die Queue nämlich bereits vorher leer war, wird ein NULL-Zeiger zurückgeliefert. In dem Beispiel oben wird die Null nun als Anfangsadresse eines Entrant-Objekts interpretiert. Wenn dispatch () nun in toc_switch () die Register mit den in der Struktur toc gespeicherten Registerwerten dieses Objekts belegen möchte, werden statt gültiger Werte die Inhalte der Speicherstellen 0-3 für ebx, 4-7 für esi, etc., und 16-19 für esp genommen. Was als nächstes passiert, hängt im wesentlichen davon ab, welche Werte diese 20 Speicherstellen zufällig hatten. Bei OOStuBS soll der Prozesswechsel ja durch eine ret-Anweisung erfolgen; der Inhalt der Speicherstelle, auf die der soeben initialisierte Stackpointer zeigt, wird also als Rücksprungadresse interpretiert. Dies kann natürlich wirklich eine Adresse im Codebereich sein, sodass nun einfach irgendein Stück des Programms ausgeführt wird, nur eben sicher nicht das richtige und sicher mit falschen Registerinhalten. Wahrscheinlicher ist aber, dass es sich gar nicht um eine Codeadresse handelt. Das erkennt der Prozessor aber nicht, er wird also versuchen, das Bitmuster, das er an dieser Stelle vorfindet, als Anweisung zu interpretieren und auszuführen. Das kann eine Weile gut gehen, obwohl natürlich vollkommener Unsinn dabei herauskommt. Früher oder später wird der Prozessor dann aber doch auf ein Bitmuster stoßen, dessen Ausführung eine Exception auslöst (z. B. durch den int-Befehl, eine Division durch Null, einen der verschiedenen Debug-Befehle oder ein Bitmuster, das sich nun wirklich nicht mehr als Anweisung interpretieren lässt). Wenn der Code von OOStuBS, der die Behandlung der Interrupts und Exceptions vornimmt, durch den Fehler noch nicht zerstört wurde, wird nun Panic::prologue () ausgeführt, also "unexpected interrupt - processor halted" ausgegeben und der Prozessor gestoppt.

Dasselbe Problem tritt übrigens auch auf, wenn einer eurer Anwendungsprozesse terminiert, ohne exit () oder kill () zu verwenden. Wenn er nämlich aus Thread::action () zurück nach kickoff () kehrt und ihr dort keinen Notstopp eingebaut habt, wird die unsinnige Rücksprungadresse verwendet, die ihr ganz am Anfang bei toc_settle () dort eingetragen habt.

Lösung

Schaut euer Programm noch einmal ganz kritisch danach durch, ob eure Stacks für die lokalen Variablen ausreichen, ob ihr irgendwo Objektzeiger verwendet, die möglicherweise NULL-Zeiger sein könnten, oder ob einer eurer Prozesse terminiert, ohne Scheduler::exit () aufzurufen.


Watch-Probleme

Symptome

Euer Programm zeigt ein merkwürdiges Verhalten bei kurzen Intervallen für die Zeitgeberunterbrechung (< 200 Mikrosekunden), z. B. extrem seltene Unterbrechungen, unexpected interrupts, u. a.

Ursache

Es sieht fast aus, als handele es sich um einen Hardware-Fehler. Sicher sind wir uns aber nicht.

Lösung

Vermeidet zu kleine Zeitgeberunterbrechungen. Sinnvolle Werte für präemptives Scheduling bei OOStuBS liegen im Bereich von ca. 10 oder 20 Millisekunden, also Watch watch (20000).


Fehlerhafte Diskette

Symptome

Unerklärliche Abstürze.

Ursache

Erstaunlich oft waren in diesem Fall einfach nur die Bootdisketten kaputt. Anscheinend ist der Verschleiß größer als vermutet.

Lösung

Versucht es mal mit einer frischen Diskette. Vielleicht hilft es ja. Ansonsten solltet ihr euch nochmal die zuvor beschriebenen Fehlerquellen durch den Kopf gehen lassen.


Warum macht der Rechner nicht das, was ich will?

Zugriffe auf den VGA-RAM werden gecachet.

Symptome

In der Applikationsschleife in Aufgabe 2 wird nichts weiter getan, als in einer Endlosschleife per kout.show (0, 0, ++x, 0x07); zyklisch die 256 möglichen Zeichen an eine Position des Grafikspeichers zu schreiben. Im Bochs funktioniert das auch wunderbar, nur auf dem echten PC ändert sich überhaupt nichts an dieser Bildschirmposition (bzw. manchmal erscheint dort nach etlichen Minuten dann doch irgendein Zeichen, das ist aber nicht reproduzierbar).

Ursache

In aktuellen BIOSen gibt es eine Einstellung "Video Memory Cache Mode", die man auf "UC" (Uncacheable) oder "USWC" (Uncached Speculative Write Combining) stellen kann; dies steht meistens auf "USWC". Dies hat zur Folge, dass "kleine" Schreibzugriffe auf den Bildschirmspeicher erst verzögert auf der Grafikkarte landen. I/O auf den Grafikkarten-Ports (z. B. ein einfaches index_port.outb (14), wie es in setpos () verwendet wird) sorgt dafür, dass der Grafikkartenspeicher aktualisiert wird.

Lösung

Einfach den "Video Memory Cache Mode" auf "UC" stellen. Alternativ eine sinnfreie Ausgabe auf einem VGA-Port tätigen:

IO_Port index_port (0x3d4);
while (1) {
    kout.show (79, 23, ++x, 0x0e);
    index_port.outb (14); // sinnfreie Ausgabe auf einem VGA-Port
}


Die Tastatur generiert keine Interrupts.

Symptome

Ihr wollt Tastaturinterrupts erzeugen, sie aber noch nicht im Interrupt-Handler abholen. Deshalb betreibt ihr Polling in main () bzw. in der Applikation, wobei das outb-Bit überprüft wird und ggf. das Zeichen vom Tastaturcontroller abgeholt wird. Nun werden zwar die Zeichen abgeholt und angezeigt, aber es kommen keine Interrupts an (d. h. eine Ausgabe in guardian () wird nie bzw. selten angezeigt).

Ursache

Anscheinend schickt der Tastaturcontroller einen Interrupt erst einige Zeit nachdem er das outb-Bit setzt und das Zeichen zum Abholen zur Verfügung stellt. Wenn ihr nun das Zeichen "zu früh" abholt, wird der Interrupt nicht generiert.

Lösung

Um das Problem kurzfristig zu beheben, könnt ihr das Abholen der Zeichen aus dem Puffer künstlich verzögern (for-Schleife) - dann werden Interrupts generiert. Auf lange Sicht sollten natürlich die Zeichen im Keyboard-Interrupthandler abgeholt werden, dann tritt dieses Problem auch gar nicht auf.


  Impressum   Datenschutz Stand: 2010-09-01 16:27   AG, WH