← ST-Computer 01 / 1987

PASCAL ruft TOS: Teil 2 - Die Floppy

Grundlagen

Auch diesem Speichermedium kann man in Pascal noch einiges entlocken, beispielsweise den direkten Zugriff auf einzelne Sektoren oder auf das Directory. Die dafĂŒr verwendeten Routinen eignen sich zum Einbau in eigene Programm eund zeigen meist erst kombiniert ihre LeistungsfĂ€higkeit.

Aufbau der Diskette

Zur besseren VerstÀndlichkeit der folgenden Befehle wollen wir zunÀchst den Aufbau einer Diskette erlÀutern (siehe auch Grafik 1). Sie ist im unbenutzten Zustand noch unbrauchbar, denn erst das Formatieren der Diskette legt die Verteilung der einzelnen Bereiche fest. Das Standardformat einer ATARI ST-Diskette hat 80 Spuren, die in konzentrischen Kreisen um den Mittelpunkt angelegt sind. Jede Spur ist wiederum in neun Teilbereiche untergliedert, die sich Sektoren nennen und je 512 Byte umfassen. Mit diesen Sektoren arbeiten die nun folgenden Routinen.

Lesen und Schreiben eines Sektors

Die erste Routine dieser Folge ist der Direktzugriff auf bestimmte Sektoren der Diskette. Er ist im normalen Pascal-Wortschatz nicht enthalten, aber durch Einsatz der Betriebssystemroutinen erreichbar. DafĂŒr gibt es z. B. im XBIOS des ST die passenden Routinen. Sie benötigen als Parameter folgende Werte:

  • Puffer [n ★ 512 Bytes]: Adresse auf einen ausreichend großen Puffer, oder ein entsprechend dimensioniertes Feld
  • FĂŒller: unbenutzer Parameter
  • Drivenummer (A=0, B = 1)
  • Sektornummer (0...9)
  • Tracknummer (0...79) bzw. [0...82]
  • Diskettenseite (0...1) je nach Laufwerk
  • Sektoranzahl [n]: Anzahl der Sektoren, die hintereinander gelesen werden (die Zahl darf aber nicht grĂ¶ĂŸer sein als die Anzahl der Sektoren auf einem Track).

Der Aufruf zum Lesen lautet:

FLOPRD(buf,filler,devno,sectno, trackno,sideno,count)

Der Aufruf zum Schreiben lautet:

FLOPWR(buf,filler,devno,sectno, trackno,sideno,count)

Die Funktionen ĂŒbergeben nach ihrer AusfĂŒhrung einen Statuswert. Wenn der Wert Null ist, dann war der Aufruf erfolgreich, bei negativen Werten ist ein Fehler aufgetreten. Solche Werte sollten vom Programm abgefragt werden, um eventuell auftretende Fehler abfangen zu können.

Wenn man nun einen Sektor lesen oder schreiben will, betrĂ€gt der Puffer 512 Bytes und die Sektoranzahl ’1’.

program DISC_COPY; CONST disk_A=0; disk_B=1; vorne=0; hinten=1; TYPE buff = packed array [1..5000] of char; VAR dummy : long_integer; track,sector,count,drive,side, error,error1,error2,error3,error4 : integer; daten1,daten2 : buff; ch : char; function FLOPRD (VAR buffer: buff; dummy: long_integer; drive, sector,track,side,count : integer ): integer; xbios(8); function FLOPWR (VAR buffer: buff; dummy: long_integer; drive, sector,track,side,count : integer ): integer; xbios(9); begin { Hauptprogramm } sector:=1; count:= 9; writeln (chr(27),'E Backup A —> B'); writeln; writeln (' Original-Diskette in Laufwerk A'); writeln (' Ziel-Diskette in Laufwerk B'); writeln; writeln (' (q)uit'); readln (ch); if ch<>'q' then begin for track:=0 to 79 do begin error1:=floprd (daten1,dummy,disk_A,sector,track,vorne,count); error2:=floprd (daten2,dummy,disk_A,sector,track,hinten,count); {SF 314} error3:=flopwr (daten1,dummy,disk_B,sector,track,vorne,count); error4:=flopwr (daten2,dummy,disk_B,sector,track,hinten,count); {SF 314} writeln(chr(27),’Y',chr(40),ehr(37),* Track ’,track:3); writeln(’ ******* Status *******'); writeln(errorl:5,error2:5,error3:5,error4:5); end; writeln (' Backup ready !!’) end; repeat until keypress; end.

Listing 1

Beim Beispiel des einfachen Diskcopy-Programms (Listing 1) werden aus ZeitgrĂŒnden immer neun Sektoren, also ein gesamter Track, gelesen; der Puffer muß deshalb ausreichend dimensioniert werden. Die gelesenen Daten werden danach sofort auf die Diskette in Laufwerk B geschrieben. Das Programm benötigt deshalb zwei Laufwerke. Außerdem ist zu beachten, daß es momentan von zweiseitigen Laufwerken ausgeht, denn es wird jeweils ein Track auf der Vorder- und RĂŒckseite gelesen. Bei Verwendung von einseitigen Laufwerken entfallen dann die Lese- und Schreibbefehle fĂŒr die zweite Seite (mit jSF 314] gekennzeichnet).

WĂ€hrend des Kopierbetriebs werden die Nummern des gerade in Arbeit befindlichen Tracks und die RĂŒckgabewerte angezeigt. Das Programm macht keine Fehlerabfrage, die Fehlerwerte werden jedoch sehr kurz (!) angezeigt. Eine Verbesserung an dieser Stelle ist dringend zu empfehlen.

Will man den Inhalt eines Sektors nÀher untersuchen oder anschauen, so ist die Definition des Puffers entscheidend. Ist dieses Feld als CHAR definiert, erhÀlt man den Sektorinhalt als Zeichen, bei Definition als BYTE erscheinen die entsprechenden Zahlenwerte.

Laufwerkskennung

Manchmal ist es notwendig, das momentan aktuelle Laufwerk zu kennen. DafĂŒr gibt es die Routine DGETDRV. Sie liefert den gewĂŒnschten numerischen Wert des momentanen Laufwerks und, nach einer einfachen Umrechnung, auch den Kennbuchstaben (siehe Listing 2).

Das Laufwerk, von dem ein Programm geladen wurde, ist das momentan aktive Laufwerk. Es wird beim Diskettenzugriff ohne Diskangabe angesprochen. Will man ein anderes Laufwerk zum aktiven erklÀren, verwendet man folgende Routine:

altdrive:= DSETDRV (neudrive)

wobei Laufwerk A = 0; B = 1; C = 2; ...

In altdrive wird die Nummer des vorherigen aktiven Laufwerks zurĂŒckgegeben.

Speicherplatz

Uber die Aufteilung eines Massenspeichers in Cluster, Sektoren, Byte, belegte und freie Bereiche gibt die Gemdos-Funktion DFREE Auskunft. Sie benötigt als Angabe nur die Laufwerksnummer (wobei Null dem aktuellen Laufwerk (!) entspricht, A=1, B=+ usw.) und gibt dann in einem Puffer vier Werte zurĂŒck:

buff[1] Anzahl der freien Cluster buff[2] Anzahl der Cluster buff[3] Anzahl der Byte/Sektor buff[4] Anzahl der Sektoren/Cluster

Daraus lĂ€ĂŸt sich dann die Anzahl der freien, belegten und aller verfĂŒgbaren Bytes berechnen (siehe Listing 3).

Directory

Eine bei manchen Anwendungen wichtige Routine ist die Anzeige des Directories. Dazu sind mehrere Systemaufrufe nötig. Der erste lautet FSETDTA und setzt die Disketten-Transfer-Adresse, ab der DTA-Puffer steht. In diesem Puffer werden, von den folgenden Befehlen, die kompletten Directory-Informationen abgelegt. Der Puffer muß eine GrĂ¶ĂŸe von 44 Byte haben und kann als ARRAY oder RECORD angelegt werden. Er enthĂ€lt folgende Informationen:

Byte Inhalt
1...21 reserviert fĂŒr TOS 22 Attribut
23.. .24 Uhrzeit
25..26 Datum
27..30 FilelÀnge (hexadezimal)
31.. .44 Filename und Extention
program LAUFWERK; VAR drive : char; function DGETDRV: integer; gemdos( $19 ); begin drive:=chr(DGETDRV+65); write('Sie benutzen momentan '); writeln('Laufwerk drive); readln end.

Listing 2

program MEMORY; TYPE buf4 = array [1..4] of long_integer; VAR buff : buf4; drive,r : integer; memory,freemem,usedmem : long_integer; function DFREE (VAR buff: buf4; drv: integer): integer; gemdos($36); begin write('welches Laufwerk ( 0=akt., 1=A, 2=B, usw. ): '); readln(drive); writeln; r:=DFREE(buff,drive); { 1 = Laufwerk A, 2=B, usw. } writeln('Funktionsrueckgabe : ',r); writeln; writeln('freie Cluster : ',buff[1]); writeln('Anzahl der Cluster : ',buff[2]); writeln('Anzahl Byte/Sector : ',buff[3]); writeln('Anzahl Sectoren/Cluster: ',buff[4]); writeln; freemem:=buff[1]*buff[3]*buff[4]; writeln('frei : ',freemem,' Byte'); usedmem: = (buff[2]-buff[1])*buff[3] *buff[4]; writeln('belegt : ',usedmem,' Byte'); memory:=buff[2]*buff[3]*buff[4]; writeln('gesamt : ',memory,' Byte'); readln end.

Listing 3

Nach Festlegung der Puffer-Adresse erfolgt der Aufruf FSFIRST. Dieser sucht das erste Diskettenfile, das mit dem angegebenen Muster (Pfadname) ĂŒbereinstimmt und schreibt die Informationen in den Puffer. Dieses Muster kann auch 'Joker’ (BruchstĂŒcke eines Namens) enthalten (z. B. *.PAS oder A???????.PAS). Der zweite Parameter dieser Funktion ist das Dateiattribut, das eine weitere Selektierung der zu suchenden Dateien festlegen kann. Wenn hier eine Null ĂŒbergeben wird, sind die Unterdirectories ausgeblendet, bei einem Wert von 16 werden auch sie angezeigt (siehe Tabelle).

Wert Bedeutung
0 Schreib-/Lesedatei
1 Nur-Lesedatei
2 versteckte Datei
4 System-Datei
8 Volume-Label
16 Unter directory

Zum Suchen eines weiteren Eintrages dient die Funktion FSNEXT. Diese benötigt keine Parameter (die von FSFIRST gesetzten sind weiterhin gĂŒltig), liefert aber eine Fehlernummer und schreibt die Fileinformation in den DTA.

Das Programm DIRECTORY (Listing 4) zeigt, wie das Directory eines beliebigen Laufwerkes gelesen und auf dem Bildschirm angezeigt wird. Dieses Programm verwendet fast alle der bereits besprochenen Routinen und gibt die Directory in ’aufbereiteter’ Form aus (siehe Bild 2). Das jeweilige Directory-File wird hier in einem Record abgelegt, um einen unkomplizierten Zugriff auf die einzelnen Elemente zu haben. Bei nĂ€herer Betrachtung dieses Records wird Ihnen auffallen, daß das Datum und die Uhrzeit in jeweils einer Integer-Zahl abgelegt werden. Der genaue Aufbau beider Zahlen kann der Grafik 3 entnommen werden. Die einzelnen Daten (z. B. Tag, Monat, Jahr) mĂŒssen erst voneinander 'getrennt’ werden. Diese Arbeit erledigt das Programm mit einigen Schiebe-Befehlen (ShR & ShL), die man sich eventuell etwas genauer anschauen muß.

Der eigentliche Filename ist in einem ARRAY of CHAR abgelegt und wird als Einzelbuchstabe ausgegeben, bis sein letztes Zeichen (chr(O)) erreicht wird.

In den nÀchsten Ausgaben folgen Tips zu

  • Filebehandlung (SchĂŒtzen, Löschen, Umbenennen, ...)
  • Sound (Erzeugen und Abspielen von Musik) (Interessante Nebeneffekte)
  • I/O (Die Schnittstellen: RS232, parallel, MIDI, Tastatur)

(MN & HS)

Bild 2
program DIRECTORY; TYPE nametyp = packed array [1..14] of char; path_name = packed array [1..80] of char; DIRREC = record reserved : packed array [0..21] of byte; time : integer; date : integer; size : long_integer; filename : nametyp; end; VAR dirfile : dirrec; wdh : integer; name : String; path : path_name; ch : char; function DGETDRV: integer; gemdos( $19 ); procedure FSETDTA( VAR buf : dirrec ); gemdos( $1a ); function FSFIRST( VAR path: path_name; search_attrib: integer ):integer; gemdos( $4e ); function FSNEXT : integer; gemdos( $4f ); procedure SET_DRIVE; VAR r,drive : integer; drv : char; function DSETDRV( drive: integer ): integer; gemdos( $0E ); begin write('bitte Laufwerk angeben (A, B, C, usw.):'); read(drv); if drv in ['A'..'Z'] then drive:=ord(drv)-65 else drive:=ord(drv)-97; r:=DSETDRV(drive); if r<0 then begin writeln('Fehler bei der Eingabe !'); readln end end; procedure SHOWFILE( VAR dirfile : dirrec ) ; VAR i,jahr,monat,tag,stunden,minuten : integer; begin with dirfile do begin jahr:=shr(date,9); monat:=shr((date-shl(jahr,9)),5); tag:=date-shl(monat,5)-shl(jahr,9); write( tag:2,' ',monat:2,' ',jahr+1980:4,' '); stunden:=shr(time,11); minuten;=shr((time-shl(stunden,11)),5); write( stunden;2,':',minuten:2,' '); write( size:8,' '); i := 1 ; while filename[i]<>chr(0) do begin write( filename[i] ); i := i + 1 end; writeln end end; procedure MEMORY; TYPE buf4 = array [1..4] of long_integer; VAR buff : buf4; freemem, usedmem : long_integer; procedure DFREE (VAR buff: buf4; drv: integer); gemdos( $36 ); begin writeln; DFREE(buff,0); { 0 fuer aktuelles Laufwerk } freemem:=buff[1]*buff[3]*buff[4]; writeln('frei : ',freemem,' Byte'); usedmem:=(buff[2]-buff[1])*buff[3]*buff[4]; writeln('belegt : ',usedmem,' Byte'); end; procedure PFAD_NAME; VAR i : integer; begin write( 'Pfadname: '); { Pfad } readln( name ); if name='' then begin name:='*.*'; writeln( name ) end; name:=concat( name,chr(0) ); for i := 1 to length( name ) do path[i] := name[i] ; end; procedure DIRECTORY_LESEN; begin FSETDTA( dirfile ) ; { setzt DTA } if FSFIRST( path, 16 ) >= 0 then begin { erster Eintrag } writeln(' Datum Zeit Byte Name’); writeln('---------------------------------------') ; repeat SHOWFILE( dirfile ); { Anzeigen } wdh:=FSNEXT; { weitere Eintraege ) until wdh < 0; MEMORY; end else write( ' keine Datei gefunden !' ); readln; end; begin { Hauptprogramm } repeat writeln(chr(27),'E'); writeln('MENUE'); writeln(' aktuelles Laufwerk:',chr(DGETDRV+65)); writeln('(D)irectory lesen'); writeln('(A)usdrucken'); writeln('(L)aufwerk aendern'); writeln('(Q)uit'); writeln; write('Eingabe: '); read(ch); writeln; case ch of 'd','D' : begin PFAD_NAME; DIRECTORY_LESEN; end; 'a','A' : begin PFAD_NAME; rewrite (outputPRN;'); DIRECTORY_LESEN; rewrite (output,'CON:*) end; 'l','L': SET_DRIVE; end; until ch='q' end.