\documentclass[10pt,a4paper]{article}
\usepackage[latin1]{inputenc}
\usepackage{amsmath}
\usepackage{amsfonts}
\usepackage{amssymb}

\usepackage{enumerate}
\usepackage{listings}
\usepackage{url}
\usepackage{float}

\usepackage[dutch]{babel}

\lstset{
language=C,
numbers=left,
numberstyle=\footnotesize,
stepnumber=1,
numbersep=5pt,
showspaces=false,
showstringspaces=false,
showtabs=false,
tabsize=4,
captionpos=t,
breaklines=false
}

% dot graphs
\usepackage{dot2texi}
\usepackage{tikz}
\usetikzlibrary{shapes,arrows}

\title{Operating systems opdracht 2: Scheduling}
\author{Sander van Veen \& Tadde\"us Kroes}

\begin{document}

\maketitle

\tableofcontents

\pagebreak

\section{Inleiding}

Scheduling heeft te maken met het toewijzen van hulpbronnen aan processen. Als 
dit op een ``eerlijke'' en effici\"ente manier gebeurt kan dit een grote 
positieve invloed hebben op de systeemprestaties. \\
\\
In deze opdracht hebben wij enkele resource schedulers gemaakt,
hierbij is onderscheid gemaakt tussen drie soorten hulpbronnen:

\begin{enumerate}
	\item \textbf{Geheugen}. De high-level scheduler bepaald in welke volgorde
	      de processen geheugen krijgen toegewezen.
	\item \textbf{CPU}. De CPU-scheduler regelt hierbij op welk moment en hoe
	      lang een proces gebruik mag maken van de processor.
	\item Drie \textbf{I/O devices}. De scheduling hiervan wordt geregeld door
	      de aangeleverde simulator. Via de shell van de scheduler kunnen de te
		  simuleren load-factoren worden ingesteld.
\end{enumerate}

\subsection{High-level schedulers}

\noindent De high-level schedulers die we hebben getest zijn:
\begin{enumerate}
	\item \textbf{First come, first served (FCFS)} \\
	      Aan het proces dat als eerste aankomt (en dus al het langste wacht)
	      wordt als eerste geheugen toegewezen.
	\item \textbf{Shortest job first (SJF)} \\
	      Het proces wat het minste geheugen nodig heeft, krijgt voorrang.
	\item \textbf{First come and fit, first served (FCFFS)} \\
	      Alle processen worden doorlopen op volgorde van aankomsttijd. Als
	      er voldoende geheugen beschikbaar is, wordt dat toegewezen.
	\item \textbf{Shortest job first (SJF) met fairness} \\
	      Het proces wat de minste hulpbronnen nodig heeft, krijgt voorrang
	      tenzij er een proces is met een threshold groter dan een van tevoren
	      gespecificeerde constante. De threshold van een proces wordt opgehoogd
	      wanneer het proces wel het minste geheugen nodig heeft, maar niet meer
	      in het geheugen past. Hierdoor krijgt een proces wat al een aantal
	      keren onfortuinlijk niet in het geheugen heeft gepast, voorrang op een
	      eventueel ``korter'' proces, waardoor starvation\footnote{Starvation
	      betekent dat een proces nooit wordt afgehandeld, omdat er geen 
	      hulpbronnen voor beschikbaar komen. Zie ook: \\
	      \url{http://en.wikipedia.org/wiki/Starvation_(computing)}} wordt
	      voorkomen.
\end{enumerate}

\subsection{CPU schedulers}

Ook hebben we verschillende CPU schedulers:
\begin{enumerate}
	\item \textbf{First come, first served (FCFS, is aangeleverd)} \\
	      Aan het proces dat als eerste aankomt, wordt als eerste CPU-tijd
	      toegewezen. Het volgende proces is pas aan de beurt als het huidige
	      proces is afgehandeld.
	\item \textbf{Round robin (RR)} \\
	      Hierbij wordt de rij in circulaire volgorde doorlopen, waarbij telkens
	      een vast aantal tijdseenheden aan CPU-tijd wordt toegewezen aan een
	      proces. Hierdoor zullen kortere processen altijd in minder tijd 
		  worden afgerond.
	\item \textbf{Longest job first (LJF)} \\
	      De processen die het meeste geheugen nodig hebben, zullen
	      waarschijnlijk ook de meeste CPU-tijd nodig hebben. Deze processen
	      krijgen daarom als eerste tijdsintervallen CPU-tijd toegewezen. Dit
	      zou als gevolg moeten hebben dat de processen min of meer tegelijk
	      worden afgerond.
\end{enumerate}

\section{Testomgeving (simulator)}

Door de practicumleiding is een simulatieprogramma aangeleverd, dat wij hier
kort zullen toelichten. 

\subsection{Procesrijen}
\begin{itemize}
	\item \texttt{new\_proc}: \\
	      Hier worden nieuwe processen geplaatst door de simulator.
	\item \texttt{ready\_proc}: \\
	      Wanneer er geheugen wordt toegewezen aan een proces in de
	      \texttt{new\_proc} rij, wordt dit proces naar de \texttt{ready\_proc}
	      rij verplaatst.
	\item \texttt{io\_proc}: \\
	      Wanneer aan een proces een I/O-device wordt toegewezen, wordt dit
	      proces in de \texttt{io\_proc} rij geplaatst. Wanneer de tijd met het
	      I/O-device erop zit, wordt het proces weer terug in de
	      \texttt{ready\_proc} geplaatst.
	\item \texttt{defunct\_proc}: \\
	      Be\"eindigde processen worden in deze rij geplaatst, zodat ze kunnen
	      worden opgeruimd m.b.v. \texttt{rm\_process}.
\end{itemize}

\begin{figure}[H]
	\begin{dot2tex}[dot,options=-tmath --autosize --cache]
	digraph G {
		rankdir=LR;

		start [shape="none",label=""];

		start -> q_{new};
	
		q_{new} -> q_{ready} [label="NewProcess"];
		q_{ready} -> q_{io} [label="I/O"];
		q_{ready} -> q_{defunct} [label="Finish"];
		q_{ready} -> q_{ready} [label="Time"];
		q_{io} -> q_{ready} [label="Ready"];
	}
	\end{dot2tex}
	\caption{De transities tussen de procesrijen. De knopen zijn de procesrijen, 
	         bij de pijlen staan de namen van de events waardoor de transities 
			 tussen de desbetreffende rijen plaatsvinden.}
\end{figure}

\subsection{Discrete Event Simulation}
Bij een Discrete Event Simulation (DES)\footnote{Zie ook:
\url{http://en.wikipedia.org/wiki/Discrete_Event_Simulation}} worden events in
chronologische volgorde afgehandeld. De event handler in deze simulator is de
functie \texttt{schedule} (\emph{scheduler.c}). Deze handler kan de
volgende soorten events afhandelen:
\begin{itemize}
	\item \texttt{NewProcess\_event}: \\
	      Er is een nieuw proces in de \texttt{new\_proc} rij bijgeplaatst door
	      de simulator.
	\item \texttt{Time\_event}: \\
	      Het lopende proces heeft zijn time-slice opgebruikt. Wordt gegenereerd
	      wanneer \texttt{set\_slice} is aangeroepen.
	\item \texttt{Ready\_event}: \\
	      Een proces is klaar met I/O en is weer achteraan de
	      \texttt{ready\_proc} rij geplaatst.
	\item \texttt{IO\_event}: \\
	      Het lopende proces gaat I/O doen en is achteraan de \texttt{io\_proc}
	      rij geplaatst.
	\item \texttt{Finish\_event}: \\
	      Het lopende proces is be\"eindigd en in de \texttt{defunct\_proc} rij
	      geplaatst.
\end{itemize}

\subsection{Implementatie van de schedulers}
Elke scheduler is ge\"implementeerd in een aparte functie, met de functienaam
\texttt{GiveMemory\_\{type\}} (high-level scheduler) of \texttt{CPU\_\{type\}} 
(CPU-scheduler). Het programma vraagt de gebruiker om de high-level en de 
CPU-scheduler te kiezen via \texttt{stdin}. Aan de hand van deze invoer worden 
twee functie-pointers toegewezen aan de functies die corresponderen met de 
gekozen schedulers. Deze pointers worden aangeroepen tijdens de executie van 
het programma. \\
\indent De constante \texttt{FAIRNESS\_THRESHOLD} hebben we op 3 bepaald. We ondervonden namelijk dat bij hogere en lagere waarden de resultaten minder goed waren.

\subsection{Variabelen}
De simulator heeft zes variabelen die worden ingevoerd via \texttt{stdin}:
\begin{enumerate}
	\item De load van het geheugen die niet wordt veroorzaakt door de
	      gesimuleerde processen.
	\item De load op de processor die niet wordt veroorzaakt door de
	      gesimuleerde processen.
	\item De load op de I/O-devices die niet wordt veroorzaakt door de
	      gesimuleerde processen.
	\item Het aantal te simuleren processen.
	\item De high-level scheduler die moet worden gebruikt (vier keuzes).
	\item De CPU-scheduler die moet worden gebruikt (drie keuzes).
	\item Het aantal tijdseenheden van de time-slice voor de CPU scheduler.
\end{enumerate}

\subsection{Statistieken}
Aan de hand van bovengenoemde variabelen geeft de simulator een aantal
statistieken terug. Deze statistieken bevatten de volgende informatie:
\begin{itemize}
	\item Het aantal be\"eindigde processen.
	\item De gebruikte tijd op en utilisatie van de CPU.
	\item De gebruikte tijd op en utilisatie van alle I/O-devices.
	\item De wachttijd op geheugentoewijzing van alle processen (grafiek).
	\item De wachttijd op eerste CPU cycle van alle processen (grafiek).
	\item De executietijd vanaf geheugentoewijzing van alle processen (grafiek).
	\item De totale verwerkingstijd van alle processen (grafiek).
\end{itemize}

\section{Experiment}

We hebben ervoor gekozen om de volgende punten te vergelijken met behulp van
grafieken:
\begin{enumerate}
	\item \emph{Verschil tussen high-level schedulers:} \\
	      De wachttijd op geheugentoewijzing wordt bepaald door de gebruikte
	      high-lever scheduler en door de load op het systeem. Daarom zetten we
	      oplopende waarden van de load factor uit tegen de gemiddelde wachttijd
	      op geheugentoewijzing.
	\item \emph{Fairness van de CPU-schedulers:} \\
	      We zetten de gemiddelde wachttijd tot de eerste CPU cycle per proces
	      uit tegen de load factor van het systeem. Hiermee kunnen we zien welke
	      CPU-scheduler het ``eerlijkst'' is. Dat wil zeggen dat elk proces
		  binnen een acceptabele tijd zijn eerste CPU cycle krijgt toegewezen.
	      Hierdoor is te zien wanneer starvation optreedt.
	\item \emph{Verschil in executietijd tussen de CPU-schedulers:} \\
	      We zetten de gemiddelde executietijd vanaf geheugentoewijzing uit
	      tegen de CPU- en I/O-load factor (de memory load houden we hetzelfde,
	      deze heeft immers alleen effect op de tijd v\'o\'or 
		  geheugentoewijzing).
	\item \emph{Verschillende combinaties van high-level en CPU-schedulers:} \\
	      We willen weten of het uitmaakt in welke combinatie de high-level en
	      CPU-schedulers worden gebruikt. Hiervoor zetten we de gemiddelde
	      totale executietijd uit tegen de load-factor van het systeem.
\end{enumerate}

\pagebreak

\section{Resultaten}

Met behulp van een bash-script (\emph{benchmark.bash}; zie appendix B.2)
hebben we data gegenereerd en verzameld voor verschillende variabelen. Deze
data hebben we in grafieken omgezet, waarbij we observaties hebben gedaan. \\
\\
Vanaf een load-factor van 0.55 is er pas een significant verschil te zien
tussen de verschillende schedulers. Daarom hebben we de metingen met een
lagere load factor dan 0.50 weggelaten uit de grafiek. \\
\\
Merk ook op dat er geen data is gegenereerd voor een load factor van 1.00.
De simulation accepteert dit niet als waarde, omdat processen dan geen
hulpbronnen kunnen krijgen toegewezen.

\subsection{Verschil tussen high-level schedulers}
\begin{figure}[H]
\hspace{-.82in}
\includegraphics{../plot/high-level.pdf}
\caption{Gemiddelde tijd voor de geheugentoewijzingen voor elke high-level
schedulers onder verschillende load-factoren.}
\end{figure}

We zien dat FCFS slechte resultaten boekt, naarmate er minder hulpbronnen 
beschikbaar zijn (hogere load factor). FCFFS heeft hier minder last van, waar
we bij SJF (zowel met als zonder fairness) vrijwel geen stijging zien vanaf 
een load factor van circa 0.65. \\
\\
We zien bij SJF zonder fairness echter wel twee grote pieken, waar deze pieken
bij SJF met fairness ontbreken. Het gebruik van de fairness threshold zorgt
ervoor dat processen die deze pieken veroorzaken wel binnen acceptabele tijd
geheugen krijgen toegewezen. 

\subsection{Fairness van de CPU-schedulers}
\begin{figure}[H]
\hspace{-.75in}
\includegraphics{../plot/first-cycle.pdf}
\caption{Gemiddelde wachttijd op eerste CPU cycle voor elke 
CPU-schedulers onder verschillende load-factoren.}
\end{figure}

LJF heeft meer en hogere pieken dan FCFS en RR. Dit verklaren wij door
het feit dat bij LJF korte processen niet snel aan de beurt komen, als
er langere processen blijven worden toegevoegd. Hoewel RR in principe
de ``eerlijkste'' is, lijkt FCFS gemiddeld een lagere wachttijd te hebben.
Het zou echter kunnen dat de grafiek er anders uit zal zien, bij een andere 
time-slice en een andere hoeveelheid processen. Wij hebben deze variabelen
echter gelijk gehouden omdat we de resultaten in dezelfde omgeving willen
vergelijken. \\
\\
Het is te verwachten dat FCFS hier het beste resultaat behaald, omdat een
proces nooit naar achteren in de wachtrij wordt geplaatst. Bij LJF zien
wij een grotere spreiding. Dit komt doordat sommige (langere) processen
snel aan de beurt komen en andere (kortere) processen juist later. Bij RR
wordt elke proces na elke time-slice achteraan de wachtrij geplaatst, dus
is het logisch dat hier de minste spreiding is te zien.\\
\\
Het valt ons op dat rond een load factor van 0.65 de wachttijd voor de 
eerste CPU cycle een piek lijkt te hebben. Wij kunnen hier geen andere
verklaring voor bedenken dan dat de aangeleverde simulator geen volledig
willekeurige processenreeks nabootst. We zouden namelijk verwachten dat
er geen daling in de grafiek voorkomt, want als er meer load op de CPU staat,
verwachten wij dat de gemiddelde wachttijd hoger is. Dit vermoeden wordt
versterkt door het feit dat we in de grafiek van het vorige resultaat ook
een afwijking rond deze load factor zien. Daarom zullen we in de volgende
resultaten de afwijkingen rond deze load factor negeren.

\subsection{Verschil in executietijd tussen de CPU-schedulers}
\begin{figure}[H]
\hspace{-.68in}
\includegraphics{../plot/run-time.pdf}
\caption{Gemiddelde executietijd vanaf geheugentoewijzing voor elke 
CPU-schedulers onder verschillende I/O- en CPU load-factoren, met constante 
memory load-factor.}
\end{figure}

Hoewel in kleine mate, zien we dat FCFS de langste gemiddelde executie-tijd 
heeft. Dit is dan ook het minst slimme algoritme. We zien ook dat de 
variatie in de load factor geen verschil maakt in executietijd tussen de
verschillende CPU-schedulers. \\
\\
Tenslotte observeren we dat LJF het beste lijkt te presteren. Dit zou kunnen
betekenen dat de meeste processen rond de gemiddelde executietijd zitten.

\subsection{Verschillende combinaties van high-level en CPU-schedulers}
\begin{figure}[H]
\hspace{-.82in}
\includegraphics{../plot/total-time-1.pdf}
\caption{Gemiddelde totale verwerkingstijd voor elke high-level en 
CPU-schedulers onder verschillende load-factoren.}
\end{figure}

Het eerste wat ons opvalt is dat de grafiek dezelfde vorm heeft als de grafiek
van het eerste resultaat (de high-level schedulers). \\
\\
Bij alledrie de lijngroepen zien we dat bij RR scheduling de combinatie het
meest stabiel vari\"eert van de groep, bij verschillende load-factoren. \\
\\
Om het verschil tussen SJF met en zonder fairness beter te kunnen zien, hebben
we het onderste gedeelte van de grafiek voor beide types apart getoond in de 
volgende grafieken. \\
\\
Meegenomen het feit dat we de afwijking in de grafiek rond de load-factor van
0.65 negeren, zien we dat de load-factor weinig effect heeft op de executietijd
bij een SJF high-level scheduler (zowel met als zonder fairness).

\pagebreak

\begin{figure}[H]
\hspace{-.85in}
\includegraphics{../plot/total-time-2.pdf}
\caption{Gemiddelde totale verwerkingstijd voor de SJF high-level scheduler
en elke CPU-scheduler onder verschillende load-factoren.}
\end{figure}

\begin{figure}[H]
\vspace{-0.5cm}
\hspace{-.85in}
\includegraphics{../plot/total-time-3.pdf}
\caption{Gemiddelde totale verwerkingstijd voor de SJF high-level scheduler
met fairness en elke CPU-scheduler onder verschillende load-factoren.}
\end{figure}
\noindent
We zien duidelijk dat SJF met fairness minder hoge pieken heeft en dat het
verschil tussen de CPU-schedulers kleiner is. Dit kunnen we verklaren doordat
er zonder fairness meer uitschieters zijn wat betreft wachttijd op 
geheugentoewijzing. De fairness threshold voorkomt deze uitschieters.

\section{Conclusies}

Uit bovenstaande waarnemingen hebben wij de volgende conclusies getrokken.

\subsection{High-level scheduling}

\begin{enumerate}
	\item Shortest job first (SJF) is de beste keuze voor een high-level
	      scheduler, omdat deze nauwelijks afhankelijk is van de load-factor
		  van het gehele systeem. Het gebruik van FCFS is een slecht idee,
		  zeker bij een hoge load-factor.
	\item Het toevoegen van een fairness threshold zorgt voor een stabielere
	      wachttijd op geheugentoewijzing.
	\item De keuze van een high-level scheduler heeft de meeste invloed op 
	      de totale performance van procesafhandeling.
\end{enumerate}

\subsection{CPU-scheduling}

\begin{enumerate}
	\item RR is het ``eerlijkst'', want deze scheduler maakt geen onderscheid
	      tussen processen in de volgorde van het toewijzen van hulpbronnen.
	\item Longest job first (LJF) is gevoelig voor starvation. Dit komt door
	      het feit dat korte processen niet gegarandeerd aan de beurt komen,
		  doordat het voor kan komen dat er langere processen bij blijven 
		  komen.
	\item Het verschil in load-factor brengt geen verschil in effici\"entie 
	      tussen de CPU-schedulers.
\end{enumerate}

\subsection{Eindconclusie}

Shortest job first is de beste keuze van de geteste high-level schedulers.
Het toevoegen van een fairness threshold helpt hier bij om uitschieters te 
voorkomen. Deze high-level scheduler met een Round Robin CPU-scheduler is 
het beste van de geteste combinaties.

\section{Opmerkingen}

Het gebruik van processtatistieken (denk aan het aantal gebruikte
CPU time-slices en een nice-factor) zou kunnen bijdragen aan een
beter presterende scheduler.

\pagebreak
\appendix

\section{Code}

\subsection{scheduler.c}

\lstinputlisting{../scheduler.c}

\pagebreak

\subsection{benchmark.bash}

\lstinputlisting[tabsize=2]{../benchmark.bash}

\end{document}
