online: 1
Rekord: 57

Last update:
2005/04/20

Home
Übergeordnete Seite
Search:
Suche starten
In English, please!

CFX Hacking

Die Speicherverwaltung des Casio CFX



Das das Memorymanagement dynamisch erfolgt und dass die meisten Daten im RAM des Casio CFX daher keine feste Adresse haben, sondern ihnen diese erst jeweils durch Pointertabelleneinträge zugewiesen wird, habe ich bereits im Kapitel der Memory Map erläutert. Aber wie erfolgt die Verwaltung des freien Teils des RAMs (Adressbereich 0832..07FFE) nun genau? Wie ermittelt der Rechner z.B., welcher Teil dieses Adressbereiches noch frei oder schon belegt ist, und für welche Datenstrukturen wie viel Speicher reserviert ist?

Wie im Kapitel der Memory Map bereits erwähnt, unterscheidet das GTR Betriebssystem den Grow - Up - Speicher, der durch die Grow - Up - Pointertabelle zugewiesen wird, und den Grow - Down - Speicher, in dem sich die Daten befinden, auf die die Grow - Down - Tabelle verweist. Der Grow - Up - Speicher wächst dabei von unten nach oben, d.h., er beginnt an 0832 mit dem ersten Grow - Up Datensatz, und reicht bis maximal 7FD2. Die Datensätze folgen im RAM direkt aufeinander, so dass wenn z.B. die Grow - Up - Pointertabelle auf insgesamt nur drei Datensätze zeigt, die 10, 37 und 20 Bytes lang sind, der Grow - Up Speicher nur 10+37+20B=67B einnimmt und von 0832 bis 0875 reicht. Beim Grow - Down - Speicher (in dem im Übrigen nur Daten im Programmformat gespeichert werden, alle anderen Datentypen sind Grow - Up) läuft das genau anders herum: er wächst (angefangen bei 7FFE) nach unten, das letzte Byte des ersten Grow - Down - Puffers befindet sich an Adresse 07FFE, und jeder weitere Grow - Down - Datenpuffer wird im RAM direkt unter den vorhergehenden gelagert (ähnlich wie bei einem Stack). Die Einträge der Grow - Down - Pointertabelle sowie die Einträge der Programmliste, die auf Daten im Grow - Down - Speicher zeigen, verweisen dabei jeweils auf das letzte Byte eines Grow - Down Datenpuffers im RAM, NICHT auf das erste, wie das die Grow - Up - Einträge tun.

Das KLINGT zwar relativ kompliziert, die Speicherverwaltung des Betriebssystems, also das Reservieren und wieder Freigeben von Speicherblöcken (= Datenpuffern für Datensätze, z.B. Listen, Funktionsspeicher et c.) und das Abfragen des insgesamt reservierten und noch freien Speichers usw., funktioniert auf diese Weise aber ERSTAUNLICH EINFACH, und die entsprechenden Speicherverwaltungsroutinen, die das Betriebssystem implementiert, umfassen jeweils nur sehr wenige Anweisungen!

Speicherverwaltung mit der Grow - Up - Tabelle

Das wird dadurch möglich, dass der Rechner sich nicht erst die Mühe macht, eine Liste mit freien und belegten Speicherblöcken im RAM zu erstellen, aus der jeweils die entsprechenden Einträge herausgesucht werden (wie´s zum Beispiel bereits beim RM Speichermanagement unter DOS der Fall ist), sondern dass die Speicherblöcke im RAM einfach linear in genau der Reihenfolge angelegt werden, in der auch die entsprechenden Pointertabelleneinträge auftreten, und dass bereits belegte Speicherblöcke dabei ggf. einfach verschoben werden (beim PC wäre das nicht möglich, weil 1. sich dadurch die Adressen der Speicherblöcke ständig ändern würden, und 2. auch viel mehr Speicher verschoben werden müsste)! Jeder 16 Bit Pointertabelleneintrag zeigt dabei auf die bestehende Datenstruktur, oder, wenn die Datenstruktur noch nicht existiert und somit keinen Speicher belegt (z.B. eine noch nicht erstellte List 1), auf die Stelle im RAM, an die die Datenstruktur geschrieben werden soll, wenn für sie Speicher reserviert wird

Stellen wir uns nun drei aufeinanderfolgende Pointertabelleneinträge a,b und c vor, dann bedeutet das:

  • Zeigt keiner der Pointer auf eine Datenstruktur, sondern alle zu a,b und c gehörigen Daten sind gelöscht, dann zeigen a,b und c auf die selbe Stelle X im RAM (a=b=c=X!), wo der Speicher für die Daten reserviert werden soll
  • Werden jetzt z.B. für a n Bytes reserviert, dann verschieben sich die Einträge von b und c um n nach oben, d.h., zu b und c wird jeweils n addiert (b' = b+n, c' = c+n)
  • Zeigen b und c allerdings auf Daten die nicht gelöscht sind (d.h., auf Daten, die mehr als 0B Speicher belegen, z.B. b p Bytes und c q Bytes), dann werden diese dabei von Adresse X nach X+n bzw. von X+p nach X+p+n kopiert
  • Die Anzahl Bytes, die ein zu einem Pointer gehöriger Datenpuffer im RAM belegt, ist gleich der Differenz des nachfolgenden Pointers und des Pointers selbst, da die Datenstruktur des nachfolgenden Pointers der des Pointers im RAM DIREKT folgt. Ist also beispielsweise b-a=10, belegt der Eintrag, auf den a zeigt, 10 Bytes, ist b-a=0, belegt er keinen Speicher und zählt als "gelöscht"
  • Belegt der a Eintrag n Bytes Speicher und wird er gelöscht, dann passiert folgendes: die Datenpuffer von b und c werden wieder um n Bytes nach unten verschoben, und von b und c (den Zeigern auf diese Daten) wird n subtrahiert

Speicherverwaltung mit der Grow - Down - Tabelle

Hier funktioniert das Ganze analog wie bei der Grow - Up Tabelle, bloss eben rückwärts: die Einträge der Grow - Down - Pointertabelle zeigen nicht auf den Anfang eines Speicherblocks, sondern auf dessen Ende. Außerdem zeigt der erste Eintrag auf den letzten Block im Speicher, der zweite auf den vorletzten usw. (der nächste Block befindet sich also immer DIREKT vor dem vorhergehenden im Adressraum).

Anmerkung: für die Einträge der Grow - Down - Tabelle gilt dabei die Besonderheit, dass sie nicht 16 Bit lang sind wie die der Grow - Up - Tabelle, sondern 32 Bit, auch wenn jeweils nur die ersten 16 für die Adressangabe verwendet werden.

Algorithmen der Speicherverwaltungsprozeduren

Die Speicherverwaltungsroutinen des Casio CFX Betriebssystems funktionieren nach folgenden Algorithmen:

Bestimmung des insgesamt belegten Speichers (MemReserved):
  • Belegte Speicherblöcke folgen im RAM linear direkt aufeinander, in der selben Reihenfolge, wie dies auch die Pointertabelleneinträge tun
  • Insgesamt vergeben zwei Pointertabellen Speicherblöcke
  • ⇒ belegter Speicher in Bytes = letzter Eintrag der Grow - Up Tabelle - erster Eintrag der Grow - Up - Tabelle + erster Eintrag der Grow - Down Tabelle - letzter Eintrag der Grow - Down - Tabelle
  • 0 = kein Speicher belegt
Bestimmung des noch freien Speichers geasamt (MemAvail):
  • Der durch Pointertabellen zu verwaltende RAM befindet sich im Adressraum 0832..7FFE (bzw. 0832..FFFE für 64KB Systeme)
  • ⇒ freier Speicher = 7FFF - 0832 - belegter Speicher = 30699 - MemReserved
  • 0 = kein Speicher mehr frei
Bestimmung der Größe eines Speicherblocks, auf den ein Pointertabelleneintrag #n zeigt (GetBlockSizeUp / GetBlockSizeDown):
  • Belegte Speicherblöcke folgen im RAM linear direkt aufeinander, in der selben Reihenfolge, wie dies auch die Pointertabelleneinträge tun
  • ⇒ für Grow - Up - Einträge: Blockgröße = Pointern+1 - Popintern
  • ⇒ für Grow - Down - Einträge: Blockgröße = Pointern - Popintern+1
Anmerkung: Grow - Down - Einträge belegen dabei immer genau 1B mehr, als der Datenblock eigentlich benötigt, da sie immer mit einem Byte FF abgeschlossen werden, auch dann, wenn die entsprechenden Daten EIGENTLICH gelöscht sind (Grow - Down - Blöcke benötigen daher, selbst im gelöschten Zustand, mindestens 1 Byte Speicher).

Reservieren von m Bytes Speicher für den Grow - Up Pointertabelleneintrag #n (GetMemUp):
  • Größe X = Summe der Größe aller Blöcke bestimmen, die durch Grow - Up - Pointertabelleneinträge oberhalb von n vergeben werden: X = letzter Pointertabelleneintrag- Eintrag #n
  • Wenn X > 0, dann Speicherblöcke um m Bytes nach oben verschieben (X Bytes von Adresse, auf die Eintrag #n zeigt, an Adresse+m kopieren)
  • m zu allen Pointertabelleneiträgen addieren, die Eintrag #n folgen (auch dann, wenn X = 0!)
Reservieren von m Bytes Speicher für den Grow - Down Pointertabelleneintrag #n (GetMemDown):
  • Größe X = Summe der Größe aller Blöcke bestimmen, die durch Grow - Down - Pointertabelleneinträge oberhalb von n vergeben werden: X = Eintrag #(n+1) - letzter Pointertabelleneintrag +1
  • Speicherblöcke um m Bytes nach unten verschieben (X Bytes von Adresse, auf die Eintrag #(n+1) zeigt, an Adresse-m kopieren; Anmerkung: X ist hier immer größer 0, da ein Grow - Down - Eintrag mindestens 1B belegt, auch wenn er gelöscht ist, s.o.)
  • m von allen Pointertabelleneiträgen subtrahieren, die Eintrag #n folgen
Freigabe des Speichers, den der Grow - Up Pointertabelleneintrag #n belegt (FreeMemUp):
  • Größe X = Summe der Größe aller Blöcke bestimmen, die durch Grow - Up - Pointertabelleneinträge oberhalb von n vergeben werden: X = letzter Pointertabelleneintrag- Eintrag (#n+1)
  • Größe m in Bytes bestimmen, die Pointertabelleneintrag #n reserviert (m = Eintrag #(n+1) - Eintrag n)
  • Wenn X > 0, dann Speicherblöcke um m Bytes nach unten verschieben (X Bytes von Adresse, auf die Eintrag #(n+1) zeigt, an Adresse, auf die Eintrag #n zeigt, kopieren)
  • m von allen Pointertabelleneiträgen subtrahieren, die Eintrag #n folgen (auch dann, wenn X = 0!)
Freigabe des Speichers, den der Grow - Down Pointertabelleneintrag #n belegt (FreeMemDown):
  • Größe X = Summe der Größe aller Blöcke bestimmen, die durch Grow - Down - Pointertabelleneinträge oberhalb von n vergeben werden: X = Eintrag #(n+1) - letzter Pointertabelleneintrag
  • Größe m in Bytes bestimmen, die Pointertabelleneintrag #n reserviert (m = Eintrag #n - Eintrag #(n+1))
  • Speicherblöcke um m Bytes nach oben verschieben (X Bytes von Adresse, auf die Eintrag #(n+1) zeigt, an Adresse-m = Adresse, auf die Eintrag #n zeigt, kopieren)
  • m zu allen Pointertabelleneiträgen addieren, die Eintrag #n folgen

Speicherverwaltungsprozeduren des Casio CFX in Pascal Syntax

In Pascalsyntax könnte man die Speicherverwaltungsroutinen, die das Casio CFX Betriebssystem implementiert, in einige wenige Zeilen fassen. Dadurch wird besonders deutlich, wie einfach das Memory Management des GTR für den RAM eigentlich gehalten ist:

{* GY359MEM.INC - Casio CFX Speicherverwaltungsroutinen für GY359                          *}
{* für ZX933 identisch, jedoch weichen die Startadressen und Größen der Pointertabellen ab *}


//---Konstantendefinitionen für Pointertabellen-----------------------------------------------------------------------------

const upbase   = 0400h; //Startadresse der Grow - Up - Pointertabelle
      downbase = 0648h; //Startadresse der Grow - Down - Tabelle, wäre 0640h für ZX933

      maxup   = 291; //maximaler Listenindex der Grow - Up Tabelle; 291, da 292 Einträge (wäre 287 für ZX933)
      maxdown =  44; //maximaler Listenindex der Grow - Downtabelle; 44, da 45 Einträge (wäre 43 für ZX933)

      maxramaddr  =  7FFFh; //maximale Adresse im RAM; 0FFFFh für 64KB Systeme
      freerambase =  0832h; //Startadresse des RAM Blocks, der durch Pointertabellen verwaltet wird

      maxfreemem =  maxramaddr-freerambase; //maximaler freier Speicher; 30699 für 32KB, 63437 für 64KB



//---Definition der Pointertabellen-----------------------------------------------------------------------------------------

var growup  : mem[upbase  ] = array[0..maxup+1  ] of word;            //Grow - Up - Tabelle
    growdown: mem[downbase] = array[0..maxdown+1] of record           //Grow - Down - Tabelle
                                                       p,dummy: word; //Grow - Down - Eintrag = 32 Bit, nur 16 Bit verwendet
                                                     end;


//---Implementation von Speichevrerwaltungsroutinen-------------------------------------------------------------------------

function memreserved: word;
begin
  memreserved := growup[maxup]-growup[0]+growdown[0].p+growdown[maxdown].p;
end;

function memavail: word;
begin
  memavail := maxfreeram-memreserved;
end;

function getblocksizeup(n: word): word;
begin
  getblocksizeup := growup[n+1]-growup[n];
end;

function getblocksizedown(n: word): dword;
begin
  getblocksizedown := growdown[n].p-growdown[n+1].p;
end;

procedure getmemup(n,size: word);
var x: word;
begin
  x := growup[maxup] - growup[n];
  if x > 0 then move(ptr(growup[n]+size)^,ptr(growup[n])^,x);
  if n < maxup then for x := n+1 to maxup do growup[x] := growup[x]+size;
end;

procedure getmemdown(n,size: word);
var x: word;
begin
  x := growdown[n+1].p-growdown[maxdown].p+1;
  move(ptr(growdown[n+1].p-size)^,ptr(growdown[n+1].p)^,x);
  if n < maxdown then for x := n+1 to maxdown do grdown[x].p := grdown[x].p-size;
end;

procedure freememup(n: word);
var x,size: word;
begin
  x := growup[maxup] - growup[n+1];
  size := growup[n+1]+growup[n];
  if x > 0 then move(ptr(growup[n])^,ptr(growup[n+1])^,x);
  if n < maxup then for x := n+1 to maxup do growup[x] := growup[x]-size;
end;

procedure freememdown(n: word);
var x,size: word;
begin
  x := growdown[n+1].p-growdown[maxdown].p;
  size := growdown[n].p-growdown[n+1].p;
  move(ptr(growdown[n+1].p-size)^,ptr(growdown[n+1].p)^,x);
  if n < maxdown then for x := n+1 to maxdown do grdown[x].p := grdown[x].p+size;
end;

Copyright (C) 2004 by Marco Kaufmann