Diejenigen, die schon einmal Accessories programmiert haben, mussten sich, sobald diese etwas komplexer wurden, bestimmt schon einmal mit dem Problem herumschlagen, daß man die GEMDOS-Funktion MALLOC zur Speicherreservierung nicht so einfach anwenden kann wie in ‘normalen’ Programmen.
Man muß nämlich beachten, daß beim Beenden eines Hauptprogramms automatisch auch alle mit MALLOC reservierten Speicherbereiche des Accessories freigegeben werden, wenn diese während der Laufzeit des Hauptprogramms reserviert wurden. Daraus folgt, daß spätestens beim Eintreffen der AC_CLOSE-Message alle reservierten Speicherbereiche wieder zurückgegeben werden müssen. Wenn man nun aber z.B. eine Adreßverwaltung als Accessory schreiben wollte, hieße das, daß deren Funktion stark eingeschränkt würde, da die Adressen bei jedem Programmende wieder gelöscht werden müßten. Und nicht nur dann: Die AC_CLOSE-Message wird nicht nur beim Beenden eines Programms gesendet, sondern auch beim Starten eines solchen. Da ich keinen Weg kenne, festzustellen, ob nun ein Programm wirklich beendet wurde, muß man zwangsläufig auch beim Starten eines Programms den Speicher freigeben, obwohl das eigentlich gar nicht nötig wäre.
Das Ganze ist also wirklich nicht sehr befriedigend. Man fragt sich, ob es da nicht irgendeine Möglichkeit gibt, das Freigeben der Speicherbereiche zu verhindern. Dazu muß man sich erst einmal im klaren sein, warum es überhaupt zur Freigabe kommt, was das Betriebssystem also macht. Die Wurzel allen Übels ist, daß Accessories zwar eigene AES-Applikationen sind, aber leider unter GEMDOS keine eigenen Prozesse. Sie haben deshalb zum Beispiel auch keine vollständig gefüllte Basepage (die man aber trotzdem verwenden kann, s.u.). Das Accessory ist für GEMDOS also der gleiche Prozeß wie das gerade aktive Hauptprogramm. Nun ist es so, daß GEMDOS intern bei jedem allokierten Speicherbereich vermerkt, zu welchem Prozeß er gehört, indem er einen Zeiger auf dessen PD (Basepage) abspeichert. Beim Ende des Hauptprogramms durch Pterm/Pterm() werden nun seinerseits alle Speicherbereiche freigegeben, die diesem PD zugeordnet sind. (Für genauere Informationen verweise ich auf [1].) Dies ist im allgemeinen ja auch ganz sinnvoll. da bei normalen Programmen nach dem Ende der Speicher ja auch nicht mehr benötigt wird. Für residente Treiber usw. steht dann ja auch Ptermres zur Verfügung, wo kein Speicher freigegeben wird. Durch den ‘Designfehler’ im TOS werden nun aber auch die Speicherbereiche der Accessories freigegeben, und das ist ja gerade unser Problem. Wie kann man es lösen oder umgehen? Dazu muß man wissen, daß es eine GEMDOS-Variable namens act_pd gibt, die immer die Adresse des PD des gerade aktiven Prozesses enthält (sprich einen Zeiger auf die Basepage des Hauptprogrammes). Wenn per Pexec ein Programm gestartet wird, zeigt act_pd danach auf dieses Programm, nach Programmende wieder auf den ‘Elternprozeß’. Bei MALLOC und Pterm wird nun act_pd benutzt, um die Zugehörigkeit der Speicherblöcke festzustellen. Haben Sie schon einen Lösungsvorschlag? Sehr gut!
Ja, die Idee ist wirklich, act_pd vor einem Accessory-MALLOC- (bzw. Mfree-) Aufruf umzusetzen (und zwar auf die Basepage des Accessories) und danach sofort wieder zurück. Die Folge ist, daß bei einem späteren Pterm die vom Accessory reservierten Speicherbereiche nicht mehr freigegeben werden, da sie ja nicht mehr dem Hauptprogramm zugeordnet sind. Ein paar ‘Details’ sind aber trotzdem noch zu besprechen, bevor Sie diesen Weg ausprobieren können. Vor allem habe ich bis jetzt immer nur von von act_pd gesprochen, aber nicht gesagt, wo diese Speicherzelle liegt. Leider ist es nämlich nicht ganz so einfach wie bei den meisten anderen Systemvariablen, die schon seit Urzeiten (in diesem Fall 1985) von ATARI garantiert wurden, act_pd war bis zur ROM-TOS-Version 1.0 undokumentiert, lag allerdings immer an der gleichen Adresse. Diese änderte sich beim ‘Blitter-TOS’ 1.2 zwar, doch hatte ATARI eingesehen, daß act_pd eigentlich doch ganz nützlich sein könnte, und erweiterte den Systemheader u.a. dahingehend, daß ein Zeiger auf act_pd enthalten ist. Man muß also anhand der TOS-Version (ebenfalls im Systemheader) unterscheiden, ob ein ‘altes’ TOS vorhanden ist, und die Adresse von act_pd entsprechend bestimmen. All dies können Sie in den Listings verfolgen. An dieser Stelle muß ich nun auch einräumen, daß das hier beschriebene Verfahren zwar recht sauber ist und auch auf allen TOS-Versionen funktioniert, aber nicht ausschließlich nur dokumentierte Eigenschaften benutzt. Das größte potentielle Problem ist, daß act_pd von ATARI nur zum Auslesen freigegeben wurde, der Schreibzugriff ist eigentlich nicht erlaubt. Da act_pd aber ja nur für sehr kurze Zeit (ein paar GEMDOS-Aufrufe) geändert wird, könnte das höchstens bei zukünftigen Multitasking-Versionen des GEMDOS/TOS zu Problemen führen, falls so etwas überhaupt irgendwann einmal kommt. Eine andere Einschränkung ist, daß nirgendwo dokumentiert ist, daß für die Zuordnung der Speicherblöcke zu den Prozessen act_pd benutzt wird. Eine andere Möglichkeit kann ich mir allerdings fast gar nicht vorstellen. Wie gesagt, diese Einwände habe ich nur der Korrektheit wegen aufgeführt, sie spielen bei heutigen STs keine Rolle und dürften auch in Zukunft unbedeutend sein.
Viel wichtiger ist dagegen folgendes: Wenn Sie das Verfahren so anwenden würden, wie ich es bis jetzt beschrieben habe, würden Sie sich evtl. bald über scheinbar unerklärlichen Speicherplatzmangel wundem, und zwar ironischerweise gerade bei Gebrauch von sauber geschriebenen Programmen. Was ich meine? Stellen Sie sich folgende Situation vor: Sie starten ein Programm, das ganz korrekt seinen überschüssigen Speicher wieder zurückgibt. Dort rufen Sie nun ihr Accessory auf, das nach obigem Verfahren ‘dauerhaft’ Speicher reserviert. Dann beenden Sie das Hauptprogramm. Der Accessory-Speicher ist immer noch unverändert erhalten. Also alles in Butter? Leider nicht ganz. Fragen Sie nun einmal den ‘freien’ Speicher ab (MALLOC(-1)). Er wird übermäßig stark geschrumpft sein. Was ist passiert? Das Programm hatte seinen überflüssigen Speicher zurückgegeben. Das Accessory hat sich nun den Anfang dieses Speicherblocks ‘dauerhaft’ reserviert. Wenn das Programm dann beendet wird, wird dieser ja nicht mehr freigegeben und damit der große freie Speicherblock in zwei Teile geteilt. Davon wird dann für das nächste Programm nur noch die größere Hälfte benutzt. Diese Situation ist natürlich inakzeptabel, was kann man also machen? Man müßte dafür sorgen, daß das Accessory nicht den Anfang sondern das Ende des freien Bereichs zugeteilt bekommt. Dann würde beim Programmende nur soviel Speicher fehlen, wie wirklich gebraucht wird. Dies ist mit einem weiteren Trick auch möglich: Es wird einfach der gesamte freie Speicher abzüglich der gewünschten Größe reserviert. Danach wird der eigentliche Speicherblock reserviert und im Anschluß daran der erste ‘Hilfsblock’ wieder freigegeben. Dieses Vorgehen erzielt den gewünschten Effekt, ist legal und hat noch zu keinen Problemen geführt.
So, wenn Sie bis hierhin durchgehalten haben, können Sie nun auch die beiden Listings verstehen. Bei dem einen handelt es sich um ein GFA-Basic-Programm, so daß die Umsetzung in eine beliebige Hochsprache keine Probleme bereiten sollte. Listing 2 ist eine Implementierung in Assembler, die man evtl. vereinfachen könnte, wenn man z.B. act_pd vorher bestimmt. Bei dieser Gelegenheit möchte ich noch darauf hinweisen, daß Accessories in Assembler gar nicht so schwer sind, wie vielfach angenommen wird. Es lohnt sich v.a. wegen des enormen Speicherplatzgewinnes sehr, auch wenn die Programmierung natürlich etwas langwieriger ist.
Ich hoffe, daß diese Routinen für einige nützlich sind und vielleicht die Erstellung von umfangreicheren Accessories erleichtern. Eventuell habe ich hiermit ja sogar den Anstoß zu ganz neuen Projekten gegeben, was mich sehr freuen würde.
Verwendete Literatur:
[1] ST-Computer Sonderheft Nr.2 (1987):
TOS intern (Alex Esser, S. 35 ff.)
[2] ReschkelJankowski/Rabich: ATARI ST Profibuch Sybex, Düsseldorf 1988,
ISBN 3-88745-563-0
' ************************************************
' *ACCMALLOC / ACCMFREE: 'dauerhaftes' Malloc für*
' * Accessories per Trick *
' * Anwendung wie Malloc / Mfree *
' ************************************************
' * 1989 by Lutz Preßler © MAXON Computer GmbH *
' * Ahornweg 11, 2904 Hatten, Tel.: 04481/--- *
' ************************************************
'
FUNCTION accmalloc(amount%)
LOCAL actpd%,pd%,adr%,ret%
actpd%=@get_actpd
pd%=LPEEK(actpd%) ! akt. PD
SLPOKE actpd%,BASEPAGE ! eigener PD
adr%=MALLOC(@malloc(-1)-amount%) ! Hilfsblock reservieren
ret%=MALLOC(amount%) ! 'dauerhaften' Speicher reservieren
VOID MFREE(adr%) ! Hilfsblock freigeben
SLPOKE actpd%,pd% ! alter PD
RETURN ret%
ENDFUNC
'
FUNCTION accmfree(adr%)
LOCAL actpd%,pd%,ret%
actpd%=@get_actpd
pd%=LPEEK(actpd%) ! akt. PD
SLPOKE actpd%,BASEPAGE ! eigener PD
ret%=MFREE(adr%) ! Speicher freigeben
SLPOKE actpd%,pd% ! alter PD
RETURN ret%
ENDFUNC
'
' * get_actpd:
' * Adresse von act_pd (GEMDOS) ermitteln
'
FUNCTION get_actpd
IF DPEEK(LPEEK(&H4F2)+2)>=&H102 ! TOS-Version
RETURN LPEEK(LPEEK(&H4F2)+40) ! ab 1.2: act_pd aus Systemheader
ELSE
RETURN &H602C ! sonst default
ENDIF
ENDFUNC
'
; *******************************************************
; * ACCMalloc/ACCMfree: dauerhaftes ACC-Malloc per Trick*
; *******************************************************
; * 1989 by Lutz Preßler © MAXON Computer GmbH *
; * Ahornweg 11, 2904 Hatten, Tel.: 04481/--- *
; *******************************************************
;
; * ACCMalloc: entspr. Malloc; Parameterübergabe anders:
; * IN: d4.l: amount
; * OUT: d4.l: maret
; * USES: d0-d2/a0-a2
; *****************
ACCMalloc: movem.l a3-a5,-(sp) ; Regs retten
clr.l -(sp) ; in Supervisormodus
move.w #$20,-(sp) ; Super
trap #1 ; GEMDOS
addq.l #6,sp
move.l d0,-(sp) ; alten SSP auf Stack
bsr Get_act_pd ; act_pd -> a4
movea.l (a4),a5 ; akt. PD
move.l #TextStart-256,(a4) ; eigener PD
; ** Das Label TextStart muß vor dem ersten Befehl im Text-Segment
; ** stehen (evtl, umbenennen). #TextStart-256 ist dann die Adresse
; ** der eigenen Basepage. Nötig, da bei ACC Base-pageadr. nicht auf
; ** dem Stack liegt.
moveq.l #-1,d0 ; größter
move.l d0,-(sp) ; Speicherblock
move.w #$48,-(sp) ; Malloc
trap #1 ; GEMDOS
sub.l d4,d0 ; - Länge
move.l d0,2(sp) ; als Parameter
trap #1 ; GEMDOS (Malloc)
movea.l d0,a3
move.l d4,2(sp) ; gewünschte Länge
trap #1 ; GEMDOS (Malloc)
addq.l #6,sp
move.l d0,d4 ; Rückgabewert
move.l a3,-(sp) ; Speicherblock
move.w #$49,-(sp) ; Mfree
trap #1 ; GEMDOS
addq.l #6,sp
move.l a5,(a4) ; alter PD
move.w #$20,-(sp) ; Super (SSP auf Stack)
trap #1 ; GEMDOS
addq.l #6,sp
movem.l (sp)+,a3-a5 ; Regs zurück
rts
; *****************
; * ACCMfree: entspr. Mfree; Parameterübergabe anders:
; * IN: d4.l: saddr
; * OUT: d4.l: mfret
; * USES: d0-d2/a0-a2
; *****************
ACCMfree: movem.l a3-a5,-(sp) ; Regs retten
clr.l -(sp) ; in Supervisormodus
move.w #$20,-(sp) ; Super
trap #1 ; GEMDOS
addq.l #6,sp
move.l d0,-(sp) ; alten SSP auf Stack
bsr Get_act_pd ; act_pd -> a4
movea.l (a4),a5 ; akt. PD
move.l #TextStart-256,(a4) ; eigener PD
; ( *** siehe ACCMalloc! *** )
move.l d4,-(sp) ; Adresse
move.w #$49,-(sp) ; Mfree
trap #1 ; GEMDOS
addq.l #6,sp
move.l d0,d4 ; Rückgabewert
move.l a5,(a4) ; alter PD
move.w #$20,-(sp) ; Super (SSP auf Stack)
trap #1 ; GEMDOS
addq.l #6,sp
movem.l (sp)+,a3-a5 ; Regs zurück
rts
; *****************
; * Get_act_pd
; * IN: Supervisormodus
; * OUT: a4: act_pd-Adresse (a0: sysbase)
; *****************
Get_act_pd: movea.l $4f2,a0 ; sysbase
movea.l #$602c,a4 ; Default-act_pd
cmpi.w #$102,2(a0) ; TOS-Version >= 1.2?
blt Get_actpdx ; nein
movea.l 40(a0),a4 ; act_pd holen
Get_actpdx: rts