← ST-Computer 07 / 1992

Dynamische Speicherverwaltung (Omikron Basic)

Programmierpraxis

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