Turbo Pascal 3 / Assembler Profis gesucht: Aus 8-Bittern das letzte Quentchen Speed rausholen

  • Guten Abend!


    Ich besitze einen NCR-DMV, ein 8-Bit-Bus System mit NEC V20 Cpu. Die modernste Software darauf ist TurboPascal 3 oder zB auch Wordstar 3.4 (mit Patch und zusätzlicher Hilfssoftware auch Wordstar 4). Das ganze unter DOS 2.11, 512KB RAM, 20MB Harddisk. Das Gerät ist so gut wie nicht "IBM-kompatibel" (andere Portnummern, andere BIOSdatenadressen, etc.).


    Der DMV ist seit Jahrzehnten gut verstaut und ich hoffe, er läßt sich problemlos wieder in Betrieb nehmen, wenn ich erstmal einen genügend großen Arbeitsplatz dafür gefunden habe.


    Jedenfalls habe ich vor, einige meiner Programme von damals (Ende 80er/Anfang 90er Jahre) nochmals zu überarbeiten, vor allem mit dem Ziel aus der Maschine mit deutlich mehr Wissen heute als damals rauszuholen was nur geht.


    Nach Durchsicht einiger der sourcecodes von damals sehe ich enormes Potential zur Verbesserung in vielerlei Hinsicht und ich habe in den letzten Monaten im "Trockenen", also ohne irgendwie testen zu können, begonnen, die Programme zu überarbeiten.


    Im Netz finden sich auch heute noch Tonnen an Sourcecodes (Pascal, gemischt mit Assembler), um die Turbo-eigenen Routinen durch schnellere Varianten zu ersetzen. Dabei dreht sich alles um schnelle Videoausgabe oder Ersatz der "string"-routinen wie pos(), insert(), delete(), upcase(), copy(), move() oder auch Vergleichsoperationen von Speicherblöcken, min() max() etc.


    Nicht gefunden habe ich auch nur ein einziges Stück sourcecode, das sich mit den FILE procedures beschäftigt, also zB BLOCKREAD/WRITE(). Tips wie man sie optimal parametrisiert gibt es schon, aber keinen einzigen Versuch, die Routinen TurboPascals durch eigene zu ersetzen.


    Die blockread-routine, der ein file-handle (nach assign(), reset()/rewrite()) übergeben wird, liest in einen vorbereiteten Speicherbereich X eine vorgegebene Menge Y Bytes einer Datei ein. Das ist natürlich in den meisten Anwendungsfällen notwendig und unumgänglich, nicht aber, wenn eigentlich mit den bytes/words (einzeln) gar nicht viel geschehen soll, sie also nicht wie zB für einen Editor lange Zeit im Speicher gehalten und im Verbund bearbeitet werden.


    Wenn also wie in meiner Aufgabenstellung die Bytes/Words der Dabei nur als solche Einheiten von Interesse sind, also nicht ihre Zusammensetzung zu größeren Strukturen wie records/strings und ihre Be-/Verarbeitung minimal ist und sie sofort an ein anderes device (ser/par/vga/disk) weitergegeben werden, ist der Einsatz der von turbopascal zur Verfügung stehenden readfile-routinen in Bezug auf möglichst schenllen Programmablauf stark verbesserungsfähig.


    Ein Beispiel:


    Der NCR-DMV hat eine Grafiklösung (NEC yGD7220), die anders als die "IBM"kompatiblen den Videospeicher nicht an bestimmten Stellen (B000/B800) im Hauptspeicher einblendet und dort zur schnellen Bearbeitung zur Verfügung stellt, sondern nur über eine 8bit-Port ein- und ausgelesen werden können. Wie alle anderen devices auch.


    Wenn nun zB auf Disk abgespeicherte Screenshots (4000 bytes, 25x80*2) eingelesen, das einzelne Attributbyte abgeändert und das Paar Zeichen/Attribut am Bildschirm ausgegeben werden sollen, so wäre die "natürliche" Programmierung in Kurzform etwa so (Pseudocode):


    var buffer:[0..3999] of byte

    f:file

    bytesgelesen:integer;


    fname = "C:\scrdata\scrshot.001"

    assign(f,fname)

    reset(f)

    blockread(f, buffer, 4000, bytesgelesen)

    show_screenshot(buffer)


    Das sieht völlig normal aus und ich glaube jeder würde im Grunde die Aufgabe so lösen. Auf der Suche das letzte Quentchen Geschwindigkeit rauszuholen bin ich zum Schluß gekommen, daß das in Wahrheit hochgradig ineffizient und langsam ist.


    Bei 4 involvierten 8-bit-Devices, CPU (intern 16 bit), DISK, RAM, VGA), die über einen 8bit-Bus miteinander kommunizieren, tauschen alle Geräte die Daten seriell-byteweise miteinander aus. Das geht gar nicht anders.


    Was passiert auf TurboPascal-Ebene bei obigem Programm eigentlich (grob):



    TPs blockread: Aufgabe: 4000 bytes von DISK in den RAM Speicherbuffer einlesen

    Annahme: turbos blockread bedient sich des interrupts 21H, Funktion 3FH, und übergibt diesem dazu den file-handle (erhalten von assign()), die Anzahl der zu lesenden Bytes und die Adresse des Pufferspeichers)

    Interrupt-routine:

    * DISK -> BUS -> CPU (Einlesen in die CPU)

    * CPU -> BUS -> RAM (Schreiben in den buffer[i])


    show_screenshot: 2000 zeichen einlesen, 2000 attribute einlesen, 2000 attr nochmals einlesen, 2000 veränderte Attribute schreiben (RAM), 2000 zurück in die CPU, 2000 in die VGA

    * RAM -> BUS -> CPU (einlesen Zeichen ch:=buffer[i])

    * CPU -> BUS -> VGA (an den VGA schicken )

    * RAM -> BUS -> CPU (attr einlesen und verändern des Attr zB: buffer[i+1]:=buffer[i+1] AND 15

    * CPU -> BUS -> RAM (schreiben des Attributs nach buffer[i+1])

    * RAM -> BUS -> CPU (wieder einlesen für ausgabe attribut nachg vga)

    * CPU -> BUS -> VGA (an den VGA port schicken)


    Wie man sieht ist verdammt viel los am BUS und im RAM. Für cycles-Fuchser viel zu viel, denn das ließe sich zb mit einem Assembler-Unterprogramm (Kernroutine) jedenfalls einfacher lösen:


    2000x:

    * disk -> bus -> CPU (Zeichen in CPU einlesen)

    * disk -> bus -> CPU (attr in CPU einlesen)

    ---------------- CPU (in der CPU attribut direkt ändern)

    2000x:

    * CPU -> bus -> VGA (zeichen in den vga-port schreiben)

    * CPU -> bus -> VGA (attr in den vga-port schreiben)


    Mit vergleichsweise deutlich weniger Programmcodebytes ohne auch nur ein Datenbyte im RAM zwischenzuspeichern.


    Im letzten Fall würden nur 8000 Datenbytes über den Bus geschickt werden.


    Ganz anders im ersten Fall. Hier werden 4000 + 4000 + 6x2000 = 20000 Bytes über den Bus geschickt, gemeinsam mit einem x-fachen Aufwand an Programmcodebytes und der Dauer der Ausführung.


    Es sollte sich hier also eine deutliche Verringerung der Gesamtablaufsdauer durch entsprechende direkte Programmierung einer "blockread"-Ersatzprozedur in einem external-Assemlberprogramm herbeizaubern lassen.


    Es müßte also eigentlich eine Routine geschrieben werden, die im Prinzip das gleiche tut wie die TP-Blockread / Dos-Interruptroutine (aber keine ist), bis auf daß die Daten nicht in einen RAMbuffer geschrieben werden, sondern gleich wie hier zB an den VGA-port weitergeleitet werden.


    Jetzt könnte der Einwand gebracht werden, daß es doch schneller sein müßte, einen großen Block in einem Rutsch zu lesen. Und einen großen Block in eiem zurüchzuschreiben (wohin auch immer). Das stimmt wohl für den allergrößten Teil der Anwendungen, nicht aber in einem Fall wie geschildert, wo die Daten mit minimaler Änderung, welche gleich direkt in der CPU vorgenommen werden kann, an ein Zielgerät geschickt werden sollen. Die Disk wie jedes andere Device hat einen 8 Bit Port. Alle Daten müssen stets durch dieses Nadelöhr, hintereinander. Blockzugriff hin oder her.


    Ein weiterer Anwendungsfall fällt mir dazu ein, eine Datei von einem LW (A) auf ein anderes (B) zu kopieren. Auch dazu wäre ein Auslagern der Daten ins RAM unnötig und kostet enorm Zeit. Das gleiche LW darf es zwar technisch sein, aber die zehntausendfachen Lesekopfausrichtungen auf andere Sektoren wäre überaus zeitaufwändig und würde die LWsmechanik extrem belasten. Außerdem werden Sektoren immer als ganzes von der LWsintelligenz in interne Buffer gelesen, die jedesmal verworfen werden müßten, weil nach jedem Schreibvorgang neuerlich derselbe vorherige Lesesektor eingelesen werden müßte um innerhalb dessen das nächste Byte am Port auszugeben.


    Ich weiß zwar, wie man mit den Lese/Schreibvorgängen über die Ports von VGA/ser/par Schnittstelle umgeht, nicht aber bei Laufwerken.


    Wenn sich hier im Forum jemand damit auskennt, würde ich mich über entsprechende code-Schnippsel sehr freuen.

  • Du kannst von Disketten nur blockweise lesen, auch bei direkter Ansteuerung

    der Hardware kann der Floppycontroller nur sektorweise lesen und in einen Speicherbereich schreiben.

    Dass es da keine optimierten Routinen gibt liegt vermutlich daran daß es nichts zu optimieren gibt.


    Unklar ist auch was du mit 2000 Zeichen an den VGA Port schicken meinst. Der DMV hat keine VGA Karte, nur

    ein serielles Terminal oder/und den NEC Grafikprozessor.


    Lt. einer Übersicht in dem Dokument "NCR DECISION MATE V Grafik-Beschreibung (7220 Chip)"

    gab es für Turbopascal ein Paket Turbograf von der Firma

    COMSOFT in Stadtbergen.

  • Ich lese hier, der NCR-DMV läuft unter MS-DOS, ohne wirklich kompatibel zum IBM Standard zu sein, weil BIOS und IO-Adressen anders sind, NEC V20 CPU, NEC yGD7220 Grafikchip - evlt. 2 Stück davon?


    Das alles hört sich sehr nach NEC-PC98 Standard an?

    1ST1

  • Du kannst von Disketten nur blockweise lesen, auch bei direkter Ansteuerung

    der Hardware kann der Floppycontroller nur sektorweise lesen und in einen Speicherbereich schreiben.

    Dass es da keine optimierten Routinen gibt liegt vermutlich daran daß es nichts zu optimieren gibt.

    So ist es. BlockRead ist am schnellsten, wenn der Puffer im Speicher möglichst groß und möglichst ein Vielfaches der Sektorgröße ist. Da gibt's nicht viel zu optimieren.


    Den Transfer aus dem gelesenen Puffer in die Grafikkarte könnte man eventuell mit DMA ein bißchen schneller machen - das hängt aber extrem davon ab, ob es zum Einen überhaupt einen DMA-Controller gibt und ob der zum Anderen überhaupt den Port der Grafikkarte als Ziel haben kann.

  • Pyewacket,


    Unklar ist auch was du mit 2000 Zeichen an den VGA Port schicken meinst. Der DMV hat keine VGA Karte, nur

    ein serielles Terminal oder/und den NEC Grafikprozessor.

    ich schrieb doch:

    Der NCR-DMV hat eine Grafiklösung (NEC yGD7220), die anders als die "IBM"kompatiblen den Videospeicher nicht an bestimmten Stellen (B000/B800) im Hauptspeicher einblendet und dort zur schnellen Bearbeitung zur Verfügung stellt, sondern nur über eine 8bit-Port ein- und ausgelesen werden können. Wie alle anderen devices auch


    Du kannst von Disketten nur blockweise lesen, auch bei direkter Ansteuerung

    der Hardware kann der Floppycontroller nur sektorweise lesen und in einen Speicherbereich schreiben.

    Ich schrieb doch

    " Außerdem werden Sektoren immer als ganzes von der LWsintelligenz in interne Buffer gelesen, die jedesmal verworfen werden müßten, weil nach jedem Schreibvorgang neuerlich derselbe vorherige Lesesektor eingelesen werden müßte um innerhalb dessen das nächste Byte am Port auszugeben."


    Und ich schrieb auch:

    "Die Disk wie jedes andere Device hat einen 8 Bit Port. Alle Daten müssen stets durch dieses Nadelöhr, hintereinander. Blockzugriff hin oder her."


    Der Controller von HD/FD verfügt über einen internen Pufferspeicher (der WD1002 hat 1024Byte), in den jener Sektor geladen wird, der vom BIOS/DOS/Software angefordert wurde. Es entscheidet das Programm, wieivele Bytes es haben möchte, nicht der Controller.


    Dagegen kann der Controller gar nicht anders, als gleich einen vollständigen Sektor (bzw. soviele wie im internen Puffer Platz haben) von der Platte zu laden, weil 1. eijn Sektor die kleinstmögliche einer Datei zugehörige Speichereinheit ist und 2. ein Sektor sowieso nur zu einer Datei gehören kann und nicht zu zweien.

    Das der Controller einen ganzen Sektor in seinen eigenen Puffer erstmal einliest ist ja auch sinnvoll, weil man davon ausgehen kann, daß selbst wenn ein Datensatz in einer Datendatei nur 1 Byte Länge hat, 511 (bei 512Byte Sektoren) weitere Datensätze gleich zur weiteren Verfügung stehen, ohne daß der Controller ein weiteres Mal lesen muß.


    Der Punkt ist, daß wenn ich wie beschrieben den blockread() Befehl von turbo pascal verwende, ich damit automatisch veranlaße, daß die angeforderte Anzahl an Bytes in den Buffer im RAM geschrieben werden. Das schreiben dorthin macht aber nicht der Controller - der spuckt über seinen Port einzelne Bytes aus und die gehen über den Bus an die CPU, nicht in den RAM - sondern die von blockread() selbst verwendete DOS Interrupt routine, der mitgeteilt wird, wieviele Bytes auszulesen sind was sie mit diesen Bytes machen soll: an die übergebene Adresse des Puffers im RAM schreiben.


    Der HD/FD-Controller weiß nicht was mit den Bytes die seinen Datenport verlassen geschieht. Er ist nur dazu da auf Anforderung hin x Bytes zu liefern.


    Eine eigenen Routine hingegen nimmt die Daten entgegen und reicht sie direkt an die VGA weiter. Ganz ohne RAM Nutzung und damit einhergehende Busaktivität. Und somit VIEL schneller.

    Disk [ portnummer ] ----> CPU (entnimmt diskport 2 bytes, schickt diese direkt an -----> VGA [ portnummer ] ----> CPU nimmt die nächsten 2 Bytes entgegen - schreibt sie in den VGA-port usw.usf. bis daß alle 4000 (2000 Zeichenbytes, 2000 Attributbytes) übertragen wurden.

  • tofro,


    siehe bitte auch meine Antwort auf Pyewacket.


    Die Verwendung einer optimalen Blockgröße ist Optimierung auf TurboPascal-Ebene. und dann ist eben Schluß. Man holt damit das Maximum des Machbaren mit der blockread() Methode heraus.

    Man kann dem TP aber nicht mitteilen, daß es die CPU veranlassen soll, die von der DISK kommenden Bytes in einem CPU-Register zu halten und gleich an einen VGA-Port weiterzureichen. Der TP blockread() Befehl lautet "lade x Bytes in den Pufferspeicher im RAM".


    Selbstverständlich macht es Sinn statt 4000 Mal 1 Byte gleich 1 x 4000 anzufordern. Aber das ändert gar nichts an der beschriebenen Problematik - es istja die Problematik bei gewissen Anwendungen. Der "Block" hat bei gewissen Anwendungen überhaupt nichts im RAM verloren weil er dort gar nicht hingehört - das Ziel ist der VGA-RAM.

    In meinem beschriebenen Szenario ist die Blockgröße, wenn man so will, 2 Bytes groß (Zeichen + Attribut). Die passen in ein CPU-Register (WORD) und können dort maximal schnell bearbeitet/verändert werden. Und dann werden die direkt dorthin gebracht, wo sie hingehören, ins VRAM und nicht ins RAM, per Portzugriff auf den Grafikcontroller.

  • Wahrscheinlich ist bei einer Architektur wie beim DMV die beste Optimierung sowieso die, dass man, anstatt direkt zu versuchen, den Framebuffer zu speichern und neu zu beschreiben (wozu der GDP eigentlich nicht so toll geeignet ist), stattdessen die Zeichenprimitive speichert und wieder neu in den GDC einspielt. Das dürfte vom Datenaufkommen her ein Wesentliches einsparen.


    Das andere kann ich aus der Ferne schlecht beurteilen ohne die Hardware zu kennen, halte es aber für extrem unwahrscheinlich, dass der Floppycontroller einzelne Bytes zum Abholen bereitstellt. An sich hat man schon sehr früh gesehen, dass das extrem unperformant ist.

  • tofro


    Ein jedes Gerät in einem PC kann in einem Rutsch nur maximal soviele Bytes herausrücken/aufnehmen, wie breit der Bus ist an den es angeschlossen ist.


    Auf einem 8-bit Bus (8088/V20 System) kann nur 1 Byte / cyklus auf die Reise von A nach B geschickt werden, auf einem 16-bit System (8086/286) können gleichzeitig 16 Bit geschickt werden, usw. bis zum heutigen 64 Bit System. Diese Busverbreiterung ist ja einer der Hauptgründe, warum die Systeme immer schneller wurden.


    Alle Geräte in einem System tauschen ihre Daten immer über den Bus miteinander und es können nie mehr als x Bits/cyclus (Vielfaches von 8 jen nach Busbreite) gleichzeitig auf die Reise geschickt werden. Alle Geräte müssen sich daran halten, besser gesagt haben gar keine Wahl. Sie sind so gebaut.


    Intern können Geräte mit höheren Busbreiten arbeiten, so ist ja zB bei Grafikkarten der VRAM meist mit 128/256 Bit ansprechbar.


    In einem System können Geräte unterschiedlicher Busbreiten (Bussysteme) zusammen sein. Die Zusammenarbeit regeln eigene Controller ("Bridges"). Heute ist es üblich, daß man PCIXpress und PCI Steckplätze findet, davor ISA (8/16Bit) gemeinsam mit PCI (32bit) und/oder VLB zu 486er Zeiten, mein Asus P5A hat 2 ISA, 5 PCI und 1 AGP Schnittstelle, die über den 32 Bit PCI-Systembus miteinander kommunizieren. Eine Komponente mit 128Bit Ansschluß, die auf einem 32bit Bus Daten liest/schreibt, hat immer dazwischen eine "Bridge" sitzen, die zB die 128bit (16 Byte) auf 4 Byte (32Bit) runter"SERIALSIERT". Dh statt 1 x 16 Bytes zu schicken tut es dies per 4 x 4 Bytes hintereinander. Die aufgeblasenste Komponente kann immer nur soviel Bytes auf einmal schicken, wie es der Systembus erlaubt. Dh pro Systemtakt. Das gilt immer. Deswegen dauert es umso länger, je größer der Block ist, weil mehr Zyklen notwendig sind, um ihn zu übertragen.


    Wieder zurück zu meinem alten 8-Bit System. Alle Komponenten haben ein 8 Bit Interface, über das sie angesprochen werden können (PORTS). Und können nur 1 Byte / Zyklus aufnehmen/rausschreiben. Weder die Festplatte noch die Flexdisk noch die Grafikeinheit noch der Hauptspeicher und auch nicht die CPU können mehr als 1 Byte / Zyklus lesen/schreiben. . Deswegen ist ja der 8086 dem 8088 in dieser Disziplin so überlegen (und nur in dieser): er ist an einem 16-Bit Bus angeschlossen und kann daher 2x soviel Bytes (/Zyklus) auf einmal entgegennehmen oder abschicken. Und wenn die anderen Komponenten auch 16-Bit Interfaces haben, dann schlägt das voll durch.


    Zugriffe in Hochsprachen wie C/Pascal etc bieten über ihre eingebauten Funktionen Möglichkeiten an, die es erlauben festzulegen, wieviel Bytes pro Zugriff eingelesen/geschrieben werden sollen. Damit erhält der Programmierer die Möglichkeit zB 8192 Bytes durch 1 !!! Aufruf der Funktion zu bearbeiten. Das ist effizienter/schneller als 8192 Mal die Funktion mit einer Zugriffsbreite von 1 Byte aufzurufen:


    blockread (input, 8192, buffer, bytesgelesen);


    versus


    for i:1 to 8192 do

    blockread (input, 1,buffer, bytegelesen)


    HInter der Fassade der blockread-funktion wird aber in beiden Fällen eine dos-funktion aufgerufen, die bei einem 8-bit Port die Bytes EINZELN hintereinander vom Diskcontroller einliest, in jener Anzahl wie sie beim Aufruf der blockread-funktion übergeben wurde.


    Und dahinter macht der Diskcontroller unabhängig von der gewünschten Anzahl an Bytes folgendes: er liest IMMER mindestens 1 Sektor in seinen internen Puffer ein (je nachdem wie groß der Puffer ist), unabhängig ob die Datei 1 Byte oder 100KB groß ist, ein. Der Sektor ist die kleinste Verwaltungseinheit des Controllers und eine Datei belegt immer mindestens 1 Sektor, auch wenn sie nur 1 Byte groß ist.


    Byte für Byte gilt übrigens auch für sogenannte DMA Zugriffe.

    Die Technik ist nur eine andere und erfordert einen DMA-Controller am Systemboard und die explizite Fähigkeit der involvierten Komponenten, DMA Zugriffe durchführen zu können. Der Vorteil der DMA Methode liegt nicht darin, daß es so etwas wie einen DMA Bus gäbe, über den parallel X Bytes / Zyklus übertragen werden können. So einen Bus gibt es nicht.

    Er liegt darin, daß abgesehen vom Setup der beiden Komponenten (also der Programmierung der Komponenten daß sie jetzt gleich einen DMA-Vorgang durchführen sollen), die CPU während des Ablaufs nicht involviert ist und sich anderen Dingen widmen kann. Die beiden Komponenten wissen nach ihrer Programmierung was zu tun ist und tun dies nach dem Startsignal selbständig. Und transferieren dann ganz normal wie sonst auch X Bytes einzeln hintereinander (es ist nur Platz für 1 Byte am 8 Bit Bus) über den Bus.

  • fritzeflink

    Hallo flinker fritz :)

    Ich kenn mich hier mit der forensoftware noch nicht so aus .... bei deinem obigen posting erscheint bei mir eine grafik unterhalb des zitats mit glocke und such-symbol in olivgrün und darunter weiß auf schwarzem hintergrund das Wort "Konversationen". Es sieht so aus als ob man darauf klicken könnte, aber man/ich kann nicht.

  • tofro


    Ein jedes Gerät in einem PC kann in einem Rutsch nur maximal soviele Bytes herausrücken/aufnehmen, wie breit der Bus ist an den es angeschlossen ist.

    Das stimmt so natürlich - wie soll es auch anders sein.


    Das heißt aber noch lange nicht, dass die CPU am Datentransfer beteiligt sein muss. Bei einem Floppy-Controller mit DMA-Fähigkeit sagt man dem Controller nur, wo er die Daten lassen soll und der Controller "schaufelt" ohne CPU-Beteiligung. das kann er i.A. auch schneller als die CPU.

  • tofro

    Ich schrieb das alles in einem ganzen Absatz zur DMA Behandlung wie das dabei zugeht; DMAing ist aber sowieso für meine Aufgabenstellung irrelevant.


    ich antwortete auf diesen Part

    halte es aber für extrem unwahrscheinlich, dass der Floppycontroller einzelne Bytes zum Abholen bereitstellt. An sich hat man schon sehr früh gesehen, dass das extrem unperformant ist.

    und habe damit erklärt, daß der FloppyController sehr wohl wie jedes andere Gerät auch, die Bytes immer nur einzeln rausrückt. Er kann nicht anders. Das ist der Punkt.


    Diese einzelnen Bytes wandern - wenn nicht DMA-Methode eingesetzt wird - IMMER in die CPU. Und dort kann der Programmierer entscheiden, was mit dem Byte geschieht.


    In der TP-Variante blockread() wird das byte aber in den RAM geladen. So ist nunmal die TP-Routine programmiert.


    Im RAM brauche ich es aber gar nicht, hat dort nichts verloren außer viel Zeit um dorthin zu kommen. Und muß von dort erst wieder über den Bus in die CPU geschoben werden, um dann von dort in den VGAport geschoben zu werden. Diese unnötige Hin-/Herschieberei kann und soll eingespart werden.


    Die Aufgabe besteht ja gerade darin, eine eigene Routine für das Einlesen der Bytes der Datei in die CPU zu schreiben. Das weiß ich nicht wie das genau geht, weil man ja die sonst verwendeten DOS/BIOS Routinen selbst schreiben muß. Man muß an die Hardware ran.


    In der von mir erwünschten Variante wird das Byte das vom Port des Diskcontroller kommt postwendend an den VGA-Port geschickt:


    Die Kernroutine in Assembler sieht (um Statusabfragen an den Ports DataREADY/BUSY/etc hier gekürzt) etwa so aus:


    mov cx, 4000

    doit:

    IN AL,PortDiskcontroller

    OUT VGAController, AL

    loop doit


    Noch schneller kann man kein Byte und auch keinen noch so großen "Block" an Bytes auf einem 8-Bit System von einem Port zum anderen schicken .


    Und "Block"s werden explizit durch die Schleife abgearbeit. Das ist unter "Block" / "In einem Rutsch" zu verstehen. Nie anders. Ob 8, 16, 32, ... Bitsystem wird ein "Block" in jenen Häppchen über den Bus geschoben, die der Bus in seiner Breite hergibt. Und der Schleifenzähler gibt die Anzahl der Happchen an.


    Der Teil der mir fehlt ist jener Programmcode bevor die Schleife beginnt, um den Diskcontroller dazu zu bringen, die Bytes zu übergeben.

  • Der Teil der mir fehlt ist jener Programmcode bevor die Schleife beginnt, um den Diskcontroller dazu zu bringen, die Bytes zu übergeben.

    Dann willst du das Datenblatt des Floppy-Controllers lesen. Da steht's drin, wie's geht.


    Du wirst aber enttäuscht über irgendwelche Geschwindigkeitsverbesserungen sein, denn das Schreiben und Lesen des Hauptspeichers, das du einsparen willst, braucht im Vergleich zum Floppy lesen nur einen Bruchteil der Zeit.

    Einmal editiert, zuletzt von tofro ()

  • Danke für den Tip, aber der DMV hat einen WD1002. Habe die Beschreibung gerade vor mir am Bildschirm :)

    Mal sehen ob ich mit meinen Amateurfähigkeiten das selbst hinkriege ...

  • Der WD1002 im DMV ist eine Variante, die den Flexcontroller beinhaltet.

    Aber um das Lesen von der Flex geht es mir vorerst gar nicht. Das FLexlaufwerk ist sowieo dermaßen dominierend langsam (Anlaufzeit, Positionierungszeit, Lesezeit), daß eine Beschleunigung hier zwar auch möglich wäre, meßbar, aber für den Benutzter kaum merkbar.

    Ich konzentriere mich erstmal auf die Harddisk.

  • Auf einem 8-bit Bus (8088/V20 System) kann nur 1 Byte / cyklus auf die Reise von A nach B geschickt werden, auf einem 16-bit System (8086/286) können gleichzeitig 16 Bit geschickt werden, usw. bis zum heutigen 64 Bit System. Diese Busverbreiterung ist ja einer der Hauptgründe, warum die Systeme immer schneller wurden.


    Vorsicht mit solchen Behauptungen.


    Das ist noch nicht alles ...



    Bin gerade noch am Reinlesen, aber Blockzugriff am Laufwerk sollte immer mit Blockgröße passieren, schneller wirds nicht. Daß man dann die Bytes alle einzeln abholen muß ist erstmal nicht anders machbar - alles andere (Einzelbyte) dürfte wesentlich langsamer werden, zumindest am Ende wo das Laufwerk ist. Was danach dann passiert, dafür kann ja das Laufwerk nichts.

    -- 1982 gab es keinen Raspberry Pi , aber Pi und Raspberries

  • Der Teil der mir fehlt ist jener Programmcode bevor die Schleife beginnt, um den Diskcontroller dazu zu bringen, die Bytes zu übergeben.


    Ich würde mir einmal die BIOS-Routinen von einem IBM XT (8088) ansehen. Diese gibt es als Assembler-Sourcelisting mit Kommentaren (inklusive Schaltpläne) in folgendem Handbuch (Original von IBM):


    IBM Personal Computer XT - Technisches Handbuch


    Dort kann man sehen, wie ein Disk-Controller (aber auch Floppy-Controller) angesprochen wird.


    Auch wenn die Hardware eine andere ist, kann man sich sicher davon einige Tipps holen.


    So ein Handbuch gibt es auch für den PC AT, der hat ja einen 16-Bit Bus.


    Noch ein Hinweis: bei deinen Betrachtungen betreffend Busbelastung gehst du immer nur von den Daten aus. Bei einer Befehlsschleife mit der CPU wird der Bus aber auch noch zusätzlich mit den Instruktionen (OP-Codes) belastet. Dies fällt bei einem DMA-Transfer praktisch weg. Da du aber mit den Daten noch etwas in der CPU tun möchtest, fällt die DMA sowieso raus.


    Gruß, PAW

  • PAW


    bei deinen Betrachtungen betreffend Busbelastung gehst du immer nur von den Daten aus. Bei einer Befehlsschleife mit der CPU wird der Bus aber auch noch zusätzlich mit den Instruktionen (OP-Codes) belastet. Dies fällt bei einem DMA-Transfer praktisch weg. Da du aber mit den Daten noch etwas in der CPU tun möchtest, fällt die DMA sowieso raus.

    richtig :),

    jedes byte braucht seine zeit; daß opcodes auch nichts anderes sind als Daten (für die Cpu) die im RAM stehen und von dort erstmal geholt werden müssen und sich den Bus mit den anderen daten teilen schrieb ich allerdings bereits:


    Ganz anders im ersten Fall. Hier werden 4000 + 4000 + 6x2000 = 20000 Bytes über den Bus geschickt, gemeinsam mit einem x-fachen Aufwand an Programmcodebytes und der Dauer der Ausführung.


    Deswegen wäre eine erfolgreiche Implementierung meiner Idee ja so interessant: wenig programmcode, schnelle cpu befehle, mimimalste busbelastung weil kein Byte unnötig bewegt wird, keinerlei Involvierung langsamer RAM-Zugriffe.


    Das Geheimnis liegt im sourcecode des Int 21H / Unterfunktion 3F bzw. der darunterliegenden Bios Routinen

    Sie ist eigentlich ein Doppelung der blockread() procedure von TurboP


    TP: blockread ( filehandle, buffer, anzahl (,recsread))

    Int21:

    AX: 3Fh
    BX: Handle
    CX: Anzahl der zu lesenden Bytes
    DX: Seg DS: Ofs des buffers


    Man sieht daß blockread() nur ein Wrapper ist, der optional als 4. Parameter die tats. gelesene anzahl zurückgibt.

  • Bin gerade dabei, den Ablauf beim IDE Bus zu untersuchen wegen Fehler eines IDE Controllers.


    Anbei mal die Aufzeichnung eines AT Rechners beim Hochstart ( nur die IDE relevanten Befehle rausgefiltert ) um von der IDE Platte MS-DOS zu starten ( Port 1F0 -1F7, 3F6 und 3F7 ).


    Ich habe die Bytes schon mal grob decodiert, damit man sie besser lesen kann.


    Also, es werden zuerst jede Menge Codes hin und hergeschickt, bis dann das Laufwerk verstanden hat, daß es einen bestimmten Sektor ( 256 words ) auslesen soll, der Rechner holt sich dann nacheinander die Daten ab.


    Die Tabelle zeigt: welches IDE Register, IDE Port, Portdata, alles zusammen und ob Write oder Read.


    Am Ende der Tabelle kann man den Start sehen, wie die eigentlichen Daten über Port 1F0 gelesen werden, nach 256 words wird dann der Code für den nächsten Sektor übertragen, danach dann die nächsten 256 words ausgelesen, usw.


    Bei IBM kompatiblen BIOS bieten sich zum Programmieren fürs Auslesen der Festplatte die INT 13H Funktionen an. Da sind die benötigten Codes alle definiert.



    Alt Status 3F6 04 W 3F6 04
    Alt Status 3F6 00 W 3F6 00
    Alt Status 3F6 08 W 3F6 08
    Status 1F7 01 R 1F7 01
    Status 1F7 90 R 1F7 90
    Command 1F7 90 W 1F7 90
    Error Reg 1F1 B4 R 1F1 B4
    Status 1F7 01 R 1F7 01
    Drive/Head 1F6 AF W 1F6 AF
    Status 1F7 40 R 1F7 40
    Alt Status 3F6 08 W 3F6 08
    Sector Count 1F2 3F W 1F2 3F
    Drive/Head 1F6 AF W 1F6 AF
    Precomp 1F1 C3 W 1F1 C3
    Status 1F7 91 R 1F7 91
    Command 1F7 91 W 1F7 91
    Status 1F7 11 R 1F7 11
    Status 1F7 00 R 1F7 00
    Drive/Head 1F6 A3 W 1F6 A3
    Status 1F7 40 R 1F7 40
    Alt Status 3F6 08 W 3F6 08
    Drive/Head 1F6 A3 W 1F6 A3
    Precomp 1F1 51 W 1F1 51
    Status 1F7 10 R 1F7 10
    Command 1F7 10 W 1F7 10
    Status 1F7 11 R 1F7 11
    Status 1F7 01 R 1F7 01
    Drive/Head 1F6 AF W 1F6 AF
    Status 1F7 40 R 1F7 40
    Alt Status 3F6 08 W 3F6 08
    Sector Count 1F2 01 W 1F2 01
    Sector # 1F3 3F W 1F3 3F
    Cyl. Low 1F4 FF W 1F4 FF
    Cyl. High 1F5 03 W 1F5 03
    Drive/Head 1F6 AF W 1F6 AF
    Precomp 1F1 51 W 1F1 51
    Status 1F7 40 R 1F7 40
    Command 1F7 40 W 1F7 40
    Status 1F7 11 R 1F7 11
    Status 1F7 01 R 1F7 01
    Drive/Head 1F6 A0 W 1F6 A0
    Status 1F7 40 R 1F7 40
    Alt Status 3F6 08 W 3F6 08
    Sector Count 1F2 01 W 1F2 01
    Sector # 1F3 01 W 1F3 01
    Cyl. Low 1F4 00 W 1F4 00
    Cyl. High 1F5 00 W 1F5 00
    Drive/Head 1F6 A0 W 1F6 A0
    Precomp 1F1 C3 W 1F1 C3
    Status 1F7 20 R 1F7 20
    Command 1F7 20 W 1F7 20
    Status 1F7 11 R 1F7 11
    DATA 1F0 FFE8 R 1F0 FFE8
    DATA 1F0 E990 R 1F0 E990
    DATA 1F0 097D R 1F0 097D
    DATA 1F0 3BFA R 1F0 3BFA
    DATA 1F0 8EC0 R 1F0 8EC0
    DATA 1F0 8ED0 R 1F0 8ED0
    DATA 1F0 8EC0 R 1F0 8EC0

    ... der Weg ist das Ziel

  • Ist es nicht evtl. einfacher, mal diese blockread Function zu nehmen, damit was zu schreiben, und dann mit einem Dissassembler das fertige Programm anzuschauen. Dort kopiert man sich dann die Teile die interessant sind und läßt einfach den Transfers ins RAM weg bzw biegt den direkt auf VRAM um.

    -- 1982 gab es keinen Raspberry Pi , aber Pi und Raspberries

  • Wie ich oben schrieb scheint blockread() nur ein wrapper für den int 21/3Fh zu sein. Die bei blockread() übergebenen Parameter werden einfach in die entsprechenden regs geschoben und dann int 21 aufgerufen. Im Prinzip weiß ich also mittlerweile, was blockread() macht. Aber bin damit dem Ziel nur einen Millimeter weiter.

    Die Interruptroutine ist es, die die Daten zurückschreibt in den der blockread()prozedur übergebenen puffer. da redet tp gar nicht mehr mit. Was es also braucht ist das Wissen, was der int21 dann eigentlich konkret macht, allso wie er es umsetzt, daß der wd-controller die gewünschten Daten schickt.


    Die Frage könnte auch lauten: Wie schreibe ich einen Festplattentreiber für Dos. Von diesem Wissen brauche ich nur einen ganz kleinen Teil.

    2 Mal editiert, zuletzt von DoPe ()

  • Zitat von DoPe

    Die Frage könnte auch lauten: Wie schreibe ich einen Festplattentreiber für Dos. Von diesem Wissen brauche ich nur einen ganz kleinen Teil.


    Da wird dir wohl nichts anderes übrig bleiben, als diverse Datenblätter durchzusehen, insbesondere das WD1002 Controller Board. Dort sind dann auch Hinweise auf diverse Komponenten wie WD1010 Disk Controller, sowie den WD1015 Buffer Manager Control Prozessor. Letzterer scheint mir für deine Zwecke nötig, da dieser die Daten zwischen der Controllerplatine und dem PC regelt.


    Interessant ist dieses alte Manual: Seite 49 - Disk Driver Example



    Ich wünsche gutes Gelingen!


    PAW

  • Macht der NCR das denn nicht mit DMA, wie der IBM im PC INT13h? Viel schneller geht es ja nicht: CPU setzt DMA Transfer zwischen Controller und Speicher auf und stößt dann den Transfer an. Das sind nur ein paar CPU Zyklen. Den Rest macht der DMA Controller.

    Irgendwelche einzelnen Bytes aus dem Disk Controller herauszujuckeln ist vermutlich deutlich langsamer.


    Wenn der Videospeicher "memory mapped" ist, könnte man wohl auch direkt per DMA von Disk in den Speicher schreiben lassen, ohne CPU Zutun, wenn es aber über eine einzelne Portadresse geht ist das wohl ein extremes Nadelöhr.

    Im PC ist das alles schon recht gut gemacht - das ist ja sicher auch eine der wichtigsten Baustellen für BIOS Entwickler. Am einfachsten mal in BIOS des NCR nachsehen, wie es dort abläuft.