Auf dem Weg zum Programmierprofi: Druckerabfrage & Dateiheader

Bild 1. Der Aufbau des Dateiheaders von »PRG«-Dateien

Jeder, der öfter mit Druckern arbeitet, kennt das Problem: Wenn Sie versuchen, einen Text zu drucken oder sich eine Hardcopy des Bildschirminhalts ausgeben lassen, gibt der ST für einige Sekunden kein Lebenszeichen mehr von sich. Wenn Sie den Drucker offline geschaltet haben oder gar kein Drucker angeschlossen ist, versucht der Computer, Daten an ein gar nicht empfangsbereites Gerät auszugeben.

Der ST fragt intern die »BUSY«-Leitung des Druckerports ab. Sofern dem ST signalisiert wird, daß er keine Daten senden kann, wartet er eine gewisse Zeit, um dem Anwender Zeit zu geben, den Drucker eventuell anzuschalten.

Drucker antwortet nicht

Leider kann der Anwender den Computer nicht davon abbringen, eine ganze Weile zu warten und dabei ständig die »BUSY«-Leitung der Druckerschnittstelle abzufragen. Haben Sie nun versehentlich einen Befehl zur Druckausgabe gegeben, so müssen Sie sich auf etwa 30 Sekunden Wartezeit einstellen.

Um solchem Ärger abzuhelfen, bieten wir Ihnen eine kleine Routine in Omikron-Basic. Sie erlaubt Ihnen schon vor Druckbeginn die Empfangsbereitschaft des Druckers abzufragen und auf das gelieferte Resultat zu reagieren.

Unsere Routine bedient sich hierzu der BIOS-Funktion Nummer 8 (»Bcostat«). Mittels dieser Betriebssystemfunktion stellt der Computer fest, ob die betreffende Peripherie (in diesem Fall der Drucker) empfangsbereit ist. Sie sollten unsere Routine in eigene Programme einbauen, Sie ersparen sich und den Anwendern damit eine Menge Ärger über unnötige Wartezeiten.

Bereits in der April-Ausgabe des ST-Magazins haben wir auf die Probleme hingewiesen, die sich dem Basic-Programmierer stellen, wenn er ein Maschinenprogramm durch den »EXEC«-Befehl zu seinem Programm hinzuladen will [1], Dort empfahlen wir als Faustregel, für den Umgang mit TTP-Programmen etwa dreimal mehr Speicherplatz zu reservieren, als im Inhaltsverzeichnis als Dateilänge angegeben sind. Für die gebräuchlichsten TTP-Programme wie »ARC 5.21« oder »ZOO« ermittelt diese Rechnung auch passende Werte. Wenn Sie sich aber nicht darauf verlassen wollen, benötigen Sie eine Routine, die den benötigten Speicherplatz exakt errechnet.

Zu diesem Zweck muß eine Routine den sogenannten »Header«, den Dateikopf des Programms, analysieren und aus den entnommenen Daten den benötigten Speicherplatz berechnen.

Der Programm-Header

Jedes Assembler-Programm setzt sich aus drei Teil-Segmenten und der sogenannten Symboltabelle zusammen. Die Teil-Segmente heißen »Text«-Segment, »Data«-Segment und »BSS«. Die Segmentierung, also die Zerteilung des Programmes in seine Einzelbestandteile, nimmt das Betriebssystem beim Laden des Programms vor. Im Text-Segment befindet sich der Programmcode. Der Assembler »übersetzt« alle Assemblerbefehle in den für den 68000er-Prozessor verständlichen Maschinencode und speichert diesen im Text-Programmteil.

An diesen Teil schließt das Data-Segment an. Darin legt der Atari ST einen Speicherbereich für konstante Daten an. In diesem Segment finden Sie also beispielsweise alle Meldungen, die der Computer während des Programmlaufs ausgibt, Icon-Block-Strukturen oder ganze Ressource-Dateien.

Diese beiden Segmente haben eines gemeinsam: Der Programmierer kann den Inhalt dieser Segmente im User-Prozessormodus nicht verändern und sollte es auch im Supervisor-Modus nicht tun. Wenn Sie dies beachten, haben Sie später Gelegenheit, die Programme auch auf ROM-Bausteinen zu benutzen. Andernfalls nicht, denn es ist ja schon hardwaremäßig unmöglich, den Inhalt von ROMs zu verändern. Wenn Sie veränderbare Daten (zum Beispiel Variablen) benutzen, legen Sie diese im BSS ab. In das »Block Storage Segment« dürfen Sie auch Daten schreiben, also im Gegensatz zum Data-Segment nicht nur lesen. Dieses Segment speichert der Atari ST bei der Assemblierung nicht gänzlich ab, er hinterläßt nur eine Nachricht über die BSS-Größe an das Betriebssystem. Das TOS reserviert automatisch einen Speicherblock, sobald der ST das Programm geladen hat. In Assembler-Programmen verfahren Sie deshalb so, als würde das BSS grundsätzlich mitgespeichert.

Die Segmentierung hat erhebliche Vorteile gegenüber nicht-segmentierenden Systemen. Möchten Sie beispielsweise Texte innerhalb eines Programmes aus dem Englischen ins Deutsche übersetzen, so mußten Sie als Programmierer auf älteren Computersystemen das ganze Programm durchsehen und an den passenden Stellen verändern. Heute brauchen Sie nur das Data-Segment zu übersetzen, mehr Arbeit fällt im allgemeinen nicht an.

So nützlich sind Symboltabellen

Auch die Anpassung eines Programmes an Festspeicher wie ROMs oder EPROMs nimmt kaum noch Arbeit in Anspruch, da der Atari ST alle veränderlichen Daten von vornherein getrennt vom Programmcode aufbewahrt. Außer einigen wenigen Anpassungsschritten fällt kaum Arbeit an.

Im Anschluß an diese Daten befindet sich manchmal noch eine »Symboltabelle«. Diese enthält Informationen über die im Assembler-Programm benutzten Labels, zu deutsch »Lesemarken«. Das Betriebssystem benutzt die Symboltabelle überhaupt nicht, allerdings die Tabelle bei der Fehlersuche durch Debugger-Programme oder bei der Disassemblierung.

Bei der Disassemblierung verwandelt ein Programm den Maschinencode eines anderen Programmes wieder in lesbare Assembler-Befehle. Dabei anfallende Labels benennen die Disassembler normalerweise nach dem Speicherplatz, an dem sie die Einsprungadressen des betreffenden Programmes gefunden haben. Diese Schreibweise ist allerdings schwer verständlich. Deshalb suchen viele Disassembler nach einer eventuell vorhandenen Symboltabelle und benutzen die hier definierten Labels anstelle der Speicheradressen.

In käuflicher Software finden Sie leider nur selten Symboltabellen. Dies hat vor allem zwei Gründe:

Erstens belegen Symboladressen viel Speicher und tragen zu erhöhten Lade- und Kopierzeiten bei. Darüber hinaus ist der freie Speicherplatz auf Disketten beschränkt.

Zweitens lassen sich die Softwarefirmen nur ungern »in die Karten schauen«. Die Firmen haben — verständlicherweise — kein Interesse daran, daß jemand ihre mühevoll entwickelten Programme disassembliert (also rückübersetzt) und Routinen entnimmt.

Wenn Sie kurze Programme »für den Hausgebrauch« entwerfen, sollten Sie nicht auf Symboltabellen verzichten, denn die Wahrscheinlichkeit ist hoch, daß Ihre Programme anfangs fehlerhaft sind. Bei der Fehlersuche helfen die Symboltabellen.

Dateispionen auf der Spur

Was passiert, wenn Sie ein Programm starten? Das TOS lädt die verschiedenen Segmente in den freien Speicher. Anschließend reserviert das Betriebssystem einen Bereich für das BSS. Bevor es beginnt, das Programm zu laden, muß es wissen, wieviel Speicher es dafür benötigt. Der ST darf nicht erst nach langer Ladezeit bemerken, daß nicht genug Speicher zur Aufnahme des Programmes frei ist. Folglich stehen diese Informationen im Header, dem Dateikopf am Anfang jedes Programmes. Dies ist eine Art »Vorspann« für ein Programm. Er enthält ausschließlich Informationen über die Länge der drei Segmente und die Länge der Symboltabelle sowie die Einsprungsadresse des Programmes. Er wird vom Linker, einem Zusatzprogramm, zu dem bereits bereitstehenden Maschinencode hinzugefügt. Bei vielen Assemblern müssen Sie den Linker nach dem Assembliervorgang aktivieren. Es gibt auch einige wenige Assembler, unter ihnen beispielsweise den GFA-Assembler, die dies bereits automatisch vornehmen. Die Zusammensetzung des Headers sehen Sie in Bild 1.

Am Anfang des Headers befindet sich in den Bytes SO bis $1 ein »Branch«-Befehl, den Atari »ph_branch« getauft hat. Mit diesem Befehl startet das Betriebssystem grundsätzlich Ihr Programm. Da die Länge des Headers immer $1C (dezimal 28) Bytes ist, stehen hier grundsätzlich die Werte $60 und S1A. Die Länge des Headers ist in [2] falsch angegeben, wir gehen darauf später noch ein. Die Zahl $60 (dezimal 96) bedeutet für den MC68000 soviel wie »springe um n-Stellen im Speicher und fahre dort fort«. Wie viele Speicherzellen der Prozessor überspringen soll, steht im folgenden Byte. Der Prozessor überspringt die angegebenen $1A (dezimal 26) Bytes und fährt dort fort, wo sich der eigentliche Programmanfang befindet. Atari hat »ph_branch« als $601A definiert, ausführbare Programme sind an ihm zu erkennen.

Auffällig ist weiterhin die Funktion der Bytes $1A und $1B im Header. Wenn man Atari und den Entwicklern des TOS, der Firma Digital Research, Glauben schenken darf, so sollten diese Bytes immer den Wert 0 annehmen. Eine Funktion haben die Entwickler nicht angegeben. Steht hier nämlich ein anderer Wert als 0, so segmentiert das Betriebssystem das geladene Programm nicht und richtet auch kein BSS ein.

Die Länge dieses Flags mit der Bezeichnung »ph_flag« haben wir in [1] leider falsch angegeben. Es handelt sich hierbei keinesfalls um ein 4 Byte langes Longword, sondern um einen ganz normalen Integer-Wert mit einer Länge von 2 Byte.

Nach dem Anzeigen des Anfangs-Branches »ph_branch« errechnet unser Programm die Größe des Speicherplatzes, der zur Unterbringung des gewünschten Programmes mindestens nötig ist. Sie brauchen sich also in Zukunft nicht mehr auf Versuche einzulassen, wenn Sie Programme durch den »EXEC«-Befehl Ihres Basics starten wollen. Lassen Sie sich einfach den nötigen Speicher mit unserem Programm berechnen und reservieren Sie ausreichend Speicherplatz in Ihrem Programm. Eventuell sollten Sie 5 bis 10 KByte RAM mehr zur Verfügung stellen, da einige Programme ihre eigene Länge nicht korrekt errechnen und etwas mehr Speicher belegen, als notwendig ist.

Leider arbeitet auch dieses Verfahren nicht problemlos. Dies liegt allerdings weder an den Programmierern des ST-Betriebssystems noch an unserem Programm, sondern . vielmehr an der Funktionsweise einiger Programme. So geben manche Programme den nicht zur Code-Speicherung benötigten Speicher nicht frei, sondern belegen den gesamten freien Speicher. Beispiele dafür gibt es genug. Diverse Terminalprogramme oder Zeichenprogramme erweisen sich als regelrechte »Speicherfresser« und benutzen jedes freie Byte, das sie bekommen können. Stellen Sie sich als ein weiteres Beispiel ein Textverarbeitungsprogramm vor, dem Sie gerade soviel Speicher zur Verfügung stellen, wie dessen eigener Code benötigt. Dann bleibt kein Platz mehr für die Texteingaben übrig. Natürlich bricht das Textverarbeitungsprogramm mit einer Fehlermeldung den Programmlauf ab.

Ähnliches passiert Ihnen bei vielen Kopierprogrammen oder Datenverarbeitungen.

Sollten Sie auf solche Probleme stoßen, so helfen dem Basic-Programmierer erneut nur Versuche. Vergrößern Sie den zur Verfügung gestellten Speicher langsam und schrittweise, bis sie das gewünschte Resultat erhalten. Sofern Sie einige Erfahrung mit der Assembler-Programmierung haben, versuchen Sie mit einem Debugger die benötigte Speichergröße herauszufinden. Dies ist aber eine sehr knifflige Angelegenheit, die wir hier nicht empfehlen möchten. Ein Debugger-Programm, mit dem so etwas theoretisch möglich wäre, ist mittlerweile in fast jedem Assembler-Paket eingeschlossen. Ein sehr guter Debugger ist auch das Public Domain-Programm »Tempelmon« von Thomas Tempelmann.

Falls weitergehendes Interesse besteht, kommen wir in einer der kommenden Ausgaben erneut auf das Thema »Header« zurück. Denn fast jeder Dateityp hat sein fest definiertes Format und einen Header, an dem er eindeutig identifizierbar ist. (tb)

Literatur:

[1] L. Prüßner, »Auf dem Weg zum Profi«, ST-Magazin 4/1989, Seite 80ff.

[2] H.-D. Jankowski, J.F. Reschke, D. Rabich, »Atari ST Profibuch«, 5. Auflage, Sybex-Verlag, Düsseldorf, 1988, ISBN 3-88745-563-0, Preis 69 Mark

' Drucker Online?
' Kurzroutine zum feststellen des Druckerstatus‘ von L. Prüssner 
' Geschrieben in Omikron.Basic.
Drucker%=0: REM bei seriell arbeitenden Druckern muss hier "1" stehen 
BIOS (Fertig%L,8,Drucker%)
IF NOT Fertig%L 
THEN PRINT "Ihr Drucker ist noch nicht online!"
END
ELSE
PRINT "Drucker empfangsbereit."
ENDIF

Listing 1. Ist der Drucker empfangsbereit?

' Header-Analyse 1.4
'
' Geschrieben im April 1989 von L. Prüssner 
' Sprache: GFA - Basic 2.02
'
Cls
Print "Header-Analyse von L. Prüssner." 
Print "Dieses Programm liest den Header "; 
Print "eines Programmes und ermittelt"
Print "daraus den benötigten Speicher." 
Print
Print "Welcher Programm-Header ";
Print "soll untersucht werden?"
Print
Fileselect "\*.*","",A$
If A$=""
    End
Endif
Print "Ich untersuche ";A$
Print
If Not Exist(A$)
    Alert 1,"Dieses Programm gibt's|ja gar nicht!",1,"Nein?",A%
    Run
Endif
Open "I",#1,A$
A%=Inp(#1)
If A%<>&H60
    Alert 1,"Die gewünschte Datei|hat keinen Header !",1,"Hmmm ?",A%
    Run
Endif
A%=Inp(#1)
If A%<>&H1A
    Print "Der Header beginnt zwar mit $60," 
    Print "hat aber eine falsche Einsprungsadresse!"
    End
Endif
A$=Input$(4,#1)
Txtlength%=Lpeek(Varptr(A$))
Print "Das .TEXT-Segment ist $";
Print Hex$(Txtlength%);" Bytes lang."
A$=Input$(4,#1)
Datlength%=Lpeek(Varptr(A$))
Print "Das .DATA-Segment ist $";
Print Hex$(Datlength%);" Bytes lang."
A$=Input$(4,#1)
Bsslength%=Lpeek(Varptr(A$))
Print "Das .BSS ist $";
Print Hex$(Bsslength%);" Bytes lang."
A$=Input$(4,#1)
Symbol%=Lpeek(Varptr(A$))
Print
If Not Symbol%
    Print "Eine Symboltabelle wurde nicht angehängt"
Else
    Print "Es wurde eine zusätzliche ";
    Print "Symboltabelle hinzugefügt, die $";
    Print Hex$(Symbol%);" Bytes belegt."
Endif
Ges%=Txtlength%+Datlength%+Bsslength%+Symbol%
Print
Print "Der Mindestens benötigte Speicher ";
Print "beträgt demzufolge $";
Print Hex$(Ges%);" Bytes."
Print "In Dezimalschreibweise sind das ";
Print Ges%;" Bytes oder ";
Print Int(Ges%/1024)+1;" kBytes."
A$=Input$(2,#1)
Reloc%=Dpeek(Varptr(A$))
If Reloc%=0
    Print "Das Programm wird ganz normal geladen." 
Else
    Print "Das Programm wird nicht reloziert "; 
    Print "oder Segmentiert"
    Print "und das BSS wird nicht gelöscht."
Endif 
Close #1 
Pause 500 
Run

Listing 2. Dieses Programm untersucht den Datei-Header


Laurenz Prüßner
Links

Copyright-Bestimmungen: siehe Über diese Seite