home *** CD-ROM | disk | FTP | other *** search
- Heap Heap Hurra! (Trickkiste 3'91)
-
- Es ist eine schöne Sache, wenn man aus einem laufenden Pro-
- gramm heraus ein anderes aufrufen kann - zum Beispiel, um
- mal eben ein paar Disketten zu formatieren. So verfügt denn
- auch fast jede professionelle Anwendung über eine "DOS-
- Shell"-Funktion.
- Turbo Pascal hat zu diesem Zweck in der Unit "Dos" die Stan-
- dardprozedur "Exec" implementiert, über die sich ein "Kind"-
- Programm laden und starten läßt. Der Haken ist allerdings:
- "Exec" funktioniert nur dann, wenn zuvor mit der Compiler-
- Direktive "{$M Stackgröße, HeapMin, HeapMax}" die maximale
- Heapgröße eingeschränkt wurde, damit das Kindprogramm in
- den freien Speicherbereich über dem Heap geladen werden
- kann.
- Bei kleineren Programmen, die vom Heap keinen Gebrauch machen,
- ist dies auch kein Problem. Muß man jedoch mit größeren
- Datenmengen operieren, kommt man ohne dynamische Variablen
- nicht aus. Dann macht es sich empfindlich bemerkbar, wenn
- permanent etliche kByte weniger Speicherplatz auf dem Heap
- verfügbar sind.
- Gerd Cebulla hat einen Weg gefunden, während der Laufzeit des
- Programms einen ungenutzten Teil des Heap freizugeben, so daß
- das Kindprogramm sozusagen auf den Heap geladen werden kann.
- Dem DOS-Profi wird hier vielleicht die Funktion $4A des
- DOS-Interrupts $21 einfallen, mit deren Hilfe sich der einem
- Programm zugeteilte Speicherplatz verkleinern oder vergrößern
- läßt. Diese Funktion erwartet im Register ES die Segmentadresse
- des Programmanfangs und in BX die gewünschte Speichergröße
- in Paragraphen, also in Einheiten à 16 Byte.
- Aber ganz so einfach ist die Sache nicht, und zwar wegen der Art
- und Weise, wie Turbo Pascal (und übrigens auch Microsofts Clone
- Quick Pascal) den Heap verwaltet.
- Als erstes kommt jeweils das Programmsegment-Präfix (PSP). Das
- PSP belegt immer genau 256 Byte und wird von DOS jedem ausführbaren
- Programm vorangestellt; seine Segmentadresse läßt sich über die
- Turbo-Systemvariable "PrefixSeg" ermitteln. Darauf folgt dann
- das eigentliche Programm inklusive aller statischen Variablen.
- uletzt kommt der Heap, der sich nochmals in drei Bereiche
- aufgliedert.
- Alle via "New" oder "GetMem" erzeugten Variablen legt Turbo-Pascal
- im unteren Teil des Heap ab. Hierauf folgt ein mehr oder minder großer
- ungenutzter Bereich, der freie Heap. Die Systemvariable "HeapPtr"
- enthält jeweils die Adresse des ersten freien Byte.
- Am oberen Ende des Speichers schließlich befindet sich die
- Fragmentliste, auf deren Anfang die Systemvariable "FreePtr" zeigt;
- hier werden alle durch "Dispose" und "FreeMem" erzeugten "Löcher"
- im Heap festgehalten. Und genau da liegt der Hase im Pfeffer:
- Wenn wir mit der oben erwähnten DOS-Funktion $4A den Speicher vom Beginn
- des freien Heap ab freigeben, wird das Kindprogramm mit hoher
- Wahrscheinlichkeit die Fragmentliste überschreiben -- jeder weitere
- Aufruf von "New" oder "Dispose" hätte dann unvorhersehbare bis
- katastrophale Folgen.
- Die Fragmentliste muß also unbedingt in Sicherheit gebracht werden.
- Diese Überlegung führt zu folgendem Algorithmus:
- 1. Fragmentliste an den Anfang des freien Heap kopieren, also an die
- durch "HeapPtr" addressierte Speicherstelle.
- 2. Speicherbereich oberhalb der umkopierten Fragmentliste freigeben
- (DOS-Funktion $4A);
- 3. Kindprogramm aufrufen (Exec);
- 4. freigegebenen Speicherbereich zurückfordern (DOS-Funktion $4A);
- 5. Fragmentliste an die durch "FreePtr" adressierte Speicherstelle
- zurückkopieren.
-
- Jetzt muß man nur noch wissen, wie sich die Länge der Fragmentliste
- ermitteln läßt. Der Segmentanteil von "FreePtr" bleibt während des
- gesamten Programmlaufs konstant und enthält die Adresse des ersten
- nicht mehr zum Programm gehörigen Segments minus $1000; bei einem
- 640-kByte-System steht hier in der Regel der Wert 9000h, da an der
- Segmentadresse A000h der EGA-Grafikspeicher beginnt. Der Offsetanteil
- von "FreePtr" ist 0, wenn noch keine Fragmentliste existiert, ansonsten
- zeigt er auf den Anfang der Liste. Die Fragmentliste wächst übrigens
- von unten nach oben und belegt für jedes Loch im Heap 8 Byte; bei einem
- Eintrag beträgt der Offset also FFF8h, bei zwei Einträgen FFF0h usw.
- Die Länge der Fragmentliste läßt sich demnach mit der Formel:
-
- Fraglen := $10000 - Ofs(FreePtr^)
-
- berechnen.
- Der eben entwickelte Algorithmus ist in der Prozedur "Execute"
- verwirklicht. Sie können diese Routine problemlos in eigene Programme
- übernehmen; vergessen Sie aber nicht die Anweisung "USES Dos" im
- Programmkopf. Beachten Sie, daß Sie keinesfalls ein Programm
- aufrufen dürfen, daß sich resident installiert (etwa einen Maustreiber
- oder das DOS-Dienstprogramm PRINT). Ein solches Programm würde
- nämlich den Heap blockieren und damit bei nächster Gelegenheit einen
- kapitalen Programmabsturz verursachen. Um das Schlimmste zu verhüten,
- bricht "Execute" in diesem Fall das Programm mit einer Fehlermeldung ab.
-
- (wr)
-