Quelltext /~heha/messtech/mpk3.zip/vxd.txt

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


Vorgefundene Kodierung: UTF-80