%*
%* Copyright (c) 2007 by Lukas Ruf (lukas.ruf@lpr.ch)
%*
%* Create: 2007-01-30:1636
%* Modify: 2007-01-30:1643
%* Commit: $Id: results.tex 3053 2007-08-20 19:32:27Z sryffel $
%*


\chapter{Evaluation}
\label{evaluation}

% In this chapter, present your achievements by underlying the model
% and architecture, as presented in \chapref{design}, and the
% implementation part (see \chapref{implementation}) by a
% evaluation results.

Testen und Evaluieren sind ein integraler Teil des Programmierens. 
% Denn was ist ein Stück Code wert, wenn es nicht getestet wurde?
In diesem Kapitel lege ich dar, mit welchen Mitteln ich die Korrektheit der
vorgestellten Implementation verifizierte. Zuerst werden die verwendeten Methoden und
Tests vorgestellt und nachher die Resultate präsentiert und diskutiert.



\section{Methodik}
\label{evaluation:methodology}

% For a good presentation of your results, clearly introduce the
% methodology you used to verify and evaluate your implementation.

Die Evaluation und das Testen von Topsy stützt sich auf drei Pfeiler:
Verifikation und Kontrolle durch die Werkzeuge des Simulators, Langzeittests und
das Last- und Crashtest-Programm von Topsy.

Die Beschäftigung mit dem Testen beginnt nicht erst am Schluss, sondern schon vor
und während der Implementation. In Topsy ist es üblich, Preconditions und
anderes mit Asserts durchzusetzen. Dies zwingt Entwickler, Funktionen nur in der
vorgesehenen Weise zu verwenden (Design by Contract).  



\subsection{Simulator}

Wenn Topsy in einem Simulator wie zum Beispiel bochs läuft, hat man die volle 
Kontrolle über alle Vorgänge. Das System kann jederzeit angehalten und sein 
Zustand inspiziert werden. Bei hardwarenahem Code, zum Beispiel für 
Kontextwechsel, ist dies oft die einzige Testmöglichkeit.

Der Debugger von Bochs erlaubt das Setzen von Breakpoints. Diese können
nicht nur auf eine Stelle im Code, sondern auch auf Lese- oder Schreibzugriffe
an bestimmte Adressen gesetzt werden. Bochs integriert auch Tools zur Analyse
des Speichers, des Page Mapping, der Segmente, des Stacks, der CPU und weiteres.

Damit können Situationen analysiert werden, die schwierig herzustellen sind,
zum Beispiel wenn das Timing eine grosse Rolle spielt. Gerade Interrupts und
besonders Nested Exceptions können so untersucht werden.


\subsection{Langzeittests}

Programmierfehler im HAL bemerkt man meistens schon beim Booten. Denn der
HAL-Layer hat genau definierte Schnittstellen nach unten und oben. Weder die
Hardware noch die Kernel Threads sind tolerant gegenüber Abweichungen und
reklamieren sofort. Der HAL muss funktionieren, damit in den oberen
Schichten überhaupt irgendetwas läuft. Die Abhängigkeit und die Frequenz, mit
der die Funktionen aufgerufen werden, sind so hoch, dass auch sehr
unwahrscheinliche Fälle mit hoher Wahrscheinlichkeit auftreten.

Sobald das System gebootet hat und die Shell läuft, müssen alle Komponenten
funktionieren. Zu diesem Zeitpunkt laufen 8 Threads, es müssen also konstant
Kontexte gespeichert und gewechselt werden. Es sind sowohl User als
auch Kernel Threads aktiv. Zudem werden ständig Nachrichten versendet, da die
Shell die Konsole nach neuem Input pollt. Dafür sind auch Nachrichten an den
Memory Manager nötig, da die Konsole die Antwort in eine Enveloppe schreibt,
welche von diesem angelegt und wieder freigegeben werden muss. 

Wenn Topsy über längere Zeit läuft, können viele Fehler wie Memory Leaks,
Fehler im Thread Management und in der IPC nahezu ausgeschlossen werden. 


\subsection{Lasttests}

\begin{figure}[!ht]
\begin{center}
  \includegraphics[width=0.66\textwidth]{topsyv3-crashme.png}
  \caption{Module von \crashme}
  \label{fig:topsyv3crashme}
\end{center}
\end{figure}

Schon seit der ersten Version ist in Topsy das Programm \crashme{} integriert. 
\crashme{} wird von der Shell aus als User Prozess gestartet (siehe Abbildung 
\ref{fig:topsyv3crashme}) und versucht, das System zum Absturz zu bringen. Es 
ist dem Unix-Testprogramm nachempfunden, welches beinahe jedes verfügbare Unix 
aus dem Tritt brachte, bis die Entwickler sich \crashme{} anschauten. Diese 
verschiedenen Module testen die Robustheit und Belastbarkeit von Topsy:
 
\begin{itemize}
  \item \codebf{syscall} \\
  Startet einen Kindprozess, welcher die Tests durchführt und auf dessen
  Ende wartet. Anhand des Rückgabewerts weiss der Elternprozess, ob die Tests
  erfolgreich waren oder nicht oder ob der Kindprozess vorzeitig beendet wurde.
  
  Der Kindprozess alloziert einige Memory Blöcke, startet einen Thread, testet
  den Yield-Syscall, empfängt eine Nachricht vom eben gestarteten Thread, killt
  ihn dann und gibt zuletzt die allozierten Speicherbereiche wieder frei.
  
  \code{syscall} ist eine Art Unit Test der verfügbaren Syscalls, testet aber
  nur ganz profane Abläufe und weder Grenzfälle noch Unerlaubtes.
  
  \item \codebf{stress} \\
  Dieser Test startet ein Thread, welcher rekursiv neue Threads erstellt.
  Sobald das Erstellen eines Threads fehlschlägt, wird abgebrochen und
  alle beendet.
  
  Der Name des Tests ist berechtigt, denn das Erzeugen eines Threads ist eine
  teure Operation und das System wird damit bis an seine Grenzen belastet. Der
  Test läuft so lange, bis kein Speicher für einen zusätzlichen Thread mehr
  verfügbar ist. Auch in dieser Situation muss Topsy weiterlaufen und andere
  Protection Domains dürfen dadurch nicht beeinträchtigt werden. Zuletzt muss
  der Speicher aller Threads wieder freigegeben werden, damit der Test
  mehrmals durchlaufen kann.
  
  \item \codebf{random [\#threads] [\#msgs]} \\
  Startet eine Anzahl Threads (Argument \code{[\#threads]}), welche eine Anzahl
  Nachrichten (Argument \code{[\#msgs]}) versenden. Die Nachrichten werden mit
  pseudozufälligen Daten gefüllt. Die meisten Nachrichten und Destinationen sind
  also ungültig.
  
  Der Test gilt als erfolgreich, wenn Topsy nicht abstürzt oder hängen bleibt. 
  
  \item \codebf{memory} \\
  Dieser Test besteht aus zwei Phasen. In der ersten werden achtzehn Threads
  gestartet, welche jeweils von einer bestimmte Stelle im Memory lesen. In der
  zweiten Phase werden zwanzig Threads einer leeren Funktion gestartet.
  
  Die erste Phase versucht systematisch von grenzfälligen Stellen im Memory zu
  lesen. In einigen Fällen darf dies nicht erlaubt sein, und die Threads müssen
  ordnungsgemäss beendet werden. 
  
  Die zweite Phase lasse ich lieber beiseite.

  \item \codebf{trap} \\
  \code{trap} besteht auch aus zwei Phasen, hat sonst aber nichts mit dem
  \code{memory}-Test gemeinsam. Zuerst werden achtzehn Threads gestartet, wobei
  die übergebenen Funktionspointer auf verschiedene Stellen im Speicher zeigen.
  In der zweiten Phase werden 48 Threads kreiert und die Funktionspointer
  zufällig gewählt.
  
  Dieser Test beansprucht vor allem das Thread Management und Exception
  Handling. Der TM-Thread muss robust gegen fehlerhafte Parameter sein und darf
  ihnen nicht blind vertrauen. Wenn die Threads tatsächlich gestartet werden,
  tritt
  meistens sofort eine Exception auf. Diese muss unter allen Bedingungen korrekt
  gehandhabt werden.
  
  Topsy muss alle Testthreads beenden, was nur gelingt, wenn alle Exception
  Handler richtig reagieren. Der Test gilt als bestanden, wenn er durchläuft,
  ohne dass Topsy beeinträchtigt wird.
  
\end{itemize}



% 
% \subsection{Constraints}
% \label{evaluation:methodology:constraints}
% 
% Document your constraints.
% 
% \subsection{Setup}
% \label{evaluation:methodology:setup}
% 
% Document your evaluation setup.
% 
% \subsection{Procedure}
% \label{evaluation:methodology:procedure}
% 
% Document how you carried out your evaluation.
% 
% \subsection{Limitations}
% \label{evaluation:methodology:limitations}
% 
% Show that you have understood the problem totally -- and
% theoretically justify where the limitations of your achievements
% are expected.

\section{Resultate}
\label{evaluation:results}

% Present your evaluation results.  Show nice graphs -- people like
% that ;)

% Dieses Kapitel beschreibt die erzielten Resultate aller Methoden zur Verifikation.

Nachdem nun alle Methoden der Verifikation vorgestellt wurden, beschreibt 
dieses Kapitel die erzielten Resultate. In allen Tests standen dem System 12MB 
an Speicher zur Verfügung. Dies ist nahe am absoluten Minimum gewählt, damit 
allfällige Memory Leaks früher entdeckt werden.


\subsection{Langzeittests}

Ich habe Topsy sowohl in Bochs als auch im VMPlayer über drei Tage laufen lassen. In
90 Stunden wurden in Bochs 287'897'998 Nachrichten versendet und das System
wurde 637'391'608 Mal unterbrochen, 7'784'271 waren verschachtelte
Unterbrechungen. VMWare ist noch ein bisschen schneller, die Zahlen
dürften noch etwas höher liegen. 

Diese Zahlen zeigen eindrücklich, wieviel geschieht, obwohl die Shell
nichts zu tun hat. Dies ist zwar gut für die Aussagekraft des Tests, zeigt aber
zugleich auch, dass die Shell und das IO Subsystem noch verbessert werden
können. 




\subsection{Lasttests}
\label{evaluation:results:crashme}

Die Tests des Tools \crashme{} wurden mehrmals durchlaufen. Die Resultate und
deren Interpretation folgen zu jedem Modul separat.

Die Tests wurden nacheinander und in keiner speziellen Reihenfolge ausgeführt.


\subsubsection{\code{syscall}}

Zum \code{syscall} Test gibt es nicht viel zu sagen. Wenn diese Syscalls nicht
funktionieren würden, könnte man das System gar nicht aufstarten und den Test
ausführen. 

Der Test ist wohl eher zur Dokumentation der Syscalls geschrieben worden. 



\subsubsection{\code{stress}}

Bei 12MB Speicher ist nicht mehr viel Platz für zusätzliche Prozesse verfügbar. Der
Test kann dennoch fast 600 neue Threads anlegen. Wenn der Speicher voll und keine Page
mehr verfügbar ist, reagiert Topsy unterschiedlich, je nach dem, wo es sich
gerade befindet. Folgende Varianten konnten beobachtet werden:
\begin{enumerate}[\label=\bf a)]
  \item Beim Syscall an den MM-Thread, der eine Enveloppe an eine
  Nachricht anhängt, hält das System mit einer \code{PANIC} an. Diese wird in
  der Funktion \code{vm\_loan\_alloc()} des Loan Managers ausgelöst, weil dieser
  keine freie physikalische Page mehr findet.
  
  Der limitierende Faktor in diesem Fall ist also das physikalische Memory,
  obwohl jeder Thread nur drei Pages braucht: zwei für den KMS und einen
  für die erste Seite des UMS. Da der UMS sehr grosszügig dimensioniert ist,
  ändert sich die Situation schon ab etwa 200MB verfügbarem Speicher. Dann wird
  der VM des PDs, in dem all diese Threads laufen, zur limitierenden Ressource,
  da alle Threads den Platz für ihren UMS reservieren müssen.
  
  \item Wenn die \code{PANIC} auskommentiert und stattdessen ein Fehler
  zurückgegeben wird, schlägt das Anlegen des Stacks für einen neuen Thread
  fehl. Die Erstellung wird daraufhin abgebrochen und alle Threads terminieren.
  
  Anhand der Memory Mappings im PD des TM-Threads und der Shell kann man 
  erkennen, dass alle Stacks der Threads wieder entfernt wurden. Der Test kann 
  beliebig oft wiederholt werden, es werden gleich viele neue Threads angelegt
  und 
  wieder beendet. Es ist also sehr unwahrscheinlich, dass in diesem 
  Code ein Memory Leak existiert.
  
\end{enumerate}

\paragraph{Lösungsmöglichkeit:} Bei vollem Speicher einfach das Allozieren 
fehlschlagen zu lassen, ist riskant. Es müsste stattdessen möglichst schnell 
Speicher freigegeben werden, indem zum Beispiel der speicherintensivste User 
Thread beendet wird. Da mir die Zeit für die Implementation und Evaluation 
eines solchen Mechanismus nicht reicht, lasse ich die \code{PANIC} stehen.


\subsubsection{\code{random}}
\label{evaluation:results:random}

Die zufällig generierten Nachrichten, die der \code{random} Test verschickt,
gehen auch an einen zufälligen Thread, der nicht existieren muss. Solange die
adressierten Threads existieren, läuft der Test gut und die Threads verwerfen
die Nachrichten zumeist schon wegen des unbekannten Typs.

Wenn der Empfänger einer Nachricht nicht existiert, hält der Message Dispatcher
\code{msgDispatcher()} das System mit einer \code{PANIC} an. Dies ist meiner
Meinung nach die falsche Reaktion. Es kann sein, dass der Empfänger einer
Nachricht nicht (mehr) läuft. Es muss auch eine Möglichkeit geben, dies
einfach zu prüfen. Es würde reichen, in solchen Fällen eine Fehlermeldung
zurückzugeben. 

Der Message Dispatcher ist Teil des architektur-unabhängigen Codes. Deshalb und
aus Zeitmangel lasse ich ihn so stehen. 


\subsubsection{\code{memory}}

11 der 18 Threads werden beim Lesezugriff beendet, die anderen laufen weiter, da
die übrigen Adressen für die Threads lesbar sind. Das Exception Management
funktioniert also.

Man sollte den Test noch erweitern und den Schreibzugriff testen. Es müssten
dann mehr Threads vorzeitig beendet werden.



\subsubsection{\code{trap}}

Alle Threads werden erfolgreich beendet. Die meisten verursachen schon beim
Laden der ersten Instruktion einen Page Fault und werden beendet. Ansonsten
treten viele verschiedene Exceptions auf. Dabei sind viele ``General Protection
Exceptions'', für die die Spezifikation \cite{bib:ia32spec} 31 verschiedene
Ursachen auflistet, einige ``Invalid Opcode Exceptions'' und wenige andere.


\subsection{Zusammenfassung}

Topsy besitzt zwar keine Unit Tests\footnote{Unit Tests sind Prozesse, welche 
benutzt werden, um Codestücke individuell auf ihre Korrektheit zu prüfen.}, mit 
Hilfe der hier vorgestellten Tools können aber aussagekräftige Resultate 
erzielt werden. Mittels des \crashme{} Programms und der Assert-Statements kann 
das System als Ganzes geprüft und können Regression Tests implementiert werden.

Natürlich sind die hier vorgestellten Resultate kein Korrektheits-Beweis. Sie
liefern aber starke Indizien für die Korrektheit des Codes und zeigen einige
klare Limitationen, welche in Zukunft behoben werden müssen.



