24.5 Prozesse, Scheduling und Prioritäten
In diesem Abschnitt wollen wir uns nun den Prioritäten von Prozessen und Threads widmen. Für eine normale Workstation ist dieses Thema zwar eher weniger wichtig, für kleine bis große Servermaschinen, Mainframes oder generell Mehrbenutzersysteme können Prioritäten jedoch sehr wichtig werden.
24.5.1 Das Scheduling
Die meisten Menschen haben beim Thema »Prioritäten« etwas verquere Vorstellungen: Sie möchten am liebsten alles mit einer hohen Priorität versehen, um alles zu beschleunigen. Jedoch werden durch das Setzen von entsprechenden Zahlenwerten keine neuen Ressourcen geschaffen, es kann im Gegenteil nur über eine eventuelle Bevorzugung von einigen Prozessen entschieden werden.
Aus dem ersten Kapitel dürfte Ihnen noch bekannt sein, dass der Scheduler den nächsten abzuarbeitenden Prozess auswählt. Prioritäten können diesen Prozess in gewisser Weise beeinflussen. Bevor wir nun auf die Details eingehen, möchten wir den Scheduling-Vorgang noch einmal hinsichtlich der Prioritäten erläutern.
Linux kennt drei verschiedene Scheduling-Policies und unterscheidet statische und dynamische Prioritäten. Jedem Prozess ist dabei eine statische Priorität zugeordnet, die über Systemcalls jedoch geändert werden kann.
Der Scheduler verwaltet dabei für jede Priorität im Bereich von 0 bis 99 eine Liste – eine sogenannte Wait Queue oder auch Warteliste – mit lauffähigen Prozessen. Will der Scheduler nun den nächsten abzuarbeitenden Prozess heraussuchen, so nimmt er den ersten Prozess aus der ersten nicht-leeren Liste mit der höchsten Priorität. Die Scheduling-Policy legt schließlich für jeden Prozess fest, wo er in der Liste seiner (statischen) Priorität wieder eingeordnet wird und wie er sich in der Liste bewegt.
SCHED_OTHER
Die normale Scheduling-Strategie ist dabei SCHED_OTHER. Sie gilt für alle Prozesse der statischen Priorität 0, die keine besonderen Erweiterungen für die Echtzeit benötigen. Dieses Scheduling nutzt das Zeitscheibenverfahren, bei dem jedem Prozess eine bestimmte Zeitdauer zugeteilt wird. Nach Ablauf dieser Zeitscheibe wird der aktuelle Prozess unterbrochen und ein anderer darf stattdessen arbeiten.
In diesem Zusammenhang wirken auch die dynamischen Prioritäten, auch Nice-Werte genannt. Diese Prioritäten können wieder über Syscalls (nämlich nice() beziehungsweise setpriority()) als Werte zwischen –20 und 20 vergeben werden, wobei –20 für die höchste zu vergebende Priorität steht.
Allerdings kann nur root seine Prozesse beschleunigen, indem er ihnen einen niedrigeren Nice-Wert als 0 zuordnet. Normale Benutzer können nur »nett« sein, also die Priorität ihrer Prozesse herabsetzen und damit weniger Ressourcen in Anspruch nehmen. Über diese dynamischen Prioritäten wird auch die Fairness des Schedulings geregelt:
Wird nämlich bei der Auswahl des nächsten abzuarbeitenden Prozesses ein lauffähiger Prozess übergangen, so wird seine dynamische Priorität etwas erhöht, damit er beim nächsten Mal bessere »Chancen« hat.
24.5.2 nice und renice
Bevor wir uns nun dem Echtzeit-Scheduling widmen, wollen wir zuerst das für den normalen Benutzer interessante Handling der Nice-Werte erläutern. Schließlich werden auf »normalen« Systemen die Echtzeitfähigkeiten in der Regel nicht genutzt.
Das Kommando »nice«
nice
Die Setzung der Nice-Werte erfolgt beim Start eines Programms in der Shell mit dem nice-Kommando. nice kommt aus dem Englischen und bedeutet so viel wie »nett«, und nett ist man ja, wenn man freiwillig auf Rechenzeit verzichtet. Dem Kommando wird die Priorität über den Parameter -n mitgeteilt, das eigentliche Kommando wird nachstehend mit allen Aufrufargumenten beschrieben:
$ nice -n 19 find / -name libcurses.a >Ergebnis
Listing 24.26 nice in Aktion: Unser find hat Zeit.
Bereits laufende Prozesse
renice
Die dynamische Priorität bereits laufender Prozesse wird mit dem Kommando renice verändert. Dabei kann mit den Parameter -p die Priorität über die Prozess-ID, mit dem -u-Parameter über den Benutzernamen oder mit -g über die Benutzergruppe geändert werden.
Im folgenden Listing wird die Nice-Priorität des Prozesses mit der PID 491 um den Wert 10 verringert. Das Gleiche gilt für alle Prozesse des Benutzers »nobody«.
$ renice +10 -p 491 -u nobody
Listing 24.27 renice für Benutzer und PIDs
24.5.3 Echtzeit-Scheduling unter Linux
Die beiden Echtzeit-Strategien des Schedulings unter Linux (SCHED_FIFO und SCHED_RR) nutzen jeweils die statischen Prioritäten 1 bis 99. Mit anderen Worten wird ein entsprechender Prozess, sofern er lauffähig ist, auch jeden anderen normalen Prozess verdrängen. <Das war schließlich die Semantik der unterschiedlichen Prioritäten, und »normale« SCHED_OTHER-Prozesse nutzen nun mal nur die geringste statische Priorität.>
Prioritäten größer null und damit alle Echtzeit-Features können nur von root beziehungsweise seinen Prozessen genutzt werden.
SCHED_FIFO ist nun die einfachere der beiden Echtzeit-Strategien. Sie funktioniert wie folgt: Wenn ein solcher Prozess von einem Prozess mit einer höheren statischen Priorität verdrängt wird, bleibt er am Anfang seiner Liste und wird erst wieder ausgeführt, wenn alle Prozesse mit höherer Priorität beendet beziehungsweise geblockt sind.
Wird ein solcher Prozess – z. B. nach einem blockierenden Syscall – wieder lauffähig, so wird er am Ende der Liste mit allen Prozessen seiner Priorität einsortiert. Ein Aufruf der Syscalls sched_setscheduler() oder sched_setparam() wird den entsprechenden Prozess wieder an den Anfang der Warteliste bringen und so möglicherweise auch den aktuellen Prozess unterbrechen. Ein Aufruf von sched_yield() dagegen wird den Prozess wieder an das Ende der Liste setzen. Ein SCHED_FIFO-Prozess läuft also, bis er durch einen I/O-Request blockiert, durch einen höher priorisierten Prozess verdrängt oder durch einen Aufruf der Funktion sched_yield() dazu gebracht wird, freiwillig die Ressource Prozessor freizugeben.
SCHED_RR ist nun eine einfache Erweiterung von SCHED_FIFO: Es gibt zusätzlich nämlich noch eine begrenzte Zeitscheibe, die ein Prozess nutzen kann, bevor er wieder an das Ende seiner Warteliste gesetzt wird. Wird ein solcher Prozess von einem höher priorisierten Task unterbrochen, so verbleibt er am Anfang seiner Liste, um später den Rest seiner Zeitscheibe noch zu vollenden.
Egal welche Scheduling-Strategie oder -parameter man nun einsetzt, ein Kindprozess wird diese immer durch fork() erben. Wer aber nun wirklich entsprechende Applikationen entwickeln will, sollte einen Blick auf die Manpages von sched_setscheduler() und sched_setparam() werfen. Dort wird die Thematik noch einmal vertieft, und natürlich wird auch erläutert, was alles schiefgehen kann. So wird beispielsweise empfohlen, bei der Entwicklung entsprechender Programme diese nur unter einer noch höher priorisierten Shell zu testen, da ansonsten bei einer Endlossschleife kein anderer Prozess mehr an die Reihe kommt und so das ganze System hängt. <Also kann auch diese Gefahr als ein Grund dafür angesehen werden, dass nur root solche Prozesse starten darf.>
Man sollte bei diesem Thema jedoch immer beachten, dass Linux ein Mehrzwecksystem ist und nicht dafür entworfen wurde, harte Echtzeit-Anwendungen zu unterstützen. Bei diesen müssen Deadlines – d. h. Zeitpunkte, bis zu denen eine Aufgabe ausgeführt werden muss – in jedem Fall eingehalten werden, ansonsten gibt es, wie beispielsweise in einem Atomkraftwerk, eine Katastrophe.
Schließlich wurde Linux auf die durchschnittliche und nicht auf eine worst-case Performance hin optimiert. In diesem Sinne kann Linux höchstens »weiche« Echtzeitanforderungen erfüllen, also eine vorgegebene Deadline in der Regel einhalten, wie dies beispielsweise bei Multimedia-Anwendungen wünschenswert ist.
In der Regel ist das ja auch gerade das, was man will: Die durchschnittliche Antwortzeit auf Interrupts kann zum Beispiel auf Kosten der theoretisch maximal möglichen Zeit verkürzt werden. Wer jedoch wirklich harte Echtzeitanforderungen erfüllen muss, sollte einen Blick auf folgende Projekte werfen:
- RTLinux: http://www.rtlinux.org
- RTAI: http://www.rtai.org
Alternativ gibt es natürlich auch Betriebssysteme wie QNX, die extra für diesen Anwendungsbereich entwickelt wurden.