Windows VxDs schreiben
Windows als moderne Programmierumgebung erscheint durch seine
Multitaskingfähigkeit geeignet, komplexe Aufgaben zu lösen. Häufig erweist es
sich als notwendig, externe Hardware anzusprechen. Die Möglichkeit, über
DDE Daten zwischen Applikationen auszutauschen, macht es attraktiv, für jede
Hardware einen speziellen Treiber zu schreiben und die Datenverarbeitung von
Standardsoftware (Tabellenkalkulation, Datenbank, CAD-Programm)
durchführen zu lassen. Nahezu jede Standardsoftware unterstützt die DDE-
Konversation.
Externe Hardware anzusteuern, die an einer Standardschnittstelle (COMx,
LPTx) angeschlossen ist, ist problemlos über die COMM-Funktionen von
Windows möglich. Auch das Ansteuern von Hardware auf einer Steckkarte mit
Ein-Ausgabeadressen (I/O-Ports) ist zumindest unter Windows 3.x problemlos,
solange die Zugriffe nicht zeitkritisch sind. Der gängige Weg läuft über eine
DLL, um zu garantieren, daß nur ein Programm auf das I/O-Port zugreifen
kann. Eine DLL wird stets nur einmal geladen. Alternativ kann man das
Anwendungsprogramm so schreiben, daß es sich nur in einer Instanz starten
läßt.
Der nächte Schritt beim Steigern des Schwierigkeitsgrades ist die Verarbeitung
von Interrupts. Die Programmierung kann dank DPMI und dem Virtuellen
Gerätetreiber VPICD genauso wie unter DOS durch "Direktzugriff"
(Anführungsstriche wegen... siehe unten!) auf den Interruptcontroller und durch
Setzen des Interruptvektors über die gewohnte DOS-Schnittstelle erfolgen.
Des weiteren gibt es Geräte mit Speicher-Ein/Ausgabe (memory-mapped I/O).
Da der Speicherbereich generell auf festen Adressen liegt und unter Windows
Standard-Aliasdeskriptoren zur Verfügung stehen, gestaltet sich der Zugriff
weitestgehend einfach.
Es gibt jedoch eine ganze Reihe Probleme, die unter Windows nicht oder nur
umständlich durch .EXE- und .DLL-Programme gelöst werden können. Hier
setzt der Bereich der VxD's ein.
VxD's sind Virtuelle Gerätetreiber, die beim Laden Teil des Windows386-Kernel
werden und mit Windows/386 erstmalig eingeführt wurden. Die Namensgebung
der Abkürzung VxD ergibt sich aus "Virtual irgendein Device", wobei das "x" für
"irgendein" (something) steht. Entweder sind sie Teil der Datei WIN386.EXE,
oder sie werden als Einzeldateien mit der typischen Endung .386 zu
verschiedenen Anwendungen mitgeliefert. Unter Windows95 wurde die Endung
auf .VXD umgestellt. Damit werden speziell für Windows95 gefertigte Virtuelle
Gerätetreiber kenntlich gemacht, die einen erweiterten Funktionsumfang
besitzen.
Beachte: VxD's sind nur unter Windows/386 (Windows Version 2.x), Windows
Enhanced Mode (Windows 3.x) und Windows95 aktiv. Sie sind nicht aktiv
(werden nicht geladen) beim Betrieb von Windows im Real- oder Standard-
Mode sowie bei WinOS2 (Windows unter OS/2). Windows NT verwendet eine
gänzlich andere Kernelstruktur und kann mit VxD's nichts anfangen. Hier
werden sog. Kernel-Mode-Treiber eingesetzt.
Die Einbindung von Virtuellen Gerätetreibern erfolgt durch Eintrag in die
SYSTEM.INI unter der Sektion [386Enh] mit der Zeile "device=avxd.386". Die
Reihenfolge spielt - im Gegensatz z.B. zur CONFIG.SYS - keine Rolle, da die
VxD's eine Lade-Reihenfolge-Nummer besitzen, an der sich der VxD-Lader des
Kernels orientiert. Auch die Groß/Kleinschreibung ist egal. Befindet sich der
Gerätetreiber nicht im Standard-Suchpfad von Windows (das Verzeichnis, das
die Datei WIN.COM, und das, das die Datei WIN386.EXE enthält), muß ein
Pfad angegeben werden. In der Datei WIN386.EXE enthaltene VxD's werden
mit einem Sternchen ("*") vor dem Gerätetreibernamen kenntlich gemacht. Eine
andere - weniger offensichtliche - Art der Einbindung Virtueller Gerätetreiber
erfolgt beim Start von Windows durch geladene residente DOS-Programme.
Unter Windows95 können VxD's in einer System-Registry (??) eingetragen
werden.
Das Schreiben von VxD's erfolgt generell in Assemblersprache. Neuerdings
setzt sich auch C mehr und mehr durch, um auch komplexere Funktionen (z.B.
Berechnungen) innerhalb des VxD's ohne großen Aufwand durchführen zu
können. Erforderlich ist dazu das DDK (Device Driver Kit, "Gerätetreiber-
Werkzeugsatz") von Microsoft, welches über das MSDN (Microsoft Developer
Network) Level 2 für z.Z. (4/96) 845,25 DM pro Jahr inkl. Umsatzsteuer
erhältlich ist. (Ziemlich viel Geld zockt MS von den Leuten ab, die Windows
populärer machen!) Alternativ läßt sich DDK-Lite aus dem Buch [Thie] in
Verbindung mit einem Microsoft Assembler (MASM) einsetzen. Des weiteren
kann VTOOLSD von Vireo Software zur VxD-Entwicklung herangezogen
werden. Von den o.g. Möglichkeiten stand mir nur das DDK-Lite zur Verfügung,
welches ich in Ermangelung eines MASM auf den vorhandenen TASM (Turbo
Assembler 3.2 von Borland, bei Borland Pascal 7.0 dabei) umgebaut habe.
Das Debuggen kann mittels der Debug-Version des Windows-Kernels und dem
WDEBUG.EXE erfolgen, die auch dem DDK-Lite beiliegen. Erforderlich ist
hierzu ein Terminal, das an eine serielle Schnittstelle des Rechners angesteckt
wird. Alternativ kann auch ein zweiter (Monochrom-) Monitor am gleichen
Rechner eingesetzt werden. Besonders komfortabel jedoch ist das Debuggen
des VxD's mit SoftICE für Windows von NuMega. Allerdings kann dieser mit
von TASM generierten Debuginformationen nichts anfangen, so daß kein
Source-Level-Debugging möglich war. SoftICE erlaubt eine freizügige
Tastenbelegung; die Konfiguration habe ich auf eine turbo-debug-ähnliche
Belegung umgestellt. Original ist sie an CodeView, dem Debugger von
Microsoft, angelehnt.
Was schließlich für die Entwicklung der VxD's gebraucht wird, ist TASM.EXE,
LINK386.EXE, ADDHDR.EXE und diverse Include-Dateien, allen voran die
VMM.INC. Nützlich sind noch Beispieldateien, die Hilfedatei sowie ein
Debugger.
Wann ist ein VxD nötig, und was kann man alles machen?
Weiter oben beschrieb ich typische Einsatzfälle "klassischer" Windows-
Treiber-Programmierung. VxD's sind vorzuziehen, wenn auch noch DMA
(Direkter Speicherzugriff) ins Spiel kommt oder höherfrequente Interrupts
(10..1000Hz) zu verarbeiten sind. Ist Kompatibilität zum Standard- oder gar
Real-Modus wichtig, können diese Problembereiche auch in DLL's gelöst
werden. VxD's sind jedoch unumgänglich oder zumindestens wärmstens
empfohlen in folgenden Fällen:
* wenn die volle Kontrolle über den Prozessor erforderlich ist, um z.B.
Echtzeitverhalten im Mikrosekunden-Bereich zu erhalten
* wenn die Interruptfrequenz 1kHz übersteigt
* wenn Hardware zu emulieren ist
* wenn Gerätekonkurrenz zu behandeln ist
* wenn Hardware vor ungewollten und gefährlichen Fremdzugriffen zu
schützen ist, um das System stabiler zu machen
* wenn man alles aus dem Prozessor herauskitzeln will (für "Sportler")
Anhand dieser Punkte kann man erahnen, wie mächtig VxD's in das
Gesamtsystem eingreifen können. Genauso fatal kann sich aber auch der
kleinste Bug (Fehler) auf das Gesamtsystem auswirken, denn es gibt keinerlei
Schutzmechanismen innerhalb des Kernels und der darin eingebundenen
VxD's.
Speicherbelegung im linearen Adreßraum unter Windows
VxD's werden beim Laden mit dem Kern verbunden. Der Kern selbst arbeitet im
FLAT-Speichermodell. Das entspricht in etwa dem guten alten TINY-Modell,
jedoch mit 32-bit-Offsets. Die Segmentregister sind fest und haben die
Basisadresse 0 und ein Limit von FFFFFFFFh. (Wem die Begriffe fremd
vorkommen, sollte dringend ein gutes Buch zur 386-Programmierung
durchackern!) Es gilt DS=ES=SS<>CS. Das Codesegment-Register zeigt auf
ein Codesegment-Deskriptor (20h), die anderen Register auf ein Datensegment-
Deskriptor (28h). Der Kernel und die VxD's kommen auf Adressen >80000000h
zu liegen. Die Paging-Einheit ist generell aktiviert (Bit31 in CR0 =1), um diese
Adreßlage zu garantieren. Alle Tasks (sog. VM's, Virtuelle Maschinen) liegen
dahinter (!). Somit ergibt sich ein Limit von knapp 2 Gigabyte für die Summe
aller Anwendungen (inklusive Swap). Eine Task ist hierbei das ganze Windows
an sich (die sog. System-VM) sowie jede einzelne DOS-Box. Unter
Windows95 ist außerdem jede Win32-Anwendung und vermutlich auch jeder
Thread noch eine Task. (Da die Threads einer VM den Speicher gemeinsam
benutzen, gehen sie in den Gesamtspeicherverbrauch nach obiger Rechnung
nicht ein.) Die jeweils aktive VM wird zusätzlich in den Bereich 0..7FFFFFFFh
gemappt. Dies ist erforderlich, da nur so mehrere V86-Tasks realisiert werden
können. Auch die System-VM hat einen Real-Mode-Rumpf, der im
wesentlichen aus dem Ergebnis des System-Schnappschusses besteht - dazu
später. Die Umschaltung erfolgt durch das Umladen des Registers CR3 auf
eine andere Page-Tabelle bei der Taskumschaltung des Prozessors (CR3 ist
im TSS enthalten).
Start des Systems (bzw. dynamisches Laden des VxD's unter Windows95):
Der VxD-Lader orientiert sich an der einzigen exportierten Struktur (der Name
lautet avxd_DDB, wobei avxd für den Namen des VxD's steht). In dieser
Struktur (in Pascal als "record" bekannt), in der z.B. einige Adressen
aufgelistet sind. Die wichtigste Adresse ist der Eintrittspunkt für die Steuer-
Prozedur. Sie wird bei jedem globalen Ereignis - z.T. mehrfach - gerufen,
beispielsweise beim Starten und Beenden von Windows sowie beim Starten
und Beenden einer DOS-Box. (Sehr wahrscheinlich unter Windows95 auch
beim Starten einer Win32-Anwendung.) Bei jedem Ruf übergibt der Kernel im
Register EAX einen Code über das momentane Ereignis. Bei einfachen "Trivial-
VxD's" genügt es häufig, nur den Start von Windows abzufangen, um z.B.
einen Interrupt-Handler zu installieren.
Der Aufruf von Systemfunktion der VMM (Virtual Machine Manager) oder
anderer VxD's (die das System erweitern) erfolgt durch einen seltsamen wie
genialen Mechanismus: Da der direkte Aufruf mit CALL nur über
Relokationstabellen möglich wäre (wie bei Windows-.EXE und .DLL realisiert),
wird hier ein Dyna-Link-Interrupt verwendet (ein simples INT 20h), dem als 2
WORDs die ID (Identifier, eineindeutiger numerischer Bezeichner) m des Ziel-
VxD's und die Nummer n der VxD-Funktion folgen. (Das erinnert mich stark an
KC-Zeiten!) Das sind insgesamt 6 Bytes. Der Interrupthandler für den INT20
wertet nun die nachfolgenden 2 WORDs aus und sucht daraufhin die wirkliche
Adresse der Routine (bzw. spuckt eine Fehlermeldung aus). Nun wäre das
nicht sonderlich genial, weil langsam. Der Int20-Handler ändert nunmehr diese
6 Bytes zu einem indirekten Aufruf um. Er ist fast so schnell wie der direkte
Aufruf, ist ebenfalls genau 6 Bytes lang, und hat gegenüber dem direkten Aufruf
den Vorteil, daß weiterhin ein Einhängen (Hook) in den Aufruf und damit die
Änderung der Funktionalität der aufgerufenen Funktion durch andere VxD's
jederzeit möglich ist. Die Adresse hinter dem Opcode zeigt auf den nten
Eintrag der Tabelle der Funktions-Einsprungadressen des VxD's m. Durch
diese Rück-Änderung wird das VxD nach und nach immer schneller.
Außerdem kann man beim Debuggen sehr leicht sehen, welche Code-
Abschnitte noch nicht abgearbeitet wurden.
Bleibt noch die Frage nach der Eineindeutigkeit des ID's m. Tatsächlich werden
sämtliche VxD-ID's zentral bei Microsoft verwaltet und vergeben. Die
Anforderung einer solchen ID gestaltet sich sehr einfach per email im Internet
an vxdid@microsoft.com. Die ID kostet nichts, und man muß (z.Z.) auch nicht
Mitglied im MSDN sein. Nur das Formular (s. Anhang) ist auszufüllen und in
ein bis zwei Wochen hat man die ID('s). Falls man keine Funktionen zu
anderen VxD's hin exportieren möchte, ist die ID unnötig (aber: siehe unten!),
und man kann die Konstante "Undefined_Device_ID" verwenden. Unter
Windows95 gibt es parallel noch einen weiteren Mechanismus zur
"dynamischen" Zuteilung einer ID - wahrscheinlich nur für VxD's mit
Windows95-Erweiterungen.
Ein Zwischenwort zum System-Schnappschuß (system snapshot):
Es ist sicher schon aufgefallen, daß sämtliche TSR-Programme in jeder DOS-
Box zu sehen sind, die vor dem Starten von Windows bereits im Speicher
waren. Lädt man jedoch ein weiteres TSR-Programm in einer von 2 DOS-
Boxen, ist das TSR für die andere Box vollkommen unsichtbar. Das TSR
verschwindet zudem automatisch (jedoch nicht immer 100%ig rückwirkungsfrei
für das System!), wenn die DOS-Box - gewaltsam oder nicht - geschlossen
wird. Was weniger bekannt ist, ist die Tatsache, auch Windows selbst nur eine
(salopp gesagt) DOS-Box ist, in der permanent ein Programm mit DPMI-DOS-
Extender läuft, in der genauso alle vorher geladenen TSR's sichtbar sind. Die
Verhältnisse unter Windows und Turbo Pascal mit DPMI-Zielplattform sind
daher sehr ähnlich.
Verantwortlich für dieses Verhalten ist der System-Schnappschuß, ein
fundamentales Ereignis beim Start von Windows. Der System-Schnappschuß
erfolgt, nachdem sämtliche VxD's ihre Initialisierungsprozedur hinter sich
haben. Der momentane Stand der DOS-Speicherbelegung sowie wichtiger I/O-
Bausteine wie Interrupt- und DMA-Controller wird festgehalten als Grundlage für
die Geburt aller VM's. Danach wird die System-VM im DOS-Modus (V86-Mode)
hochgefahren. COMMAND.COM wird gestartet, und diese führt Datei
WINSTART.BAT (falls vorhanden) aus. TSR-Programme, die hier geladen
werden, sind in anderen DOS-Boxen nicht sichtbar. Das schafft in den DOS-
Boxen mehr Platz, falls ein TSR nur für Windows selbst gebraucht wird.
Danach startet das eigentliche Windows (???.EXE?)
Nach dieser Einleitung kommen wir nun zu einem denkbar einfachen VxD: Es
tut schlichtweg nichts. Declare_Virtual_Device ist ein großes Makro, welches
unter anderem die nachfolgenden angegebenen Parameter in die exportierte
Struktur avxd_DDB einträgt. Desweiteren existiert der Funktionsverteiler bei
avxd_Control. Mit Control_Dispatch wird - je nach Grund des Aufrufs mit Code
in EAX die zugehörige Routine gerufen oder nach dem Makro Control_Dispatch
fortgesetzt. Letztlich expandiert das Makro schlichtweg zu
cmp eax,code
jz label
Control_Dispatch ist aber auch in der Lage, bei vielen (fünf und mehr)
Verzweigungen effizientere Sprungtabellen zu generieren. Um diese Art der
Code-Generierung zu aktivieren, muß der Nachrichtenverteiler in
Begin_Control_Dispatch und End_Control_Dispatch eingerahmt werden. In
diesem Fall hat der Startpunkt der Prozedur den Namen avxd_Control, der bei
Declare_Virtual_Device anzugeben ist. Im DDK werden beide Varianten
verwendet. Diese beiden fundamentalen Teile finden sich sogar bei der
Windows95-Programmierung in Assembler wieder, da diese beiden Makros
nicht in C-Compiler übernommen wurden. Unser Beispiel verzweigt bei Sys_Init
zu einer Routine, die CY auf Null setzt (so wie der Funktionsverteiler auch): für
den Kernel die Nachricht: Alles okay, VxD bleibt im Speicher.
Jetzt ist es an der Zeit, sich die Beispiele aus dem DDK anzuschauen.
Zusammen mit der Hilfe können die Wirkungen der einzelnen Befehle und
Makros erkundet werden.
Doch noch viel im Unklaren geblieben? Macht nichts! Aus den
Kurzbeschreibungen (*.TXT) geht in etwa hervor, was man mit VxD's so alles
für Zaubertricks machen kann. Noch neugierig?
Ich denke, das Beispiel HDSLEEP von mir sollte halbwegs verständlich
erscheinen. Dieses kleine Programm "erweitert" ja nur den 32-bit-Zugriff um
eine Schlaffunktion. Vorausgesetzt, der 32-bit-Zugriff ist in der
Systemsteuerung aktiviert. Ist gar keine Möglichkeit vorhanden (die Checkbox
fehlt), hat Windows ein Problem mit der Festplatte entdeckt, und schaltet stur
den 32-bit-Zugriff (mehr über diese Funktion mit diesem eigentlich irreführenden
Namen später) aus. Bei mir war beispielsweise ein unentdeckter
Bootsektorvirus die Ursache...
Einige grundlegende trockene Erläuterungen müssen erst noch kommen, ehe
wir zu praktischen Beispielen kommen, die mehr machen, als nur am Kernel
herumzuspielen. Insbesondere ist eine Kommunikation sowohl mit der
Hardware als auch mit einer Applikation von Interesse. Genaugenommen
beinhaltet HDSLEEP einen Hardwarezugriff - die paar OUT-Befehle nämlich.
Versuchen Sie dieselben Befehle in einer DOS-Box auszuführen: Windows
(genauer: das Standard-VxD WDCRTL) rächt sich mit weißer Schrift auf
blauem Grund, daß ein unzulässiger Zugriff auf die Ports des
Festplattencontrollers durchgeführt werden sollte. Mit dem Befehl "TSS" unter
SoftICE kann man die Ursache sehen oder zumindest erahnen: WDCRTL
besitzt die Ports... Und WDCRTL kann die Ports besitzen, solange es will, und
wie eine Glucke darauf achten, daß niemand sie sehen (lesen) oder gar
berühren (schreiben) kann - das HDSLEEP greift trotzdem zu! VxD's haben
immer freien Zugang zu allem, was man will. Es gibt keine privilegsbedingten
Fehler zu bedenken. Man kann beispielsweise, wann immer es einem einfällt,
in Real Mode schalten. Wozu auch immer...
Ausnahmen können dennoch auftreten: die Allgemeine Schutzverletzung (#GP,
Int0D), beispielsweise beim Schreiben ins Codesegment oder beim Aufruf
eines Interrupts >=60h - führt zur sofortigen Beendigung von Windows(!) mit
dem Kommentar "Windows protection error", sowie Page-Faults (#PF, Int0E),
die vom VMM zusammen mit PAGEFILE und PAGESWAP behandelt werden.
Vielleicht stellt sich die Frage: Ist es nicht gefährlich, Portzugriffe aufs
geradewohl auszuführen? Könnte nicht zufällig WDCRTL unterbrochen und
durcheinandergebracht werden? Damit kommen wir in den nächsten Teil:
Keinesfalls! Der Kernel ist ein kooperatives, single-threaded System. Es kann
gar nicht zu solchen Unterbrechungen kommen (Ausnahmen: Asynchrone
Funktionen und zugehörige Dienste). Salopp gesagt: hier finden wir das gute
alte DOS wieder. Aber das hat keineswegs damit zu tun, daß Windows auf
DOS aufsetzt - sämtliche modernen Betriebssysteme (OS/2, Windows NT,
Unix) verwenden dieses Prinzip im Kernel. Wenn gerade Code im Kernel
ausgeführt wird, "schlafen" sämtliche Prozesse - im Windows-Jargon sind das
die VM's (und bei '95 kommen wohl noch die Threads hinzu). Es gibt jedoch
eine "aktive VM". Eine Taskumschaltung (VM-Umschaltung müßte es ja
heißen) erfolgt nur bei Ein- oder Austritt aus dem Kernel. Innerhalb bleibt die
VM immer gleich. Für viele Callbacks wird ein Zeiger auf eine VM-Struktur in
EBX mitgegeben; bei Bedarf kann man sich jederzeit dieses EBX über die
VMM-Funktion Get_Cur_VM_Handle besorgen.
Sicher bekannt ist, daß Windows sowohl präemptives als auch kooperatives
Multitasking unterstützt. Leider kann man als Programmierer schlecht
aussuchen, was man gerne hätte, denn die Zuordnungen liegen fest: 16-bit-
Windows-Anwendungen untereinander werden kooperativ umgeschaltet; die
Umschaltung jedoch zwischen dem 16-bit-Windows zu den 32-bit-Windows-
Anwendungen und den DOS-Boxen erfolgt präemptiv. Für den Benutzer
offensichtlich erfolgt die Taskumschaltung in regelmäßigen Abständen (der
"Zeitscheibe") - diese Umschaltung ist aber für den VxD-Programmierer recht
unwichtig. Weniger bekannt, aber von immenser Wichtigkeit ist, daß VM's mit
Prioritäten versehen werden. (Damit ist NICHT die Priorität gemeint, die im PIF-
Editor eingestellt werden kann!) Normale VM's bekommen die relativ niedrige
Priorität Cur_Run_VM_Boost verpaßt. Existiert eine VM mit höherer Priorität,
wird diese solange abgearbeitet, bis dieser Zustand aufhört (eine andere
Priorität aufrückt oder dieselbe rückvermindert wird). Erst wenn mehrere
gleichprivilegierte VM's zusammenstoßen, tritt das Zeitscheiben-Multitasking
in Aktion. Genausogut kann man VM's mit zu niedrigen Prioritäten (und sei es
nur die Differenz von 1!) gänzlich einschläfern. Und wozu ist das gut?
Programmierer, die unter Windows NT oder Unix Erfahrungen gesammelt
haben, könnten erahnen, worum es geht: Wenn ein Programm auf eine
Tasteneingabe wartet, verbraucht es keinerlei Rechenleistung. Das liegt daran,
daß genau jene Priorität zu klein ist - der Prozeß steht vollkommen. Erst ein
Tastendruck füllt das Standardeingabegerät des Prozesses mit einem Zeichen
- und diese spezielle Datei hat auch noch die im Kernel fest eingebaute
Eigenschaft, den Prozeß wieder aufzumuntern - und weiter geht's!
Auch für die Erhöhung der Priorität gibt es ein nettes Beispiel: den Signal-
Handler. Unix-Programme können einen Signal-Handler installieren, mit der
beispielsweise ^C (SIGTERM, man möge die unscharfen Begriffe verzeihen!)
abgefangen werden (z.B. jedes Terminal-Programm tut das und legt die
magische Tastenkombination oft auf ^] ). Schickt ein Prozeß nun zu einem
anderen Prozeß ein Signal, muß das eine zwangsweise Taskumschaltung zur
Folge haben, da erst mal der Signal-Handler abgearbeitet werden muß. Selbst
wenn der Ziel-Prozess gerade wartend auf Tasteneingabe steht. Dazu wird
einfach die Ausführungspriorität über alle anderen gehoben - und voilà - die
Task wird umgeschaltet! Nebenbei bemerkt ist das Signal SIGKILL, was bei
"kill -9" ausgesendet wird, ein Sonderfall: es entfernt den Prozeß, ohne den
sterbenden Prozeß zu informieren. Daß "kill" ohne "-9" auch (manchmal)
Prozesse killt, liegt am Standard-Signal-Handler, der - wenn nicht
überschrieben - das laufende Programm umgehend beendet.
Nun ist ein Unix-System im Vergleich zu Windows konzeptionell sehr viel
einfacher - es gibt ja dort auch keine hartnäckigen DOS-Programme, die laufen
sollten. Es ist also zu erwarten, daß Windows beileibe komplizierter ist. Zum
Glück werden viele Dienste von VMM und Standard-VxD's erledigt, ansonsten
würde wohl alles ziemlich ausufern.
Allerdings bleibt die Frage, wozu diese Prioritäten unter Windows Sinn machen
sollen. Wer will schon eine schlafende DOS-Anwendung? Und doch gibt es
sie: Wenn in der .PIF-Datei "Leerlaufzeit entdecken" angeschaltet wurde, wird
beim Fehlen irgendwelcher Ereignisse die DOS-Box gänzlich eingeschläfert.
Sehr augenscheinlich, wenn der Bildschirmschoner des Norton Commanders
im Fenster stehenbleibt.
Kommen wir aber mal zu einem Szenario, was wohl laufend vorkommt und
über das den Kopf zu zerbrechen lohnt. Angenommen, wir haben einen
Rechner mit einer Soundkarte. Alle Soundkarten haben I/O-Ports, einen (oder
mehrere) Interrupt(s) und - als ob das nicht schon kompliziert genug wäre -
mindestens einen DMA-Kanal. Wir starten ein Spiel in einer DOS-Box,
welches Soundausgabe unternimmt, und es sollte funktionieren. (Zumindest im
Vollbild:-) Hier sollten die Frage eigentlich lauten: Wie kann das überhaupt
gleichzeitig funktionieren? Was passiert, wenn die Soundkarte ein Interrupt
anmeldet? Zunächst gelangt dieser in den Kernel, zum VPICD. Jeder Interrupt,
auch ein Software-Interrupt, gelangt in den Kernel.) Dieser ruft dann den
Interrupt-Handler auf. Welchen? Den von der DOS-Box oder von der (immer
noch laufenden) System-VM? Oder sogar von der gerade aktiven VM? (So
nach einer Art Russisch Roulette:-)
Wie einzusehen, wären die letzten beiden Entscheidungen fatal. Das System
würde sofort abstürzen.
Der Trick: VPICD führt Buch, welche VM den betreffenden Kanal des Interrupt-
Controllers freigegeben hat. In diese VM wird dann der Interrupt
"hineingeblasen" ("virtualize" im Fachenglisch). Dazu wird die
Ausführungspriorität der Ziel-VM über alle hinweg angehoben, und beim Aufruf
der Interrupt-Prozedur - bei der der Kernel verlassen wird - wird die aktive VM
gegebenfalls umgeschaltet. Sonderfälle wie ein gesperrter Interrupt in der Ziel-
VM werden ebenfalls beachtet. Damit die Buchführung auch dann funktioniert,
wenn DOS-Programme direkt auf die Ports des Interruptcontrollers zugreifen
(wie sollten sie es sonst tun?), müssen die Ports von VPICD geschützt sein.
Der Portzugriff zum Interruptcontroller ist somit "unecht" geworden. VPICD
sorgt dafür, daß sich die Ports noch genauso verhalten wie unter purem DOS.
Bleibt noch die Frage, was VPICD tut, wenn ein Interrupt auftaucht, der zu
einem Kanal gehört, der bereits vor dem Start von Windows freigegeben war.
Hier wird tatsächlich das o.g. Russisch-Roulette-Verfahren eingesetzt, d.h. die
aktive VM bekommt den Interrupt zu sehen. Da alle VM's die gleichen TSR-
Bereiche aufweisen, sollte sich dadurch kein Absturz ergeben. Problematisch
wird die Sache aber, wenn diese TSR's wiederum Callbacks zu
Anwenderprogrammen aufrufen. Beispielsweise können in 2 DOS-Boxen
Programme mit Mausunterstützung laufen. Der Maustreiber informiert bei
Änderung der Position das Anwenderprogramm per Callback. Ja welches? Am
besten alle beide? (An dieser Stelle bin ich überfragt, ob nun der Maustreiber
das können muß oder sich ein VxD, namentlich VMD darum kümmert. Zudem
kommt noch die Fensterüberdeckung ins Spiel.)
Keine Regel ohne Ausnahme: der Tastatur-Interrupt. Er wird immer in die
fokussierte VM hineingeblasen. Das ist im Unterschied zur aktiven VM die, zu
der die Tasteneingaben gelangen, also das Fenster mit der blauen Titelzeile.
-Gliederung-
Einführung
Projekte
Software für Ultraschall-Wassermeßplatz
Treiber zur Ansteuerung des Ultraschallwandlers USM10
Treiber zur Ansteuerung des Schrittmotors
Hardware-Voraussetzung: die isel-Mikroschrittkarte mit Motor
Windows-Oberfläche SMS10
Kernel-Treiber MPK3D.386
Software für Ultraschall-Luftmeßplatz
Ansteuerprogramm für Ultraschallsender und Motor
Der Schrittmotortreiber
* Basis: Die Schrittmotorkarte
Die isel-Microstep-Steuerkarte besitzt alle Komponenten zum Betreiben von
drei 2-Strang-Schrittmotoren im Mikroschrittbetrieb inklusive
Leistungsendstufen 2A sowie je 2 opto-entkoppelte Endschalter-Eingänge.
Zusätzlich ist ein universeller Niederspannungs-Relaisausgang vorhanden.
Gedacht ist diese Karte zur Ansteuerung einer NC-Maschine mit 3 linearen
Achsen (3 Freiheitsgrade) und 1 rotierenden Werkzeug (Bohrer oder Fräse), die
ebenfalls von isel angeboten wird.
Der Mikroschrittbetrieb, in der Literatur [Fischer] auch als Minischrittbetrieb
bezeichnet, zeichnet sich durch viele Vorteile in mechanischer Hinsicht aus:
* Vermeiden ruckartiger Bewegungen bei kleinen Geschwindigkeiten, wie sie
insbesondere bei synchroner Bewegung mehrerer Schrittmotoren bei
kleinen Steigungen auftreten
* Verringerung der Geräuschentwicklung
* Verringerung von Resonanzerscheinungen
* Sanfterer Anlauf und Stop
Erkauft werden diese Vorteile durch erhöhten elektronischen Ansteueraufwand.
So sind für jeden Motor 2 Digital-Analog-Umsetzer vonnöten. Auf der isel-
MPK3-Karte wurde die Konstantstrom-Impulsansteuerung eingesetzt, die einen
optimalen energetischen Wirkungsgrad erreicht, jedoch bezüglich
elektromagnetischer Verträglichkeit an einem Meßplatz unangenehme
Störungen verbreitet. Die Pulsfrequenz ist kartenintern auf ca. 20kHz
festgelegt.
Etwaiger interner Aufbau der isel-Mikroschritt-Steuerkarte
Bisherige und geplante Verfahrensweise
Angedacht war eine Softwarelösung, die die Meßdaten zu MatLab transferiert...
Unter DOS benötigt man daher ein Programm, das alle drei Komponenten des
Meßplatzes (Positionierung des Motors, Aussenden von Impulsen, Aufnahme
von Meßwerten) vereinigt und die Meßwerte in eine von MatLab lesbare Datei
schreibt. Die mächtige Skriptsprache von MatLab und das DDE von Windows
konnten nicht genutzt werden. Daher wurde versucht, die Meßwertaufnahme
usw. unter Windows zu erledigen...
Aufbau und Wirkungsweise des Treibers MPK3D
* Ausgangspunkt: Treiber unter DOS
Vorlage für diesen Treiber war die Software MPK3DRV.EXE, die von der Firma
isel zur Karte mitgeliefert wurde. Es ist ein reines DOS-Programm und als TSR
(Terminate and Stay Resident) programmiert. Es belegt ca. 50 KB im
Hauptspeicher und verbraucht soviel Rechenleistung, daß mindestens ein
386/40MHz erforderlich ist, um den Treiber absturzfrei zu installieren [isel]. Der
Treiber kapselt die komplizierte Ansteuerung der 3 Schrittmotoren für Geraden-,
Kreis- und Helixbewegungen und stellt seine Funktionen über einen dedizierten
Softwareinterrupt, namentlich Int 78h, zur Verfügung. Für die Benutzung am
Meßplatz ist nur die Bewegung einer Achse nötig.
Offenbar wurde dieser Treiber fest für die oben erwähnte Werkzeugmaschine
konzipiert. Da der Treiber seine normale Arbeit erst dann aufnimmt, wenn alle
drei Achsen eine Referenzfahrt zum Endlagenschalter durchgeführt haben,
mußte eine Emulationsschaltung entwickelt werden, die in zeitlich richtiger
Reihenfolge entsprechende Signale generiert. Alternativ hätte man den Treiber
patchen können - die Stellen zu finden ist jedoch bei einer 50KB großen, in
Hochsprache geschriebenen Datei jedoch sehr kompliziert. Aufgrund des in der
Datei enthaltenen Vermerks über den Compiler muß es sich um Microsoft C
handeln.
* Problem: die Benutzung unter Windows
Ausschlaggebend für die Entscheidung, einen gänzlich neuen Treiber zu
schreiben, waren jedoch nicht die o.g. Probleme. Hauptproblem ist, daß ein
Start von Windows unmöglich ist, solange MPK3DRV.EXE geladen ist.
Aufgrund einer Fehlimplementierung des Softwareinterrupts Int 2Fh kommt es
beim Versuch, Windows zu starten, zum Absturz. Derselbe Fehler führt auch
zu Problemen bei Benutzung von Netzsoftware, die diese Schnittstelle
benutzen, wie Novell Lite, Lantastic und Personal Netware (aber nicht Novell
Netware), sowie mit dem CD-ROM-Extender MSCDEX.
Um dieses Problem zu umgehen, wurde bisher der Treiber in einer DOS-Box
betrieben. Um eine flüssige Verarbeitung zu realisieren, mußte in der .PIF-
Datei eine hohe Hintergrundpriorität eingestellt werden. Außerdem mußte der
Schalter "Leerlaufzeit entdecken" abgeschaltet werden. Trotzdem war das
Ergebnis ein eher stotternder Motor, dessen Bewegungs-Unregelmäßigkeiten
zunahmen, wenn Windows-Applikationen arbeiteten, sowie ein sehr zäh
arbeitendes Windows.
Damit entstand aber ein neues Problem: Wie soll eine Windows-Anwendung
mit dem Treiber in der DOS-Box kommunizieren? Aufgrund der Trennung in
zwei Virtuelle Maschinen (VM's) mit gegenseitig abgeschirmten Adreßräumen
führt ein Software-Interruptaufruf seitens des Windows-Programms nicht zum
Ziel. Erforderlich wurde ein Stück Software, das in beiden VM's sichtbar ist und
dazu dient, Anforderungen durchzureichen, um wenigstens eine
Synchronisation zu erreichen. Da ein vor Windows geladenes TSR-Programm
diese Forderung erfüllen kann, wurde noch ein solches TSR-Programm erstellt.
Insgesamt hatte man eine Lösung, die funktionierte, aber:
* die Lösung ist mit den vielen erforderlichen Installationsschritten schwer
handhabbar
* die Pflege der vielen zusammengehörigen Software-Teile ist sehr kompliziert
und keineswegs zukunftsträchtig
* der Motor stottert; man muß das Windows sehr vorsichtig handhaben, um
Schrittfehler zu vermeiden. Insbesondere ist das Starten größerer Applikationen
problematisch.
* das Stottern des Motors führt zu einer stärkeren mechanischen
Beanspruchung der an der Motorachse befestigten sensiblen Meßanordnung
* Windows wird stark in seiner Leistungsfähigkeit eingeschränkt, es läuft zäh
* die Funktionen, die der Treiber zur Verfügung stellt, sind sehr mächtig. Dieser
Leistungsumfang wird aber gar nicht gebraucht
* für die korrekte Funktion der Software ist eine komplizierte
Emulationsschaltung für die Endschalter nötig
* der Treiber arbeitet mit einem Kompromiß zwischen Präzision und
Geschwindigkeit, was auf schlechte Programmierung hindeutet
* es ist nur eine Notlösung, und wird es auch immer bleiben
* diese Art der Hardware-Ansteuerung ist unter Windows und jedem anderen
Multitasking-System veraltet
Zudem schwört die Firma isel auf das gute alte DOS und erklärte nicht die
Absicht, einen Windows-Treiber zu entwickeln.
Aufgrund dieser vielen negativen Aspekte fiel die Entscheidung, die Hardware
selbst in die Hand zu nehmen und einen geeigneten neuen Treiber zu
entwickeln. Die erforderlichen Informationen zur Ansteuerung der Mikroschritt-
Karte fanden sich im wesentlichen in der isel-Beschreibung. Der Rest wurde
durch Probieren ermittelt.
Der neue Treiber muß mit sehr hoher Privilegierung laufen. Dafür bieten sich
unter Windows die sog. VxD's, Virtuelle Gerätetreiber, an. Diese Programme
arbeiten eng mit dem Kernel zusammen, die gemeinsam im
höchstprivilegierten Ring des Mikroprozessors 80386, dem Ring 0, arbeiten.
Der Windows-Kern besteht im wesentlichen aus dem Initialisierungsblock, dem
VxD-Lader, sowie einer Reihe Standard-VxD's, allen voran der VMM, der Virtual
Machine Manager. Alle diese Teile sind in der Datei WIN386.EXE vereinigt.
Virtuelle Gerätetreiber zu schreiben ist keine alltägliche Sache. Es existiert
bisher keinerlei deutsche Dokumentation oder öffentliche Publikation darüber.
Um VxD's zu schreiben, benötigt man das DDK. Dieses kann man über das
MSDN beziehen, bei dem man mindestens "Level 2" sein muß. Die dazu
erforderliche Mitgliedschaft im MSDN ist recht kostenintensiv [msdn] und keine
einmalige Ausgabe - die Kosten fallen jährlich an. Da diese Mittel nicht zur
Verfügung standen, wurden Wege gesucht, das Ziel auch ohne dieses DDK zu
erreichen.
Ausschlaggebend was das Buch [dave], erhältlich in der hiesigen
Universitätsbibliothek, dem eine Diskette mit DDK-Lite, einem abgespeckten
DDK, beilag. Hilfe für die ersten Schritte fand sich auch in der Newsgroup
"...windows.vxd", die in der TUC bezogen werden kann. Dort wurde u.a.
recherchiert, daß für diesen Problemkreis bisher nur 3 Bücher verlegt wurden
[norton] [dave] [hazzah], und keines davon behandelt die besonderen Aspekte
von Windows95 (von NT ganz zu schweigen).
* Er
Dieser Treiber benutzt die CMOS-Echtzeituhr als Interruptquelle. Die
zugehörige Interruptanfrage-Leitung ist IRQ8. Um
Kompatibilitätsschwierigkeiten zu umgehen und den Rechner nicht unnötig mit
Interrupts zu belasten, wurde die Frequenz auf 1024 Hz festgelegt. Diese
Frequenz wird beispielsweise vom BIOS erwartet, wenn die AT-Zeitgeber-
Funktionen des INT1A benutzt werden. Siehe dazu auch
simtel/msdos/info/timer*.zip.
Die Frequenz liegt erheblich niedriger als beim Originaltreiber, wo sie 10 kHz
betrug. Um dennoch hohe Verfahrgeschwindigkeiten des Schrittmotors zu
erreichen, werden pro Interrupt gegebenfalls auch mehrere Mikroschritte bis
hinauf zu einem Ganzschritt ausgeführt.
Der Treiber unterstützt die gleichzeitige Ansteuerung von allen 3 Motoren der
Karte. Alle Prozeduren erwarten daher in EBX einen Zeiger auf die zu
bearbeitende Motor-Struktur. Das entspricht objektorientierter Programmierung
- allerdings ohne virtuelle Methoden, da es weder Objekte abzuleiten noch
Code wiederzuverwenden gibt.
Wellenform des Strangstroms
Das Originalprogramm verwendet eine sinusförmige Stromsteuerung, deutlich
erkennbar an der im Programmcode eingebauten Sinustabelle. Jedoch ist
dadurch die Gesamtstromaufnahme schwankend. Dabei schwankt auch das
Haltemoment und vermutlich auch die Mikroschrittwinkel. Das verschlechtert
die mechanisch machbaren Eckwerte erheblich. Erforderlich ist eine
Untersuchung, wie die Kurvenform, die je nach Motorkonstruktion und Linearität
der Ansteuerschaltung von der idealen Sinusform abweicht, aussieht. In jeder
Stellung sollte einerseits das Haltemoment gleich sein
(Gesamtstrombedingung) als auch der erwartete Ruhwinkel stimmen
(Stromverhältnisbedingung). Folgende Versuchsanordnung könnte hierzu
verwendet werden (beispielsweise als Thema einer Studienarbeit):
Vorschlag eines Versuchsaufbaus zur Erfassung der Strangstrom-Kurvenform
1
1
Detected encoding: ANSI (CP1252) | 4
|
|