3 Betriebssytem Windows NT 4.0

 

3.1 Grundlegende Eigenschaften

Windows NT ist seit 1993 in Deutschland erhältlich. Die Version 4.0 wurde 1996 herausgegeben. Der Nachfolger Windows 2000 ist seit Ende 1999 bzw. Anfang 2000 erhältlich.

Windows NT ist ein 32-Bit Betriebssystem mit Multiprocessing und Multithreadingfähigkeit. Windows NT setzt nicht auf DOS auf, sondern ist ein absolut eigenständiges Betriebsystem. Programme werden unter Windows NT in getrennten Speicherbereichen ausgeführt, so daß selbst bei Programmabstürzen das System nicht in Mitleidenschaft gezogen wird.

Jeder Benutzer hat seine eigene Arbeitsumgebung, die er selbst gestalten kann und die ihm bei jeder neuen Anmeldung wieder zur Verfügung steht. In einem Windows NT Netzwerk kann diese Umgebung sogar mit auf andere Rechner wandern. Bei Windows NT kann im Gegensatz zu z.B. LINUX nur ein Benutzer interaktiv mit dem System arbeiten und Programme ausführen.

Windows NT ist kein Multiuser-System, auf dem gleichzeitig viele Benutzer Programme ablaufen lassen können. Gleichwohl können mehrere Benutzer Windows NT als Datei-Server oder Web-Server nutzen.

Bei Server-Lösungen im sicherheitsrelevanten Bereichen, die gegen Angriffe aus dem Internet geschützt werden müssen, kann es sehr sinnvoll sein LINUX statt Windows NT oder SOLARIS in Betracht zu ziehen. Hier kann der erfahrene Administrator bei Bekanntwerden eines Fehlers selbst sofort den Code des Betriebssystem ändern. Bei Windows NT ist man auf den Hotfix von Microsoft angewiesen, der natürlich nicht sofort verfügbar ist.

 

3.2 Prozesse / Threads in Windows NT

In Windows NT ist ein Prozess die laufende Instanz einer Anwendung. Er setzt sich aus Codeabschnitten im Speicher zusammen, die aus Programmen und DLL's geladen wurden. Jeder Prozess verfügt über seinen eigenen Adreßraum und seine eigenen Ressourcen, wie z.B. Threads, Dateien und dynamisch reservierten Speicher. Prozesse selbst führen keinen Code aus; sie stellen den Adressraum dar, in denen sich der Code befindet. Der Code im Adreßraum eines Prozesses wird durch einen Thread abgearbeitet, wobei jeder Windows NT Prozess mindestens einen ausführenden Thread besitzt.

 

Windows NT-Prozesse besitzen verschiedene Merkmale:

Ein Prozess besteht aus mehreren Threads, von denen jeder eine sog. Zeitscheibe vom Windows NT Kernel zyklisch zugewiesen bekommt, während der er abgearbeitet wird. Beendet der letzte Thread seine Aufgabe, so terminiert der Prozess. [HaWi1997]

 

3.3 Zustände von Prozessen / Threads

Prozesse und Threads besitzen feste Momente in ihrem Leben wie Einrichten, Ausführen von Aktionen und Sterben.

Nachdem ein Thread eingerichtet wurde, durchläuft er (nach[Hamilton97]) die folgenden Stadien:

 

 

3.4 Scheduling

In Windows NT wird die Steuerung von Threads durch den sogenannten Dispetcher geregelt. Der Dispetcher verwaltet eine Dispetscher Ready Queue, eine Liste in der alle Threads, die sich im Zustand Ready befinden, ihrer Priorität nach geordnet sind. Jeder Thread erhält einen bestimmten Anteil der CPU-Zeit auch Zeitscheibe genannt. Besitzen mehrer Threads die selbe Priorität so erhalten all diese Threads eine gleich großen Anteil der CPU-Zeit. Threads mit einer geringeren Priorität werden erst berücksichtigt, wenn alle Threads mit einer höheren Priorität abgearbeitet sind.

Die Prioritäten in Windows NT sind in zwei Hauptgruppen unterteilt Echtzeit-Prioritäten (Prioritätswert 16-31) und der Bereich der variablen Priorität (Prioritätswert 0-15). Der einzige Thread mit der Priorität 0 ist der "idle" Thread des Kernel-Thread -Objekts, der auf Deferred-Process-Ojekte wartet. Die meisten Threads besitzen eine Priorität zwischen 1 und 15.

Um ein "Verhungern" des Systems zu vermeiden , werden die variablen Prioritäten bestimmter Threads entweder nach oben gesetzt, z.B. +2, um die Performence des vordersten Tasks zu steigern, oder auch erniedrigt, falls ein Thread Ressourcen zu horten beginnt. Ein stark CPU-lastiger Prozess / Thread, der zudem eine entsprechend hohe Priorität besitzt wird, während jeder aktiven Phase um eins herabgestuft. Langsam begibt er sich wieder auf das Niveau seiner Basispriorität. Dieses Verfahren der dynamischen Prioritätenanpassung macht Windows NT relativ robust.

 

 

3.5 Erzeugung und Steuerung von Prozessen / Threads

In Windows NT 4.0 wird ein Prozess gestartet, sobald eine Anwendung aufgerufen wird. Dieser Prozess eignet sich Speicher, Systemressourcen und Threads an, die er zur Ausführung der Anwendung benötigt. Ein Thread ist in Windows NT die kleinste ausführbare Einheit. Beim Starten eines Prozesses wird automatisch mindestens 1 Thread gestartet. Solange noch ein Thread ausgeführt wird, der mit dem Prozess verknüpft ist bleibt dieser bestehen. Ein Prozess hat also mindestens einen Thread. Oft besitzt ein Prozess aber auch mehrere Threads zu unterschiedlichen Zeitpunkten (siehe Abb. 3.1).

Ein Thread ist aber immer einem bestimmten Prozess zugeordnet und existiert nur innerhalb dieses Prozesses. Anders als in Unix wird in Windows NT zwischen Prozessen und Threads unterschieden. Ein Windows-NT-Thread ist für das System weniger aufwendig als z.B. ein Prozess in Unix. Obwohl in Windows NT Prozesse in vielen Fällen genau so gehandhabt werden können wie Threads befaßt sich diese Diplomarbeit hauptsächlich mit den Threads, die innerhalb eines Prozesses (Anwendung) erzeugt und gesteuert werden, da sie vergleichbar zu den Threads in Java und den "Sohnprozessen" in Unix sind.

Bei Windows NT wird nicht wie bei Unix eine Kopie des aufrufenden Prozesses erzeugt. Beim Aufruf der CreatThread() Funktion wird ein Thread kreiert, der dann eine bestimmte Funktion ausführt.

 


Abb. 3.1: Typischer Windows-NT-Prozess mit seinen Threads [HaWi1997]

 

Funktionen zum Arbeiten mit Threads unter Windows NT die anhand von beispielhaften C/C++ Code erläutert werden:

Zum Erzeugen eines Threads ruft man die Funktion CreateThread() auf.

 

Beispiel CreateThread():

Long WINAPI Print (long Parameter)
{
 ** Hier wird das Programm des Threads ausgeführt **
}

unsigned long nThreadID ;
HANDLE hThread = CreateThread ( NULL,
                                0,
                                (LPTHREAD_START_ROUTINE)Print,
                                (void*)1,
                                0,
                                &nThreadID
                               );

Parameter:

  • Wird hier CREATE_SUSPENDED eingesetzt wartet der Thread bis er durch ResumeThread() aufgerufen wird.
  • Als Rückgabewert dieser Funktion erhält man einen Handle auf den erzeugten Thread. Bei einem Fehler wird NULL zurückgegeben.

     

    Ein Thread Handle sollte nachdem er nicht mehr benötigt wird durch die Funktion CloseHandle() geschlossen werden. Das Betriebssystem schließt einen Handle auch automatisch sobald kein Prozess oder Thread mehr existiert der diesen Handle benötigt.

     

    Beispiel CloseHandle():

    CloseHandle ( hThread );

    Parameter:

     

    Um einen Thread zu beenden gibt es mehrere Methoden.

    Der Thread kann von sich aus die Funktion ExitThread() aufrufen, sich selber beenden und einen Rückgabewert übermitteln.

     

    Beispiel: ExitThread(NO_ERROR);

     

    Die ExitThread() Funktion wird auch automatisch aufgerufen wenn der Thread seinen Programmcode abgearbeitet hat.

     

    Beispiel für einen automatischen Aufruf von ExitThread() :

    long WINAPI Threadfunktion() // Funktion die der Thread ausführt
    {
     for (int i=0; i<3; i++)
     {
       ** In dieser Schleife wird 3 mal etwas ausgeführt **
     }
    return NO_ERROR;                // Hier wird der Thread beendet.
    }                               // Er übergibt NO_ERROR.
    

     

    Durch die Funktion TerminateThread() kann der Thread beendet werden. Diese Methode ist allerdings nicht empfehlenswert, da der Thread hierbei keine Möglichkeit besitzt seine Ausführung solange fortzusetzen, bis er z.B. einen kritischen Bereich verlassen hat, können Fehler in der Synchronisation enstehen.

    Beispiel TerminateThread():

    TerminateThread ( hThread, NO_ERROR );

    Parameter:

     

    Den Exit-Code eines Threads kann man mit der Funktion GetExitCodeThread() erhalten. Wird diese Funktion für einen Thread aufgerufen, der noch nicht beendet ist, liefert diese Funktion STILL_ACTIV zurück.

    Beispiel GetExitCodeThread():

    DWORD dwResult;
    GetExitCodeThread ( hThread, &dwResult );
    if ( dwResult  != NO_ERROR )
    {
     ** Dieser Programmteil wird im Falle eines Fehler ausgeführt **
    }
    

    Parameter:

     

    Ein weiterer wichtiger Punkt mit der Arbeit von Threads und Prozessen in Windows NT ist die jeweilige Priorität. In Windows NT gilt je höher der Prioritätswert, desto "dringender" ist der Prozess bzw. Thread. Diese Priorität kann vom Benutzer verändert werden. Das Betriebssystem hat allerdings auch Einfluß auf die Priorität. In Windows NT besitzt der Programmierer aber einen viel stärkeren Einfluß auf die Systemabhängige automatische Steuerung der Prozesse und Threads als in Unix. Es ist daher darauf zu achten, das man die Priorität einzelner Prozesse und Threads nicht zu stark anhebt, da ansonsten das System nicht mehr optimal arbeiten kann.

    Damit dies nicht geschieht gibt es in Windows NT verschiedene Klassen von Prioritäten für Prozesse (siehe Abb. 3.2).


    Abb. 3.2: Proiritätsstufen in Windows NT [HaWi1997]

    Die Priorität eines Threads kann mit der Funktion SetThreadPriority() verändert werden. Dies gilt allerdings nur für die relative Priorität des Threads. Die Prioritätsklasse ist abhängig davon welche Klasse der Prozess besitzt, der den Thread gestartet hat. Ein Prozess kann nicht einen Thread in der Prioritätsklasse IDLE_PRIORITY_CLASS besitzen und einen anderen in der REALTIME_PRIORITY_CLASS.

     

    Beispiel SetThreadPriority():

    SetThreadPriority ( hThread, 
                        THREAD_PRIORITY_LOWEST 
                       );

    Parameter:

    Es gibt zusätzlich zu diesen Möglichkeiten noch 2 weitere Parameter für die Benutzung mit der SetThreadPriority() Funktion:

     

    Um die Priorität eines laufenden Threads abzufragen benutzt man die Funktion GetThreadPriority().

    Beispiel GetThreadPriority():

    int prio;
    prio = GetThreadPriority ( hThread );

    Parameter:

    Als Rückgabewert wird die Priorität in die Variable prio geschrieben.