Relativdatei

Aus C64-Wiki
(Weitergeleitet von Relative Datei)
Zur Navigation springenZur Suche springen

Eine Relativdatei, relative Datei (Abkürzung im Directory: REL) ermöglicht im Gegensatz zu sequentielle Dateien den wahlfreien Zugriff. D.h. man kann direkt auf ihre Daten zugreifen, ohne erst die Datei vom Anfang ausgehend einlesen zu müssen, und zudem den Inhalt an beliebigen Stellen ändern. Organisiert ist eine relative Datei in Datensätzen wählbarer Länge, sogenannte Records. Die bei relativen Dateien nötigen zusätzlichen Verwaltungsinformationen sind in Side Sectors und bei neueren DOS-Varianten auch in Super Side Sectors abgelegt.
Diese Struktur entspricht weitgehend dem, wie auch heute gängige Dateisysteme organisiert sind (bezogen auf eine Datensatzlänge von 1).

Eigenschaften[Bearbeiten | Quelltext bearbeiten]

Im Directory einer Diskette (hier die Datei: TOOLS) werden relative Dateien durch den Dateityp REL gekennzeichnet:

"C64 WIKI 2015"     01 2A  
100 "DATENBANK"        PRG
64  "SPIELE"           SEQ
44  "TOOLS"            REL
56  "ANWENDUNGEN"      USR
450 BLOCKS FREE            

Im ursprünglichen CBM-DOS gab es noch keine relative Dateien. Eingeführt wurden sie mit Version 2.0 und ab DOS-Version 2.7 wurde das Format erweitert, um die höhere Kapazität neuerer Laufwerkstypen, wie der CBM8250 oder der 1581 nutzen zu können. Allerdings wurde das erweiterte Format nicht zwangsläufig verwendet, daher ist auch bei der 1571 mit DOS 3.0 eine REL-Datei auf 720 Blöcke beschränkt.

Eine offene oder nicht korrekt geschlossene Relativdatei wird nicht mit der sonst üblichen Markierung, einem "*" vor dem Typkürzel (Markierung als sogenanntes splat file) gekennzeichnet.

Vor- und Nachteile[Bearbeiten | Quelltext bearbeiten]

Vorteile einer relativen Datei:

  • Schnelles Positionieren auf eine beliebige Position innerhalb der Datei.
  • Tabellenartige Organisation, somit gut für Datenbanken geeignet (Index ist dabei die Datensatznummer).
  • Einfaches und dynamisches Reservieren von Diskettenplatz.
  • Gemischte Schreib- und Lesevorgänge.

Eine relative Datei hat auch gewisse Einschränkungen:

  • Die Implementierung ist bei einigen CBM-DOS-Versionen fehlerhaft: Beim Schreiben von mehreren aufeinander folgenden Datensätzen können andere Datensätze überschrieben werden (siehe Abschnitt "Fehler").
  • Die benötigten Verwaltungsinformationen belegen zusätzliche Blöcke auf der Diskette (je einen auf 120 Datenblöcke).
  • Es sind weniger Datenkanäle pro Laufwerk verfügbar, da eine relative Datei den Pufferspeicher von 3 sequentiellen Dateien belegt.
  • Nur bedingt für binäre Daten geeignet, da eine Folge von Null-Bytes bis zum Ende eines Datensatzes das vorzeitige Ende dieses Datensatzes markiert.
  • Sie kann nicht mehr gekürzt werden (nur durch Umkopieren auf eine neue Datei und Löschen der alten, was aber entsprechenden Platz auf der Diskette erfordert).
  • Die Behandlung im Vergleich zu den "Standarddateien" im sequenziellen Format ist völlig anders und zeigt kein vereinfachtes Verhalten, z.B. das kompatibel zu sequenziellen Dateien wäre.

Kenndaten[Bearbeiten | Quelltext bearbeiten]

Kennzahlen einer relativen Datei:

  • Logische Datensatzgröße: 1 bis 254 Bytes.
  • Maximale Anzahl Datensätze pro Datei: 65535.
  • Theoretische Anzahl Nutzdatenblöcke pro Datei: 720 im Falle von CBM DOS 2.6 wie bei einem 1541-Laufwerk, 90.720 ab CBM-DOS 2.7 mit erweitertem Format.
  • Maximale Nutzdatengröße: 167.132 Bytes auf 1541-Diskette, 182.880 Bytes auf einer doppelseitigen 1571-Diskette, theoretisch bis zu 23.042.880 Bytes beim erweiterten Format.

Verwendung in BASIC[Bearbeiten | Quelltext bearbeiten]

In den Basic-Varianten vor Version 4.0 (insbesondere BASIC V2 und BASIC 3.5) ist das Ansprechen von relativen Dateien mit Hilfe der allgemeinen Ein- und -ausgabebefehlen OPEN, CLOSE, PRINT#, INPUT# und GET# realisierbar. Durch das Absetzen eines entsprechenden Diskettenbefehls (Record Position) über den Fehlerkanal erfolgt das Positionieren auf einen gewünschten Datensatz.

Erst ab BASIC 4.0 (samt späteren Varianten) und BASIC 7.0 wird die Behandlung relativer Dateien auch durch entsprechende Befehle und Parameter unterstützt:

  • DOPEN: Mit Parameter L<Datensatzgröße> wird eine relative Datei angelegt.
  • RECORD: Positioniert auf einen Datensatz entsprechend dem Diskettenbefehl Record Position.
  • Systemvariablen DS$ und DS erlauben den Fehlerkanal auszulesen, um den Status der jeweiligen Operationen abzufragen (genau genommen auch schon bei BASIC 3.5 vorhanden).

Verwendung in BASIC V2[Bearbeiten | Quelltext bearbeiten]

Gilt auch für BASIC 3.5.

Erstellt wird eine REL-Datei mit dem OPEN-Befehl, wobei als Dateiattribut L übergeben wird und anschließend, durch ein Komma getrennt, die Datensatzlänge als ASCII-Code:

OPEN 1,8,2,"RELDATEI,L,"+CHR$(100)

Erstellt, falls noch nicht vorhanden, auf Laufwerk 8 eine Relativdatei mit Namen RELDATEI und einer Datensatzlänge von 100 Bytes. Ist eine Datei mit diesem Namen, aber falschem Dateityp vorhanden, wird die Floppy-Fehlermeldung "64, FILE TYPE MISMATCH,00,00" erzeugt. Ist die REL-Datei zwar vorhanden, aber mit einer anderen Datensatzlänge angelegt worden, gibt der Fehlerkanal "50, RECORD NOT PRESENT,00,00" zurück. Zum Öffnen einer bereits vorhandenen REL-Datei muss die Datensatzlänge nicht übergeben werden, es reicht in einem solchen Fall schon ein OPEN 1,8,2,"RELDATEI"

Wird die Datei unmittelbar nach der Erstellung geschlossen, zeigt das Inhaltsverzeichnis einen Verbrauch von 0 Block, allerdings werden bereits 2 Blöcke auf dem Datenträger belegt, nämlich ein Side-Sector- und ein Datenblock, auch wenn dieser nur leere Datensätze (1. Byte Wert 255, Rest 0) enthält, so viele, wie in einen Datenblock passen.

Unmittelbar nach dem Öffnen der Datei zeigt der Datensatzzeiger auf den 1. Datensatz (am Dateianfang). Mittels PRINT# kann nun der jeweilige Datensatz beschrieben werden, wobei das Ende der mit dem PRINT#-Befehl gesendete Daten gleichzeitig die EOI-Markierung bildet, also das Ende des Datensatzes kennzeichnet. Überschreitet die Anzahl der zum Schreiben übertragenen Zeichen die Datensatzlänge, so gibt die Floppy die Fehlermeldung "51, OVERFLOW IN RECORD,00,00" aus. Der Datensatz wird trotzdem geschrieben, allerdings auf die passende Anzahl von Zeichen abgeschnitten. Werden weniger Zeichen übertragen, als in den Datensatz passen, wird der restliche Teil mit Null-Bytes aufgefüllt.

Das Einlesen der Daten kann sowohl mit INPUT# als auch mit GET# erfolgen, wobei GET# aufgrund folgender Gründe vorzuziehen ist:

  • INPUT# kann nur Datensätze mit der maximalen Länge des BASIC-Eingabepuffers (bei Rechner mit Basic V2 oder Basic 3.5 sind es 88 Zeichen) einlesen, ansonsten wird das Programm mit der Fehlermeldung ?STRING TO LONG ERROR abgebrochen.
  • Felder innerhalb eines Datensatzes lassen sich mittels INPUT# nur durch die üblichen Trennzeichen wie das RETURN-Zeichen, Komma etc. realisieren. Bei einer freien Aufteilung des Datensatzes in Felder muss nicht jeder Datensatz zwingend mit einem RETURN-Zeichen enden. Mit GET# ist das Einlesen flexibler, wobei das Ende eines Datensatzes durchaus vor der Datensatzlänge erreicht werden kann.
  • Unter bestimmten Umständen kann der INPUT#-Befehl wegen eines fehlenden RETURN-Zeichens hängen bleiben, sodass das Programm nur noch mittels RUN/STOP -RESTORE  verlassen werden kann.

Das Kriterium für das Ende eines Datensatzes, wenn dieser beispielsweise mittels GET# eingelesen wird, ist das gesetzte Bit 6 der Systemvariable STATUS (Wert 64, entspricht dem EOF-Bit), welches bei REL-Dateien nicht das Dateiende, sondern das Ende des Datensatzes anzeigt (EOI-Markierung).

Einzelne Datensätze werden mit dem Record-Positionierbefehl "P" ausgewählt, in dem an den Befehlskanal des Laufwerks folgender String gesendet wird:
"P"+CHR$(<Kanalnummer>)+CHR$(<Datensatznummer_Low>)+CHR$(<Datensatznummer_High>)+CHR$(<Byteposition>)

  • <Kanalnummer> ist die im OPEN-Befehl übergebene Sekundäradresse, wobei in den Commodore-Handbüchern empfohlen wird, den Wert 96 (hex. $60) zu addieren[1].
  • <Datensatznummer> ist die Datensatznummer beginnend mit 1.
  • <Byteposition> ist die Position innerhalb des Datensatzes, ebenfalls beginnend mit 1. Die Angabe ist optional, allerdings muss darauf geachtet werden, dass dann der PRINT#-Befehlt mit ";" endet, da sonst das RETURN-Zeichen mit seinem Codewert 13 als Byteposition interpretiert wird.

Beispiel:

OPEN 1,8,2,"RELATIVDATEI,L,"+CHR$(100)
OPEN 15,8,15
PRINT#15,"P"CHR$(2)CHR$(5)CHR$(0)CHR$(15);
PRINT#1,"INHALT BEI POSITION 15";
CLOSE 1
CLOSE 15

Öffnet die Datei RELATIVDATEI (legt sie auch an, sollte sie nicht existieren) und positioniert auf das 15. Byte des 5. Datensatzes (ohne "+"-Operator, der bei der PRINT nicht notwendig ist) und schreibt dort die angegebene Zeichenkette (ohne abschließenden Zeilenvorschub). Das CLOSE-Kommando für Dateinummer 1 sorgt dafür, dass die geschriebenen Daten schließlich auch auf die Diskette geschrieben werden (und nicht im Puffer der Floppy verbleibt).

Ist der ausgewählte Datensatz nicht vorhanden, meldet das Laufwerk "50, RECORD NOT PRESENT,00,00". Diese Meldung ist aber nur informativ. Sobald nämlich Daten geschrieben werden, werden die fehlenden Datensätze erstellt. Leere Datensätze werden dabei durch ein Zeichen mit dem ASCII-Code 255 gekennzeichnet.

Verwendung in BASIC 4.0 und BASIC 7.0[Bearbeiten | Quelltext bearbeiten]

Im Grunde gilt das Gleiche wie bei der BASIC-V2-Verwendung. Das dort genannte Beispiel lässt sich aber mit Hilfe der bereits seit BASIC 4.0 eingeführten diskettenorientierten Befehlen folgendermaßen formulieren:

DOPEN #1,"RELATIVDATEI",L100
RECORD #1,5,15
PRINT#1,"INHALT BEI POSITION 15";
DCLOSE #1

Öffnet die Datei RELATIVDATEI (legt sie auch an, sollte sie nicht existieren) und positioniert auf das 15. Byte des 5. Datensatzes und schreibt dort die angegebene Zeichenkette (ohne abschließenden Zeilenvorschub). Das DCLOSE-Kommando sorgt dafür, dass die geschriebenen Daten schließlich auch auf die Diskette geschrieben werden (und nicht im Puffer der Floppy verbleibt).
Siehe auch Hinweis bezüglich noch nicht existenter Datensätze.

Als Sekundäradresse wird implizit eine entsprechend freie im Bereich von 98 ($62) bis 111 ($6F) gesucht wird, die in Bezug auf die Floppy einer Kanalnummer von 2 bis 15 entspricht.

Zumindest ab Basic 7.0 gilt für den INPUT#-Befehl ein höheres Limit von 160 Zeichen. Bei Überschreiten dieser Zeichenanzahl liefert das BASIC die Fehlermeldung ?STRING TO LONG ERROR.

Empfehlungen[Bearbeiten | Quelltext bearbeiten]

  • Sowohl vor als auch nach Schreib/Lesevorgängen sollte ein Positionierbefehl abgesetzt werden, da bei einigen Floppytypen sonst die Gefahr von Datenverlust besteht[2].
  • Außerdem wird empfohlen, den Speicherplatz, den die Datei voraussichtlich benötigt, sofort nach dem Erstellen zu reservieren. Zwar benötigt das Anlegen einige Zeit, dafür sind die Zugriffe aber anschließend schneller.

Floppy-Meldungen[Bearbeiten | Quelltext bearbeiten]

  • "50, RECORD NOT PRESENT,00,00"
    Es wurde auf einen bisher noch nicht angelegten Datensatz positioniert. Das ist eigentlich keine Fehlermeldung, sondern eine Information, dass die Positionierung nach dem letzten tatsächlich vorhandenen Datensatz erfolgt ist und ein Schreiben des Datensatzes die Datei entsprechend erweitern würde.
  • "51, OVERFLOW IN RECORD,00,00"
    Entweder ist beim Positionieren der Posititionsindex innerhalb des Datensatzes größer als der Datensatz oder beim Schreiben auf den Datensatz überschreiten die Daten den Datensatzumfang oder eine existierende Relativdatei wird explizit mit einer anderen Dateigröße geöffnet...
  • "52, FILE TOO LARGE,00,00"
    Das Schreiben auf einen zuvor positionierten Datensatzes würde zu einer zu großen Datei führen, die die verfügbare freie Kapazität der Diskette sprengen würde.
  • "64, FILETYPE MISMATCH,00,00"
    Beim Versuch eine relative Datei zu öffnen, existiert bereits eine gleichnamige Datei anderen Typs oder ein RECORD-Aufruf wird auf eine Datei sequenziellen Typs durchgeführt.
  • "70, NO CHANNEL,00,00"
    Der Diskettenbefehl Record Position wurde mit einem nicht zugewiesenen Channel-Parameter aufgerufen (weil das vorhergehende Öffnen der Datei mit OPEN misslang) oder beim Öffnen der Datei standen nicht mehr genügend freie Puffer zur Verfügung.

Datenorganisation[Bearbeiten | Quelltext bearbeiten]

Eine relative Datei besteht intern aus zwei Listen von verketteten Blöcken:

  • Die eigentlichen Datenblöcke, die genau wie bei einer sequentiellen Datei miteinander verkettet sind. In den Datenblöcken werden die Datensätze nahtlos aneinander gespeichert.
    • In die 254 Bytes des ersten Datenblocks einer Relativdatei würden also z.B. bei einer Datensatzlänge von 100 Bytes zwei Datensätze plus die ersten 54 Bytes des dritten Datensatzes passen (der im nächsten Datenblock dann fortgesetzt wird).
  • Maximal sechs Side Sectors, in denen die Spur/Sektornummern der Datenblöcke abgelegt sind. Außerdem verweisen die Side Sectors aufeinander.
    • Auf den ersten Side Sector wird durch Bytes 19/20 des Eintrags der Relativdatei im Directory verwiesen.
    • Mit DOS 2.7 wurde ein erweitertes Format eingeführt, bei dem diese Side Sectors zusätzlich in Gruppen zusammengefasst werden, deren jeweils erster Block im sogenannten Super Side Sector vermerkt wird.

CBM-DOS kann also die genaue Position eines Datensatzes auf der Diskette wie folgt ermitteln (Pseudocode):

datensatz_block_nr = int(datensatznummer * datensatzlänge / 254)
side_sector_nr = int(datensatz_block_nr / 120)
datensatz_nr_in_side_sector = datensatz_block_nr - side_sector_nr * 120
datensatz_in_spur = side_sector[side_sector_nr][datensatz_nr_in_side_sector * 2 + 16]
datensatz_in_sektor = side_sector[side_sector_nr][datensatz_nr_in_side_sector * 2 + 17]
datensatz_ab_offset = frac(datensatznummer * datensatzlänge / 254) * 254 + 2

Da die Side Sectors aufeinander verweisen, sind also pro Datensatzzugriff nur maximal drei Blöcke zu lesen (Side Sector, Datenblock und ggf. weiterer Datenblock mit "überstehendem" Rest des Datensatzes).

Aufbau Datenblöcke[Bearbeiten | Quelltext bearbeiten]

Position Beschreibung
0 Spurnummer des nächsten Datenblocks,
beim letzten Datenblock 0
1 Sektornummer des nächsten Datenblocks,
beim letzten Datenblock Anzahl der belegten Bytes
(abhängig von der maximalen Anzahl der noch vollständig hineinpassenden Datensätze)
2 - 255 Datenbytes

Datensätze, die nicht vollständig in einen Block passen, werden im nächsten Block fortgesetzt. Leere Datensätze werden durch den Wert 255 ($FF) gefolgt von Null-Bytes gekennzeichnet, nur teilweise genutzte Datensätze mit Null-Bytes aufgefüllt.

Aufbau Side-Sector-Blöcke[Bearbeiten | Quelltext bearbeiten]

Position Beschreibung
0 Spurnummer des folgenden Side Sectors
1 Sektornummer des folgenden Side Sectors
2 Index des Side Sectors (0 - 5)
3 Datensatzlänge
4/5 Track/Sektor des Side Sectors 0
6/7 Track/Sektor des Side Sectors 1
8/9 Track/Sektor des Side Sectors 2
10/11 Track/Sektor des Side Sectors 3
12/13 Track/Sektor des Side Sectors 4
14/15 Track/Sektor des Side Sectors 5
16 - 255 Track/Sektor-Zeiger auf insgesamt 120 Datenblöcke

Aufbau eines Super-Side-Sectors[Bearbeiten | Quelltext bearbeiten]

Aufbau eines Super-Side-Sectors bei einer 1581[2]

Position Beschreibung
0 Spurnummer des ersten Side Sectors in Gruppe 0
1 Sektornummer des ersten Side Sectors in Gruppe 0
2 254 ($FE)
3/4 Track/Sektor der Side-Sector-Gruppe 0
5/6 Track/Sektor der Side-Sector-Gruppe 1
... ...
253/254 Track/Sektor der Side-Sector-Gruppe 125
255 unbenutzt

Fehler[Bearbeiten | Quelltext bearbeiten]

Im Artikel Shiloh's Raid: 1541 Relative Bug Spray[3] wird gezeigt, dass die Pufferorganisation bei einer gewissen Änderungsabfolge für Datensätze, die Blockgrenzen überschreiten, durcheinander kommen kann - mit fatalen Folgen: Blockinhalte der relativen Datei werden an falsche Stellen auf Diskette geschrieben und etliche dabei beteiligte Datensätze damit zerstört.

Vermeiden kann dies mit Standardmitteln unter anderem durch eine etwas aufwändigere Positionierungslogik, die bei Bedarf ein doppeltes Positionieren mit einer kurzen Wartezeit dazwischen durchführt und so der Floppy die Chance gibt, den entsprechenden Puffer noch richtig zurückzuschreiben.

Außerdem kann man sich ggf. auf die Datensatzgrößen 1, 2, 127 oder 254 beschränken, bei denen Blockgrenzen überschreitenden Datensätze nicht vorkommen und damit auch der Fehler kann.

Einzelnachweise[Bearbeiten | Quelltext bearbeiten]

  1. Im Setzen von Bit 6 und 5 ($60) bei der Kanalnummer ist zumindest bei der 1541 laut Quell-Code und der dort vorgenommenen Maskierung der Sekundäradresse keine besondere Wirkung zuschreibbar.
    Ursprung dieser Empfehlung könnte in der internen Vergabepraxis bei DOPEN für Sekundäradressen im Wertebereich von $62 bis $6F liegen.
  2. 2,0 2,1 Commodore 1581 Disk Drive User's Guide
  3. Artikel "Shiloh's Raid: 1541 Relative Bug Spray" in Transactor Vol. 7, Issue 4 (1987), Seiten 73-76 Sprache:englisch

Weblinks[Bearbeiten | Quelltext bearbeiten]