Wie Windows funktioniert


Einleitung

In diesem Text will ich versuchen zu Erklären, wie eigentlich Windows im Prinzip funktioniert. Auf technische Details werde ich größten teils nicht weiter eingehen, deshalb erhebt dieser Text keinen Anspruch auf Vollständigkeit.

Ein Blick in die Vergangenheit

Bevor wir anfangen uns mit Windows zu beschäftigen, wollen wir mal einen Blick zurück in die Vergangenheit werfen und kucken, wie es damals, in der "Vor-Windows-Zeit", war. Mit der "Vor-Windows-Zeit" meine ich die Zeit als es noch kein Windows gab und man den Begriff "Multitasking" nur in Zusammenhang mit Servern kannte.

Unter DOS oder einem vergleichbaren Betriebssystem, konnte sich ein Programm über bestimmte Gegebenheiten bzw. Umgebungseigenschaften sicher sein:


Was bedeutet dies nun im Einzelnen?

Vom Singletasking zum Multitasking

Da DOS eine singletasking Betriebssystem war, es konnte immer nur ein Programm zur selben Zeit ausgeführt werden, stand ein Programm nie in Konkurrenz mit einem anderen. Es konnte also über alle zur Verfügung stehenden Systemressourcen alleine verfügen. Wurde unter einem singletasking Betriebssystem ein Programm gestartet, gab das Betriebssystem die Kontrolle an das Programm ab und wurde nach Beendigung des selbigen wieder an das Betriebssystem zurückgegeben. Daraus resultieren obige Gegebenheiten für das Programm.

Multithreading

Was ändert sich nun für ein multitasking Betriebssystem mit grafischer Oberfläche?
Als erstes muss das Betriebssystem in der lage sein mehrere Programme gleichzeitig auszuführen. Dies bringt nun einige Probleme bzw. Aufgaben mit sich, die das Betriebssystem in der Lage sein muss zu lösen.
Als erstes wäre da das Problem, dass ein Programm nun nicht mehr alleine über die Rechenzeit der CPU verfügen kann bzw. darf. Das heißt das Betriebssystem muss in der Lage sein die Rechenzeit der CPU unter den verschiedenen Prozessen aufzuteilen. Womit wir beim sogenannten Multithreading wären. Als Thread ("Faden") bezeichnet man einen Ausführungspfad innerhalb eines Prozesses. Ein Prozess kann mehrere Threads haben. Der Thread ist es auch, der den eigentlichen Programmcode ausführt. Desweiteren kann ein Thread nie ohne einen Prozess existieren, da erst ein Prozess ihm den nötigen "Lebensraum" zur Verfügung stellt.
Wir stellen also fest: Ohne Multithreading kein Multitasking, da jeder Prozess mindestens einen Thread braucht, der seinen Code ausführt.

Wer mehr dazu wissen will, sei auf mein Delphi Tutorial zur Thread-Programmierung unter Windows mit Delphi verwiesen.

Wie löst nun das Betriebssystem dieses Problem?
Das Betriebssystem teilt die CPU-Zeit, die einem Prozess zur Verfügung steht nun in Zeitscheiben ein. Und jedes mal, wenn eine Zeitscheibe um ist, speichert es die Register der CPU, lädt die gespeicherten Register des nächsten Threads und stellt diesem Thread die Rechenzeit der CPU zur Verfügung. Effektiv wird dann zwar doch wieder nur ein Programm zur gleichen Zeit ausgeführt, da die Zeitscheiben aber so kurz sind, unter Windows ca. 22 ms, wird für den Benutzer eine quasi Parallelität erzielt.
Aber alleine kann das Betriebssystem dies auch nicht regeln. Es ist auch erforderlich, dass die Hardware multithreadingfähig ist. Beispielsweise muss die CPU auf einen Interupt reagieren, der ihr sagt, jetzt auf einen anderen Prozess umzuschalten.

Anmerkung: Windows 3.1 kannte noch kein "richtiges" Multithreading. Es kannte nur das sogenannte korporative Multithreading. Das heißt ein Prozess musste von sich aus Rechenzeit abgeben und eben mit den anderen Prozessen kooperieren. Das preemptive Multitasking kam erst mit Windows 95 bzw. NT. Hier bestimmt das Betriebssystem an Hand von Prioritäten wann ein Prozess wie viel Rechenzeit zugeteilt bekommt.

Der Speicher

Das nächste Problem ist der Speicher. Laufen mehrere Prozesse bedeutet dies, dass einem Prozess nicht mehr der gesamte Speicher zur Verfügung steht, der Speicher muss vom Betriebssystem unter den laufenden Prozessen aufgeteilt und verwaltet werden. Stichwort wäre hier der Fachbegriff "Speichermanagment" zu nennen. Darauf will ich aber nicht weiter eingehen, da dies ein weiteres sehr komplexes Thema für sich wäre. Nur so viel: Windows stellt jedem Prozess einen virtuellen Adressraum von 4 GB zur Verfügung. 4 GB daher, da mit 32 Bit (heutige Rechnerarchitektur) 4294967296 verschiedene Speicheradressen adressiert werden können. Effektiv stehen dem Prozess aber nur 2 GB zur Verfügung, da Windows für sich selber 2 GB im Adressraum des Prozesses beansprucht. Windows schützt auch jeden Adressraum eines Prozesses vor unberechtigten Zugriff. Ein Prozess hat unter Windows nur Zugriff auf seinen eigenen Adressraum, aber nicht auf den Adressraum eines anderen Prozesses.

Anmerkung: Unter Windows 3.1 war dem noch nicht so und unter NT basierenden System sind die Adressräume der Prozesse noch besser geschützt.

Kommen wir nun zum nächsten Problem, die Bildschirmausgabe.

Nicht umsonst heißt Windows "Fenster"

Wenn der Nutzer in der Lage ist mehrere Programme gleichzeitig auszuführen, sollte man es ihm auch ermöglichen mit mehreren Programmen gleichzeitig zu arbeiten, also es ihm ermöglichen in einem Programm seiner Wahl Eingaben zu machen. Er muss also auf die Programmoberfläche des gewünschten Programmes Zugriff haben. Unter Windows bekommt jedes Programm einen Teil des Bildschirmes zugewiesen in dem es seinen Ausgaben darstellen und die Eingaben des Benutzers empfangen kann. Diese erscheinen als sogenannte Fenster auf dem Desktop. Der Desktop nimmt den gesamten Bildschirm ein und ist somit das unterste Fenster. Die Fenster der Programme sind Unterfenster, Kinder, des Desktopfensters, die bestimmte Eigenschaften haben, sie lassen sich minimieren, maximieren und verschieben. das Zeichnen der Fenster übernimmt Windows nur der Clientbereich des Fensters kann vom Programm zur Ein- und Ausgabe genutzt werden und es obliegt dem Programm diesen Teil des Fensters zu zeichnen, darzustellen und zu verwalten.
Die Kontrollelemente auf einem Fenster, Schaltflächen, Texteingabefelder, Listenfelder etc., sind für Windows auch nur Fenster, und zwar Kindfenster des Hauptfensters des Programmes. Da dies Kindfenster des Hauptfensters sind, ist auch selbiges für das Zeichnen verantwortlich.

Da jedes Programm für den Clientbereich seines Fensters selbst verantwortlich ist, wie sollte auch Windows wissen, was es darzustellen gibt, führt dies zu einem weiteren Problem: Wie teilt Windows dem Programm mit, wann es seinen Clientberich neu zu zeichnen hat. stellen wir uns zum Beispiel mal folgende Situation vor: Wir haben zwei Fenster A und B. Fenster A wird von Fenster B teilweise oder ganz überdeckt. Jetzt wird Fenster B geschlossen, minimiert oder verschoben und es wird Fenster A teilweise oder ganz sichtbar. Jetzt müsste Fenster A den Teil des Bildschirmes neu zeichnen bzw. aktualisieren der ihm gehört und vorher nicht sichtbar war. Nur, woher weiß Fenster A, dass es sich ganz oder eben teilweise neuzeichnen muss? Es muss also in irgendeiner weise eine Kommunikation zwischen Windows und den Programmen und deren Fenstern stattfinden.

Die Kommunikation

Die Kommunikation unter Windows basiert auf Nachrichten. Das heißt jedes Ereignis löst eine Nachricht aus auf die das Programm / Fenster oder Windows selber entsprechend reagieren kann. Setzten wir zur Veranschaulichung dazu unser Beispiel aus dem vorherigen Kapitel fort.
Fenster A muss nun von Windows benachrichtigt werden, dass es sich neu zu zeichnen hat. Dazu schickt Windows an das Fenster eine entsprechende Nachricht. In diesem Fall wäre das die Windows-Nachricht WM_PAINT. Das Programm kann nun diese Nachricht "abfangen" und entsprechend darauf reagieren und alles nötige veranlassen, um den Clientbereich seines Fensters zu aktualisieren. Damit dies funktioniert muss ein entsprechender Mechanismus vorhanden sein.

Nachrichtenschlange und Nachrichtenschleife

Windows richtet nun für jedes Fenster eine so genannte Nachrichtenschlange ein. In diese Nachrichtenschlange plaziert es alle Nachrichten für ein Fenster und dessen Kindfenster. Auf der anderen Seite unterhält das Programm für sein Fenster eine so genannte Nachrichtenschleife. Die Nachrichtenschleife ist im Prinzip eine Endlosschleife und läuft so lange das Fenster bzw. der zugehörige Prozess existiert. Diese Nachrichtenschleife holt nun die von Windows in der Nachrichtenschlange plazierten Nachrichten ab und reicht sie im Programm an eine Funktion weiter, die entsprechend auf die ankommenden Nachrichten reagiert. Dabei ist zu beachten, dass immer nur eine Nachricht nach der anderen abgeholt und bearbeitet werden kann!

.. und immer eine Hand voll Handles
Damit Windows nun weiß an welches Fenster es eine Nachricht zu schicken hat, bzw. damit es die Nachrichten in der richtigen Nachrichtenschlange plaziert, muss es die Fenster eindeutig identifizieren können. Dazu vergibt es an jedes Fenster eine eindeutige Kennnummer welche man unter Windows als Handle bezeichnet. Ein Handle ist vergleichbar mit einem Autokennzeichen oder einer Hausnummer. Der Wert spielt dabei keine Rolle und ist für einen Programmierer eigentlich auch uninteressant.

"Programm reagiert nicht mehr" und nackte Fenster

Mit diesem Wissen können wir auch konkret zwei Phänomen verstehen, die beim Programmieren immer wieder auftauchen:

  1. Die Anwendung / das Fenster reagiert nicht mehr auf Benutzereingaben und zeichnet sich nicht neu
  2. Auf das Fenster gezeichnetes verschwindet, nach dem unser Fenster überdeckt und wieder sichtbar wurde

Der Grund für das erste Problem ist die Tatsache, dass immer nur eine Nachricht nach der anderen abgeholt wird bzw. dass wenn das Programm mit etwas anderem beschäftigt ist, die Nachrichtenschleife keine Nachrichten aus der Nachrichtenschlange abholt und weiterreicht. Ein Thread kann eben auch nur einen bestimmten Code gleichzeitig ausführen, entweder den Code für die Nachrichtenschleife oder eben anderen Code wie zum Beispiel eine Berechnung. Dauert diese Berechnung nun länger, macht es für den Benutzer den Eindruck, als ob das Programm nicht mehr reagiert - was ja auch der Fall ist - und "abgestürzt" sei, was es aber eigentlich nicht ist. Im Taskmanager erscheint dann immer der schöne Text: "Programm reagiert nicht mehr".
"Was tun, sprach Zeus." Die Lösung klingt recht simpel, kann aber in der Umsetzung für den Programmierer nicht zu vernachlässigende Stolperfallen bereit halten. Die Lösung sieht so aus, dass man es dem Programm ermöglichen muss, während der langen Rechenoperation Nachrichten aus der Nachrichten schlage mit einer Nachrichtenschleife zu holen. Dies kann man entweder so machen, dass man die längere Berechnung kurzzeitig unterbricht, in die Nachrichtenschleife geht, die anliegenden Nachrichten abholt und verarbeitet. Oder aber man erzeugt in den Prozess einen zweiten Thread, der die Berechnung durchführt und somit kann der Hauptthread, der zu dem Fenster gehört, in Ruhe die Nachrichten abholen und darauf reagieren. Diese zweite Art das Problem zu lösen, bezeichnet man auch als "Multithreading-Programmierung". Ich verweise hier noch mals auf mein Delphi-Tutorial zu diesem Thema.

Wenden wir uns nun dem zweiten Phänomen zu: Das mysteriöse Verschwinden von Zeichnungen und Grafiken auf unserem Fenster. Stellen wir uns folgendes Programm vor: Wir haben eine Programm geschrieben, welches nach dem Anklicken einer Schaltfläche auf unser Fenster ein Rechteck malt. Schieben wir nun ein anderes Fenster über unser Fenster und das Rechteck wird es im wahrsten Sinne des Wortes ausradiert. Warum?
Nun das ist relativ einfach. Windows sagt unserem Fenster es soll sich neuzeichnen, da wir das Rechteck aber im Klick-Ereignis der Schaltfläche gezeichnet haben und nicht im Neuzeichnen-Ereignisses des Fensters, wird eben gar nichts gezeichnet und unser Rechteck verschwindet. Auch hier ist die Lösung bzw. die Lösungen - es gibt zwei - eigentlich wieder relativ einfach. Entweder wir zeichnen das Rechteck, immer dann wenn unser Fenster neu gezeichnet werden muss und lösen beim Klick auf die Schaltfläche ein neu zeichnen unseres Fensters auf oder aber, und das ist die elegantere Methode, wir zeichnen auf ein Bitmap im Speicher und kopieren es bei Bedarf auf unser Fenster.
Diese zweite Lösung hat noch den Vorteil, dass etwaiges Flackern beim Zeichnen verhindert wird, da die entsprechende Kopier-Funktion von Windows wesentlich schneller arbeitet, als die GDI Zeichenfunktionen, die generell nicht sehr schnell sind.

Da dies nicht sehr komplex ist, kann ich an dieser Stelle schnell ein kleines Beispiel in Delphi zeigen:

procedure TForm1.FormCreate(Sender: TObject);
begin
  { Bitamp erzeugen }
  bmp := TBitmap.Create;
  { Hintergrundfarbe únd Stift dicke }
  bmp.Canvas.Brush.Color := clRed;
  bmp.Canvas.Pen.Width := 3;
  { Größe festlegen }
  bmp.Width := Paintbox1.Width;
  bmp.Height := Paintbox1.Height;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  { Rechteck zeichnen }
  bmp.Canvas.Rectangle(5,5, 75, 75);
  { OnPaint der Paintbox auslösen }
  PaintBox1.Repaint;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  { Bitmap freigeben }
  bmp.Free;
end;

procedure TForm1.PaintBox1Paint(Sender: TObject);
begin
  { bei Bedarf neu zeichnen }
  BitBlt(PaintBox1.Canvas.Handle, 0, 0, bmp.Width, bmp.Height, bmp.Canvas.Handle,
    0, 0, SrcCopy);
end;

Mit diesem kleinen Programm-Beispiel will ich hier zum Ende kommen. Ich hoffe, ich konnte einen kleinen und verständlichen Einblick in die Funktionsweise von Windows vermitteln.


2010-12-29T23:44:58 +0100, mail+homepage[at]michael-puff.de