Disk Copy
Wer hat sich als stolzer Besitzer eines Commodore 64 und einer 1541-Floppy noch nicht beim Kopieren von Programmen geärgert? Solange es sich um reine Basic-Programme handelt, geht es noch: Originaldiskette einlegen, Programm laden, Diskette wechseln, Programm »saven«, Diskette wechseln, Programm laden...
Schlimmer wird es, wenn sich um Maschinenprogramme oder gar um sequentielle Files handelt. Das Anlegen einer Sicherheitskopie wird zur zeitraubenden, umständlichen Prozedur und unterbleibt deshalb, bis man eines Tages feststellt, daß das Original aus irgendwelchen Gründen nicht mehr läuft. Mir ist das mit einer Datei passiert, die aus über 400 Einträgen bestand und die ich eines Tages einfach nicht mehr einladen konnte. Seitdem gibt es bei mir von allen wichtigen Disketten Sicherheitskopien, bei deren Erstellung mir das folgende Programm gute Dienste leistet.
Das Besondere an diesem Programm ist, daß alle Arten von Files mit Ausnahme relativer Dateien kopiert werden können und das recht komfortabel und schnell. Der Trick besteht darin, daß die Files, die man kopieren möchte, der Reihe nach in den Speicher gelesen werden bis dieser voll ist. Da das Programm auch die »versteckten« RAM-Bereiche mitbenutzt, die man normalerweise gar nicht ansprechen kann, weil Basic-Interpreter und Betriebssystem an derselben Speicheradresse liegen, kommt man in einem Durchgang auf stattliche 226 Blöcke (zirka 56 KByte). Sollte das noch nicht ausreichen, startet das Programm einen zweiten oder auch dritten Durchgang. Dann ist spätestens die gesamte Diskette kopiert (bei ganz ungünstigen Verhältnissen wird noch ein vierter Durchgang gebraucht). Damit ergeben sich Zeiten von unter 20 Minuten für eine Komplettkopie.
Wie funktioniert's?
Sehen wir uns zuerst das Basicprogramm an (Bild 1): Ich habe es absichtlich in mehrere Teile zerlegt, um es übersichtlicher zu machen. Die REM-Zeilen können beim Eintippen natürlich ebenso wegfallen wie die Zeilen, in denen nur ein Doppelpunkt steht.
Zeile 100 bis 140
setzt zuerst die Speicherobergrenze für Basic herunter (Speicherstelle 56) und berechnet, wieviel Speicher zum Kopieren zur Verfügung steht (RB). Um spätere Erweiterungen einbauen zu können (zum Beispiel Backup für relative Dateien), arbeite ich hier nicht mit festen Zahlen, sondern mit Variablen, die das Programm selbst berechnet. Das gilt ebenso für die Startadressen der Maschinenroutinen. Diese befinden sich direkt hinter dem Basic-Teil und brauchen deshalb nicht erst über DATA-Zeilen bei jedem Programmlauf »eingepoked« werden. Das spart nicht nur Zeit, sondern vor allem auch Speicherplatz (zirka 700 Bytes). Dafür müssen Sie beim Abschreiben zwei Teile zusammenfügen. Im Initialisierungsteil werden auch alle Variablen und Arrays eingerichtet (siehe Variablentabelle).
Zeile 160 bis 240
enthält das Menü. Sie können hier jederzeit weitere Funktionen einfügen.
Wird der Programmteil »Formatieren« gewählt, springt das Programm in die Zeilen 700 bis 750. Interessant ist hier die Möglichkeit, bei der Frage nach der Disk – ID keine Eingabe zu machen, sondern »RETURN« zu drücken. Dies ist bei Disketten sinnvoll, die bereits formatiert sind, aber gelöscht werden sollen. Das DOS der 1541 löscht dann nur die BAM und die erste Seite des Directory, was viel schneller geht als vollständiges Neuformatieren. Das Programm schließt diesen Teil mit Ausgabe der Fehlermeldung ab und springt wieder ins Menü.
Der Programmteil »Directory« ermöglicht ein schnelles Einlesen des Directorys, natürlich ohne Programmverlust. Man kann sich so einen kurzen Überblick über Original- und Zieldiskette verschaffen, ohne die Kopierroutine aufrufen zu müssen. Hier wird ein Maschinenteilprogramm benutzt, um Speicherplatz und Zeit zu sparen.
Zeile 270 bis 650
Kommen wir nun zum Kern der Sache, dem eigentlichen Kopierteil, Dieser befindet sich in den Zeilen 270 bis 650. Im ersten Teil (Zeile 270 bis 350) werden alle Files, die auf der Diskette sind, in ein Stringarray (NF$(I)) eingelesen, die zugehörigen Blocklängen in ein Variablenarray (BL%(I)). Das dauert etwas länger, weil der Speicherplatz begrenzt ist und der Basic-Interpreter mitunter eine Garbage-Collection (Müll-Sammlung) durchführt, um Platz zu schaffen. Die Anzahl der Files wird in der Variablen AN festgehalten. Ist die Diskette leer, weil man zum Beispiel noch die gerade formatierte Zieldiskette im Laufwerk hatte, springt das Programm ins Menü zurück.
In Zeile 370 bis 440 erfolgt die Auswahl, welche Files kopiert werden sollen. Das Programm schreibt dazu die einzelnen Namen mit der zugehörigen Blocklänge auf den Bildschirm und Sie können mit »J« oder »N« aussuchen. Die Entscheidung merkt sich das Programm wieder in einem Variablenarray (CF%(I)): Ist CF%=-1, wird kopiert, sonst nicht. Gleichzeitig wird in der Variablen BL aufaddiert, wieviele Blöcke zu kopieren sind, damit das Programm herausbekommt, ob ein Durchgang ausreicht oder nicht. Wenn Sie alle Files mit »N« kennzeichnen, springt das Programm wieder ins Menü.
Nachdem nun endlich alle Entscheidungen und Vorbereitungen abgeschlossen sind, geht es ans Kopieren (Zeile 460 bis 650). Der Reihe nach werden alle gekennzeichneten Files in den Speicher eingelesen. Dazu wird das Laufwerk mit »OPEN«-Befehlen angesprochen, weil so alle Arten von Files geladen und auch abgespeichert werden können. (Mit »LOAD« könnten nur Programmfiles geladen werden.) Erfreulicherweise enthält die Variable NF$ nicht nur den Namen des jeweiligen Files, sondern auch den Typ, also PRG, SEQ oder USR. Deshalb können alle Filetypen mit ein und derselben Routine verarbeitet werden.
Der Unterschied zwischen Lesen und Schreiben liegt lediglich darin, daß dem »OPEN«-Befehl im ersten Falle ein R (für Read), im zweiten ein W (für Write) angehängt wird. Die Datenübertragung selbst erledigt das Maschinenprogramm, dem wir uns gleich zuwenden werden. Um Variablen an diese Routinen übergeben zu können, benutzen wir die Zeropage-Speicherstellen 252 bis 255 ($FC bis $FF).
Werfen wir nun noch einen Blick auf die Maschinensprach-Teile (Bild 3 bis 5). Dabei soll vor allem erläutert werden, wie man den vollen RAM-Speicher des C 64 nutzen kann.
Dazu ist ein kurzer Blick auf die Speicheraufteilung des C 64 erforderlich, insbesondere den Teil ab $A000 bis $FFFF (dez. 40960 - 65535). Hier liegt normalerweise der Basic-Interpreter, der 8 KByte Adreßraum benötigt ($A000 - $C000). Darüber liegen in 4 KByte die Ein-Ausgabe-Einheiten ($D000 - $E000). Ganz oben im Speicher befindet sich das Betriebssystem, das genau wie der Basic-Interpreter 8 KByte belegt ($E000 - $FFFF). Zusätzlich ist aber der gesamte Bereich auch noch mit RAM bestückt. Woher weiß der Prozessor nun, was er benutzen soll? Lediglich 3 Bits in Speicherstelle 1 sind für die Auswahl zuständig: Bit 0 schaltet den Basic-Interpreter ein und aus, Bit 1 gleichzeitig Basic-Interpreter und Betriebssystem, Bit 2 ist für uns schon uninteressant. Werden beide, Bit 0 und Bit 1, auf 0 gesetzt, ist zusätzlich auch noch der Ein-Ausgabebereich abgeschaltet. Eigentlich doch ganz einfach. Der Teufel steckt wie fast immer im Detail: Wenn der Basic-Interpreter abgeschaltet ist, wie soll dann ein Basic-Programm laufen? Mehr noch, ohne sein Betriebssystem ist der Prozessor hilfloser als ein Blinder im Nebel.
Die Lösung liegt einfach darin, zu verhindern, daß der Prozessor überhaupt auf die Idee kommt, in sein Betriebssystem oder ins Basic hineinzuspringen. Letzteres ist nicht schwierig, denn wir befinden uns ja in einem Maschinenprogramm, wenn wir das Basic abschalten. Und bei der Bearbeitung eines solchen Programms kann nur dann etwas passieren, wenn der Prozessor das Programm verläßt. Das tut er allerdings jede 1/50 Sekunde, zum Beispiel um die interne Uhr weiterzustellen und nachzusehen, ob eine Taste gedrückt wurde etc. Das ist die sogenannte Interrupt-Routine. Wenn wir ihm die sperren, kann eigentlich gar nichts mehr schiefgehen. Und es funktioniert tatsächlich:
Im Leseteil des Maschinenprogramms wird zuerst der mit »OPEN« eröffnete Kanal als Eingabekanal gesetzt (FFC6). Dann wird ein Byte über diesen Kanal geholt. Jetzt sperrt der SEI (Set Interrupt) die Interruptroutine und wir können in aller Ruhe schalten und walten. Wir schieben das Byte ins X-Register, legen die beiden unteren Bits der Speicherstelle 1 auf 0, holen unser Byte aus dem X-Register und legen es im RAM ab. Jetzt setzen wir die Bits wieder auf 1, löschen die Interrupt-Sperre, und alles ist wieder in Ordnung. Nachdem unser Byte im RAM sicher untergebracht ist, fragen wir nun ab, ob vielleicht ein Fehler aufgetreten ist (FFB7) oder das Ende unseres Files erreicht ist. Wenn ja, springen wir ins Basic zurück und brechen im Fehlerfalle das Programm mit einer entsprechenden Meldung ab. Wenn nein, wenden wir uns dem nächsten Byte zu, das übertragen werden soll und behandeln es mit der gleichen Sorgfalt. Ganz zum Schluß müssen wir noch wieder die Kanäle zurücksetzen (Tastatur als Eingabe, Bildschirm als Ausgabe (FFCC)). Das alles klingt zwar umständlich und langwierig, geht aber in Wirklichkeit unglaublich schnell.
Das Schreiben auf Diskette bringt nichts grundsätzlich Neues, der ganze Vorgang läuft hier einfach andersherum ab. Unser Kanal 1 ist jetzt Ausgabekanal ($FFC9), und anstatt ein Byte von der Diskette zu holen, geben wir es aus ($FFD2).
Auch die Directory-Ausgabe folgt diesem Muster:
Kanal 3 als Eingabe setzen ($FFC6), Zeichen für Zeichen holen ($FFCF) und – jetzt auf dem Bildschirm – ausgeben ($FFD2).
Wichtige Bedienungshinweise
So, nun steht dem Eintippen des Programms nichts mehr im Wege. Noch ein paar wichtige Hinweise: Die beiden Teile des Programms müssen beim ersten Mal zusammengefügt werden. Dazu gehen wir folgendermaßen vor:
- Tippen Sie das Programm »Disk Copy« ab und speichern Sie es auf Diskette.
- Starten Sie das Programm mit »RUN« und drücken Sie die »RUN/STOP«-Taste, wenn das Menü erscheint.
- Geben sie ein: »PRINT PR« und schreiben Sie sich die angezeigte Zahl auf.
- Tippen Sie das Programm »Basic-Data-Lader« ein und starten Sie es. Auf die Frage nach der Anfangsadresse geben Sie Ihre aufgeschriebene Zahl ein.
- Folgen Sie genau den Anweisungen des Programs und geben Sie die beiden »POKE«-Befehle ein.
- Speichern Sie das vollständige Programm auf Diskette ab.
Jetzt haben Sie das Programm gebrauchsfähig auf Diskette. Sie können auch beliebige Änderungen am Programm durchführen, der Maschinensprach-Teil wird sich immer automatisch mitverschieben.
Anpassung auf VC 20:
Das Programm läuft auch auf dem VC 20, für den ich es ursprünglich geschrieben hatte. Nur Zeile 110 muß geändert werden:
110 POKE56,PEEK(46)+14:CLR:RB=PEEK(644)-PEEK (56):...
Wenn Sie mit einer 1541-Floppy arbeiten, sollten Sie noch einfügen:
118 OPEN1,8,15,'"UI-":CLOSE1.
Und nun viel Spaß beim Kopieren.
(Dietrich Weineck)RB | Anzahl dar zur Verfügung stehenden RAM-Blöcke |
PA | Anzahl der Durchgänge beim Kopieren |
AN | Anzahl der Files auf der Diskette |
BL | Anzahl der zu kopierenden Blöcke |
NF$ | Name und Typ des Files |
PE | Programmende |
MR | Adresse der Maschinenroutine zum Lesen |
MW | Adresse der Maschinenroutine zum Schreiben |
MD | Adresse der Maschinenroutine für Directory-Ausgabe |
NF$(I) | Feld für Filenamen |
CF%(I) | Feld für Kopierflags |
BL%(I) | Blocklänge der einzelnen Flles |
P%(I) | Anzahl der Files in einzelnen Durchgaengen |
AL%(I) | Endadresse der einzelnen Flles im Speicher (Low-Byte) |
AH%(I) | Endadresse der einzelnen Files im Speicher (Hlgh-Byte) |
RW | Flag für Lesen oder Schreiben |
I,J | Schleifenvariable |