Dynamische Speicherverwaltung (Omikron Basic)

Bei Programmen die mehrere Dateien im RAM-Speicher verwalten sollen, steht der Programmierer oft vor dem Problem, eine möglichst flexible Lösung zu finden. Speicherchips sind nach wie vor nicht gerade billig, daher sollte ein guter Programmierer darauf bedacht sein, den RAM-Speicher, so gut es programmtechnisch möglich ist, optimal auszunutzen.

Der Idealfall beim Verwalten mehrerer Dateien sollte hierbei so aussehen, daß diese Dateien fein säuberlich im Speicher hintereinander stehen, so daß kein einziges Byte verschwendet wird. Das wäre ja alles kein Problem, wenn der Anwender nicht die Angewohnheit hätte, während der Arbeit Dateien aus dem Speicher löschen zu wollen. Würde er immer die Datei, die er zuletzt in den Speicher laden würde, auch zuerst wieder löschen, wäre auch dies kein Problem, weil man dann lediglich Speicherplatz von hinten „abschneiden“ müßte. Doch es ist ja tragischerweise oft so, daß der Anwender ausgerechnet eine Datei aus der Mitte löschen möchte, wenn er sie nicht mehr benötigt. Im RAM würde beim Löschen eine Lücke entstehen. Ähnliche Probleme haben ja auch die Entwickler der Treiber-Software für Diskettenlaufwerke und Festplatten. Was tun mit den Lücken, die durch das Löschen von Dateien entstehen? Bei Disketten und Festplatten hat man das Problem dadurch gelöst, daß man sich diese Lücken „merkt“ und Dateien, die. neu geladen werden, in diese leeren Stellen einfügt. Das hat jedoch den Nachteil, daß Dateien, die größer sind als diese leeren Stellen, in mehrere Einzelteile zerstückelt werden, was dazu führt, daß die Arbeit mit Disketten und Festplatten, bei häufigem Lesen und Beschreiben, mit derzeit immer langsamer vonstatten geht, da die einzelnen Sektoren, auf denen sich die Daten befinden, erst zusammengesucht werden müssen. Glücklicherweise gibt es für die Programmierer einer RAM-Verwaltung eine komfortablere Möglichkeit der Speicherverwaltung, die in diesem Artikel vorgestellt werden soll.

Die Grundidee der Problemlösung besteht darin, daß man die Lücke, die durch das Löschen einer Datei aus dem Speicher entstehen würde, nicht bestehen läßt. Um diese Lücke sinnvoll auszufüllen, verschiebt man einfach die Dateien, die hinter der gelöschten Datei stehen, an die Anfangsadresse der gelöschten Datei. Bei einem Diskettenlaufwerk würde man bei einer solchen Operation oft mehrere Minuten warten müssen, der Computer selbst kann jedoch auch größere Speicherbereiche relativ schnell verschieben.

Welche Überlegungen müßte man anstellen, um das Ganze programmtechnisch umzusetzen? Damit man es praxisnah nachvollziehen kann, findet der OMIKRON.BASIC-Programmierer am Ende dieses Artikels eine Liste der Befehle, die die entsprechenden Arbeitsgänge zeigen, auf die in dieser Beschreibung durch Nummern (1)..(2)..(3)... usw. Bezug genommen wird.

Zunächst sollte man einen bestimmten Speicherbereich reservieren, der als dynamischer RAM-Speicher dienen soll (1). In diesen Speicherbereich kann man nun die einzelnen Dateien laden (2). Die Anfangsadresse der ersten Datei, die geladen wird, liegt an der Anfangsadresse des reservierten Speicherbereichs. Nachfolgend geladene Dateien würden jeweils an die zuletzt geladene Datei angehängt werden. Die zweite Datei würde also an die Anfangsadresse des reservierten Bereichs + der Dateilänge der ersten Datei geladen werden. Wurden bereits mehrere Dateien geladen, müßte man also die einzelnen Dateilängen zur Anfangsadresse hinzuaddieren, um zu ermitteln, an welche Adresse die nächste Datei geladen werden soll. Das Programm müßte sich also, um die Dateianfänge wiederzufinden, in einem Variablenfeld die einzelnen Dateilängen merken und sie bis zu der Datei, auf die man zugreifen möchte, addieren. Kommen wir nun zu dem Fall, daß eine Datei aus der Mitte herausgelöscht werden soll. Hierzu addiert man die einzelnen Längen der nachfolgenden Dateien und verschiebt den gesamten nachfolgenden Bereich an die Speicheradresse, an der die zu löschende Datei steht (3). Da man die einzelnen Dateilängen sinnvollerweise in einem Datenfeld ablegen sollte, würde sich auch der jeweilige Feldindex verschieben. Die Nachfolgedatei würde also jetzt den Feldindex der gelöschten Datei haben (4). Auch der Feldindex weiterer nachfolgender Dateien würde sich um -1 verschieben. Wie man sieht, ist Flexibilität gar nicht so schwierig, und es wäre schade, wenn man sie nicht in jedem Fall, wo es nötig ist, einsetzt. Warum nicht den Mut haben und die Daten ein bißchen im RAM hin- und herschieben; letztendlich ist der Computer ja da, um für uns zu arbeiten.

Wichtige Arbeitsschritte zur dynamischen RAM-Verwaltung:

(1) CLEAR nnnn : Ram_Startadresse=MEMORY(nnnn)  
(2) BLOAD Dateiname$,Datei_Startadresse  
(3) MEMORY_MOVEB Adresse_Alf,Gesamtlaenge_Nachfolgedateien TO Adresse_Neu  
(4) FOR i=Feldindex_Nachfolgedatei to Feldindex_Letzte_Datei  
        Dateilaenge(i-1 )=Dateilaenge(i)  
    NEXT

Das OMIKRON.BASIC-Listing soll noch einmal anhand eines praktischen Beispiels zeigen, wie man dieses Prinzip der dynamischen Speicherverwaltung in eigenen Programmen verwenden kann. Das Beispielprogramm erlaubt es, bis zu 10 Dateien im Speicher zu halten, die Dateien mit einem kleinen Datenmonitor anzusehen und einzelne auch wieder zu löschen. Die Darstellung im Datenmonitor erfolgt im ASCII-Modus, so daß alle möglichen Arten von Dateien in den Speicher geladen und angesehen werden können.

Um mit dem Befehl MEMORY_MOVEB vernünftig arbeiten zu können, benötigt man zur Behebung eines Fehlers im BASIC die Routine Patch_Basic aus der dem OMIKRON.-BASIC mitgelieferten GEM-Library. Für diejenigen, die sowieso mit GEM arbeiten, wird die Routine durch ApplInit automatisch aufgerufen. Diejenigen, die auf GEM verzichten möchten (aus welchem Grund auch immer), müßten sich diese Prozedur aus der GEM-Library rauskopieren und sie folgendermaßen aufrufen:

Patch_Basic $63,32,$6604,$6602:
Patch_Basic $148,38,$6402,$6404

GEM-Programmierer können hierauf verzichten und, wie das Beipiel-Listing zeigt, auf einige weitere Programmierhilfen zurückgreifen, wie zum Beispiel auf die Darstellung von Fenstern mit Titel und Infozeile sowie auf eine erweiterte Fileselectbox mit Titelzeile (für ältere TOS-Versionen).

'*********************************
'* Dynamische Speicherverwaltung *
'*       mit OMIKRON BASIC       *
'*       von Mario Srowig        *
'*   (c) 1992 MAXON Computer     *
'*********************************

'LIBRARY Gem ,"c:\omikron\interpr\GEM.LIB":' GEM-Library in Basic einbinden

CLEAR 150000: 'Genug Speicher für Zugriff durch MEMORY reservieren

Appl_Init:'GEM anmelden

PRINT CHR$(27);"f":'Cursor aus 
Pfad$="A:\*.*":Ja%L=-1:Nein%L=0 
Ndateien_Max%L=10:Nbytes%L= 100000:'Maximal 10 Dateien in einem 
Create_Dyna_Ram(Ndateien_Max%L,Nbytes%L) :'Gesamtspeicher von 100 000 Bytes 
'verwalten 
Init_Funktionkeys
Titel%L= MEMORY(80):'Speicher für Window Titel-
Info%L= MEMORY(80):' und Infozeile reservieren

MOUSEOFF

Wind_Get(0,4,X%L,Y%L,W%L,H%L)
Wind_Create(17,X%L,Y%L,W%L,H%L,Handle%L)
Wind_Open(Handle%L,X%L,Y%L,W%L,H%L)

'HAUPTMENUE - SCHIRM

REPEAT
    Clear_Screen
    Wind_Set(Handle%L,2,"Dynamische Speicherverwaltung", Titel%L)
    Wind_Set(Handle%L,3,"",Info%L)

    LOCATE 5,2: PRINT "F1=Datei laden"
    LOCATE 7,2: PRINT "F2=Datei zeigen"
    LOCATE 9,2: PRINT "F3=Speicherplatz INFO"
    LOCATE 11,2: PRINT "F4=Datei aus RAM löschen" 
    LOCATE 13,2: PRINT "F5=Programm Verlassen"

    Scan_Keyboard
    IF Scan_Code%L=F1%L THEN Datei_Laden 
    IF Scan_Code%L=F2%L THEN Datei_Zeigen 
    IF Scan_Code%L=F3%L THEN Speicherplatz_Info 
    IF Scan_Code%L=F4%L THEN Datei_Aus_Ram_Loeschen 
    IF Scan_Code%L=F5%L THEN Programm_Verlassen 
UNTIL Endlos%L

DEF PROC Datei_Laden 
    Laden%L=Ja%L
    Auswahl_Datei(Pfad$, "DATEI LADEN")
    IF Ok%L AND Laden%L AND Datei_Vorhanden%L THEN 
        IF Dateilaenge%L>Nbytes_Frei%L THEN 
            Hinweis "Es ist nicht genügend|Speicherplatz vorhanden!"
        ELSE
            Datei_Start%L=Dyna_Ram_Start%L 
            FOR I%L=0 TO Dateiptr%L
                Datei_Start%L=Datei_Start%L+Dateilaenge%L(I%L)
            NEXT
            BLOAD Dateiname$,Datei_Start%L 
            Dateilaenge%L(Dateiptr%L) =Dateilaenge%L 
            Dateiname$(Dateiptr%L)=Name$ 
            Dateiptr%L=Dateiptr%L+1
            Nbytes_Frei%L=Nbytes_Frei%L-Dateilaenge%L 
        ENDIF 
    ENDIF 
RETURN

DEF PROC Auswahl_Datei(R Pfad$,Kopfseile$)
    MOUSEON
    Fsel_Exinput(Pfad$,Name$,Kopfzeile$,Ok%L)
    MOUSEOFF
    IF Ok%L AND Laden%L THEN
        Dateiname$=LEFT$(Pfad$,LEN(Pfad$)-INSTR(1,MIRROR$(Pfad$)+"\","\"))+"\"+Name$ 
        Datei_Vorhanden Dateiname$
        IF NOT Datei Vorhanden%L THEN Hinweis "Die Datei ist|nicht vorhanden!"
    ENDIF
RETURN

DEF PROC Datei_Zeigen
    Wind_Set(Handle%L,2,"Datei Zeigen",Titel%L) 
    Wind_Set(Handle%L,3,"F1=Vorblättern F2=Zuruckblättern ESC=Abbruch", Info%L) Clear_Screen 
    Dateien_Zeigen
    PRINT CHR$(27);"e":'Cursor ein 
    LOCATE 20,2: PRINT "Welche Datei soll gezeigt werden? ";
    INPUT Eingabe$ USING "0-0",Ret%L,2 
    PRINT CHR$(27);'Cursor aus 
    Dnr%L=VAL(Eingabe$)
    IF Dnr%L<=Dateiptr%L THEN 
        Clear_Screen
        Startadr%L=Dyna_Ram_Start%L 
        FOR I%L=0 TO Dnr%L-2
            Startadr%L=Startadr%L+Dateilaenge%L(I%L) 
        NEXT
        Laenge%L=Dateilaenge%L(Dnr%L-1)
        Zeichennr%L=0 
        Daten_Anzeigen 
    ELSE EXIT 
    ENDIF

    REPEAT
        Scan_Keyboard

        IF Scan_Code%L=F1%L THEN :' Vorblättern
            IF Zeichennr%L>Laenge%L THEN Zeichennr%L=Zeichennr%L-1520 
            IF Zeichennr%L<0 THEN Zeichennr%L=0 
            Daten_Anzeigen 
        ENDIF

        IF Scan_Code%L=F2%L THEN :' Zurückblättern 
            Zeichennr%L=Zeichennr%L-3040 
            IF Zeichennr%L<0 THEN Zeichennr%L=0 
            Daten_Anzeigen 
        ENDIF

        IF Scan_Code%L=1 THEN EXIT 
    UNTIL Endlos%L 
RETURN

DEF PROC Daten_Anzeigen 
    FOR Zeile%L=5 TO 24 
        FOR Spalte%L=2 TO 77
            LOCATE Zeile%L,Spalte%L 
            IF Zeichennr%L<=Laenge%L THEN
                PRINT CHR$(1);CHR$(PEEK(Startadr%L+Zeichennr%L)) 
                Zeichennr%L=Zeichennr%L+1 
            ELSE PRINT " "
            ENDIF
        NEXT
    NEXT
RETURN

DEF PROC Speicherplatz_Info 
    Clear_Screen
    Wind_Set(Handle%L,2,"Speicherplatz INFO",Titel%L)
    Wind_Set(Handle%L,3,"ESC=Abbruch",Info%L)
    LOCATE 5,2: PRINT "Freier Speicherplatz: ";Nbytes_Frei%L;" Bytes"
    Dateien_Zeigen
    REPEAT : Scan_Keyboard: UNTIL Scan_Code%L=1 
RETURN

DEF PROC Datei_Aus_Ram_Loeschen
    Clear_Screen
    Wind_Set(Handle%L,2,"Datei aus RAM löschen",Titel%L)
    Wind_Set(Handle%L,3,"ESC=Abbruch F1=löschen",Info%L)
    Dateien_Zeigen 
    REPEAT
        Scan_Keyboard 
        IF Scan_Code%L=F1%L THEN 
            PRINT CHR$(27);"e"
            LOCATE 20,2: PRINT "Welche Datei soll gelöscht werden? ";

            INPUT Eingabe$ USING "0-0",Ret%L,2 
            PRINT CHR$(27);"f"
            Dnr%L=VAL(Eingabe$)
            IF Dnr%L<=Dateiptr%L THEN 
                IF Dnr%L=Dateiptr%L THEN
                    Nbytes_Frei%L=Nbytes_Frei%L+Dateilaenge%L(Dnr%L-1)
                ELSE
                    Gesamtlaenge%L=0 
                    FOR I%L=Dnr%L TO Dateiptr%L-1 
                        Gesamtlaenge%L=Gesamtlaenge%L+Dateilaenge%L(I%L)
                    NEXT
                    Datei_Start%L=Dyna_Ram_Start%L 
                    FOR I%L=0 TO Dnr%L-2
                        Datei_Start%L=Datei_Start%L+Dateilaenge%L(I%L)
                    NEXT
                    MEMORY_MOVEB Datei_Start%L+Dateilaenge%L(Dnr%L-1),Gesamtlaenge%L TO Datei_Start%L 
                    Nbytes_Frei%L=Nbytes_Frei%L+Dateilaenge%L(Dnr%L-1)
                    FOR I%L=Dnr%L-1 TO Dateiptr%L-1 
                        Dateilaenge%L(I%L)=Dateilaenge%L(I%L+1)
                        Dateiname$(I%L)=Dateiname$(I%L+1) 
                    NEXT 
                ENDIF
                Dateiptr%L=Dateiptr%L-1 
                Clear_Screen 
                Dateien_Zeigen 
            ENDIF 
        ENDIF 
    UNTIL Scan_Code%L=1 
RETURN

DEF PROC Dateien_Zeigen
    FOR I%L=7 TO 7+Dateiptr%L-1 
        LOCATE I%L,2
        PRINT "Datei Nr.: "; USING "###"; I%L-6;
        PRINT " Dateiname: ";Dateiname$(I%L-7);
        LOCATE I%L,40
        PRINT " Dateilänge: "; USING "#######"; Dateilaenge%L(I%L-7);
        PRINT " Bytes"
    NEXT
RETURN

DEF PROC Clear_Screen
    Wind_Get(Handle%L,4,X%L,Y%L,W%L,H%L)
    FILL COLOR =0: PBOX X%L,Y%L,W%L,H%L 
RETURN

DEF PROC Programm_Verlassen 
    Appl_Exit 
    END 
RETURN

DEF PROC Init_Funktionkeys
    FOR I%L=1 TO 10: KEY I%L=" ": NEXT 
    'Scan-codes
    F1%L=$3B:F2%L=$3C:F3%L=$3D:F4%L=$3E:F5%L=$3F 
    F6%L=$40:F7%L=$41:F8%L=$42:F9%L=$43:F10%L=$44 
RETURN

DEF PROC Scan_Keyboard 
    REPEAT 
    A$= INKEY$
    IF A$<>"" THEN
        Byte1%L= ASC( MID$ (A$,1,1))
        Shift_R%L= BIT(0,Byte1%L)
        Shift_L%L= BIT(1,Byte1%L)
        Control%L= BIT(2,Byte1%L)
        Alternate%L= BIT(3,Byte1%L)
        Caps_Lock%L= BIT(4,Byte1%L)
        Scan_Code%L= ASC( MID$(A$,2,1))
        Ascii_Code%L= ASC( MID$(A$,4,1))
    ENDIF
    UNTIL A$<>""
RETURN

DEF PROC Create_Dyna_Ram(Ndateien_Max%L,Nbytes%L) 
    Nd_Max%L=Ndateien_Max%L-1 
    Dyna_Ram_Start%L= MEMORY(Nbytes%L)
    Nbytes_Frei%L=Nbytes%L 
    DIM Dateilaenge%L(Nd_Max%L),Dateiname$(Nd_Max'%L)
    Dateiptr®L=0
    Dateilaenge%L(Dateiptr%L)=0 
RETURN

DEF PROC Datei_Vorhanden(Datei$)
    OPEN "F",15,Datei$,0 
    Datei_Vorhanden%L= NOT EOF(15)
    CLOSE 15
    IF Datei_Vorhanden%L THEN 
        OPEN "i",15,Datei$
        Dateilaenge%L= LOF(15)
        CLOSE 15 
    ENDIF 
RETURN

DEF PROC Hinweis(Meldung$)
    MOUSEON
    FORM_ALERT (1,"[1]["+Meldung$+"][OK]",Wahl%) 
    MOUSEOFF 
RETURN

LIBRARY CODE Gem

Mario Srowig
Links

Copyright-Bestimmungen: siehe Über diese Seite