Der Geräte-Manager

Dieses Kapitel beinhaltet die folgenden Themen:

Einführung

Der QNX-Geräte-Manager (Dev) bildet die Schnittstelle zwischen Prozessen und zeichenorientierten Terminal-Geräten. Diese Terminalgeräte befinden sich im Ein-/Ausgabe-Namensraum und beginnen mit dem Präfix /dev. So lautet z.B. der Name für ein Konsolen-Terminal unter QNX:

/dev/con1

Geräte-Dienste

QNX greift auf diese Geräte mit den üblichen Funktionen wie read(), write(), open() und close() zu. Aus der Sicht von QNX ist ein Terminal exakt das gleiche wie eine Datei, oder besser gesagt, ein Stream (Datenstrom), zu dem geschrieben und von dem gelesen werden kann.

Der Geräte-Manager reguliert den Datenfluß zwischen einer Anwendung und dem Gerätetreiber. Er entnimmt die entsprechenden Parameter aus einer Struktur namens terminal control structure (kurz termios), welche für jedes Gerät existiert. Der Anwender kann mit dem stty-Werkzeug die Einstellungen abfragen oder verändern. Vom Programm aus stehen die Funktionen tcgetattr() und tcsetattr() zur Verfügung.

Die termios-Struktur kontrolliert dabei Eigenschaften wie:

  • Leitungsstatus (inkl. Baud-Rate, Parität, Stop-Bits und die Anzahl der Datenbits)
  • Zeichenecho
  • Eingabe-Editierverhalten
  • Reaktionen auf Leitungsunterbrechungen und Verbindungsabbau
  • Datenflußkontrolle durch die Hardware oder Software
  • Automatische Ersetzung von Zeichen bei der Ausgabe

Über dies hinaus bietet der Geräte-Manager noch weitere Funktionen für die Steuerung von Terminal-Geräten. Hier ist eine kleine Auswahl von Funktionsaufrufen mit denen ein Prozeß das Verhalten von Geräten steuern kann.

Ein Prozeß kann:über diese C-Funktion:
zeitlich kontrollierte Leseoperationen durchführen dev_read() oder read() und tcsetattr()
einen Prozeß asynchron benachrichtigen, daß Daten für die Eingabe bereitstehen dev_arm()
warten, bis die Ausgabe vollständig durchgeführt ist tcdrain()
Unterbrechungsanforderungen über den Kommunikationskanal senden tcsendbreak()
die Verbindung eines Kommunikationskanals beenden tcdropline()
Eingabedaten zurückstellen dev_insert_chars()
nicht-blockierende Lese- und Schreiboperationen durchführen open() und fcntl() (O_NONBLOCK mode)

Editierender Eingabemodus

Das signifikanteste Bit in der termios Kontrollstruktur, ist das ICANON-Bit. Ist dieses Bit gesetzt, führt der Geräte-Manager alle Editierfunktionen automatisch aus. Nur wenn eine Zeileneingabe beendet wurde - typischerweise mit einem Carriage Return (CR) - werden die Daten an die Applikation weitergeleitet. Dieser Modus wird auch oft als edited, canonical, oder manchmal auch als cooked Modus bezeichnet.

Die meisten zeilenorientierten Anwendungen laufen im Editiermodus. Die Shell ist ein typisches Beispiel hierfür.

Die folgende Tabelle zeigt einige spezielle Kontrollzeichen, die in der termios Kontrollstruktur gesetzt werden, um dem Dev anzuzeigen, wie eine Editierung durchgeführt werden soll.

Dev wird:Beim Erhalt von:
den Cursor um eine Position nach links bewegen LEFT
den Cursor um eine Position nach rechts bewegen RIGHT
den Cursor an den Anfang der Zeile bewegen HOME
den Cursor an das Ende der Zeile bewegen END
das Zeichen links von der momentanen Position löschen ERASE
das Zeichen an der momentanen Cursorposition löschen DEL
die komplette Eingabezeile löschen KILL
die momentane Zeile löschen und die vorherige wiederherstellen UP
die momentane Zeile löschen und die nächste Zeile wiederherstellen DOWN
zwischen Einfüge- und Überschreibmodus wechseln (jede neue Zeile beginnt wieder mit dem Einfügemodus) INS

Zeileneditierzeichen variieren zwischen verschiedenen Terminaltypen. Die QNX-Konsole wird mit einem kompletten Satz an Editiertasten gestartet.

Ist ein Terminal über eine serielle Schnittstelle mit QNX verbunden, müssen die Editierzeichen für das entsprechende Terminal definiert werden. Um dies zu erreichen, steht Ihnen das stty Werkzeug zur Verfügung. Wenn Sie zum Beispiel ein VT100 Terminal an eine serielle Schnittstelle anschließen (sagen wir /dev/ser1), dann können Sie mit dem folgenden Statement die entsprechenden Steuerzeichen aus der terminfo-Datenbank für die Schnittstelle /dev/ser1 setzen:

stty term=vt100

Wenn Sie aber stattdessen an dieser Schnittstelle ein Modem angeschlossen haben, welches über qtalk auf der Gegenseite ebenfalls mit QNX verbunden ist, setzen Sie die Editiertasten wie folgt:

stty term=qnx

Nicht-Editierender Eingabemodus (raw mode)

Wenn das ICANON-Bit nicht gesetzt ist, befindet sich das Gerät im raw Modus. In diesem Zustand werden keine Zeichenübersetzungen durchgeführt und jedes empfangene Zeichen wird unmittelbar an den QNX-Prozeß weitergegeben.

Beispiele hierfür sind bildschirmorientierte und serielle Kommunikationsprogramme.

Liest eine Applikation von einem raw Gerät, kann die Anwendung bestimmen, bei welchen Bedingungen eine Eingabeanforderung befriedigt ist. Die Kriterien zur Festlegung des Eingabeverhaltens befinden sich in der termios Kontrollstruktur. Die Mitglieder dieser Struktur heißen: MIN und TIME. Es steht außerdem ein weiteres Kriterium zur Verfügung, um das Eingabeverhalten bei der Benutzung von dev_read() zu beeinflussen. Dieses Mitglied in der termios Kontrollstruktur, TIMEOUT, kann sinnvoll eingesetzt werden, um Protokolle zu implementieren oder wenn Echtzeitanwendungen dies verlangen. Beachten Sie, daß der Standardwert für TIMEOUT beim read() immer auf 0 gesetzt ist.

Wenn ein QNX-Prozeß eine Leseanforderung für n Bytes Daten durchführt, definieren die drei zuvorgenannten Parameter, wann die Bedingungen für die Leseanforderungen erfüllt sind:

MINTIMETIMEOUTBeschreibung:
0 0 0 Gibt sofort die zur Verfügung stehenden Bytes zurück. (maximal jedoch n Bytes).
M 0 0 Gibt maximal n Bytes zurück, wenn mindestens M Bytes verfügbar sind.
0 T 0 Gibt maximal n Bytes zurück, wenn mindestens ein Zeichen verfügbar ist oder T x 0,1 Sekunde verstrichen ist.
M T 0 Gibt maximal n Bytes zurück, wenn M Bytes verfügbar sind oder ein Zeichen empfangen wurde und die Zeit zwischen zwei Bytes den Wert T x 0,1 Sekunden überschreitet.
0 0 t Reserviert.
M 0 t Gibt maximal n Bytes zurück, wenn t x 0,1 Sekunde verstrichen ist oder M Bytes verfügbar sind.
0 T t Reserviert.
M T t Gibt maximal n Bytes zurück, wenn M Bytes verfügbar sind oder t x 0,1 Sekunde verstrichen ist, ohne daß ein Zeichen empfangen wurde oder ein Zeichen empfangen wurde und die Zeit zwischen den nächsten empfangenen Zeichen den Wert T x 0,1 Sekunde überschreitet.

Gerätetreiber

Die folgende Abbildung zeigt ein typisches QNX-Geräte-Subsystem.


fig: images/devsub_de.gif


 

 

Der Geräte-Manager-Prozeß (Dev) verwaltet den Datenfluß von und zu den QNX-Applikationen. Die Schnittstelle zur Hardware hingegen wird von individuellen Treiberprozessen gebildet. Der Datenaustausch zwischen dem Dev und seinen Treibern wird über Warteschlangen im Shared Memory für jedes Terminalgerät abgewickelt.


Note: Da Shared Memory Warteschlangen benutzt werden, ist es erforderlich, daß Dev mit allen Treibern auf der gleichen physikalischen CPU läft. Der Vorteil hingegen ist ein erhöhter Durchsatz.

Für jedes Gerät werden drei Warteschlangen benutzt, wobei jede als FIFO-Warteschlange implementiert ist. Außerdem existiert für jede Warteschlange eine Kontrollstruktur.

Empfangene Daten werden vom Treiber in der Warteschlange für Eingangsdaten abgelegt und vom Dev entgegengenommen, wenn ein Prozeß diese Daten angefordert hat. Enthält ein Treiber einen Interrupthandler, benutzt dieser typischerweise eine Bibliotheksfunktion innerhalb des Dev, um Daten in dieser Warteschlange abzustellen - dies gewährleistet einen konsistenten Eingabemechanismus und minimiert die Arbeitslast des Treibers erheblich.

Ausgabedaten werden vom Dev in die Ausgabewarteschlange abgestellt; der Treiber wiederum entnimmt sie hieraus, wenn er die Zeichen physikalisch übertragen will. Der Dev ruft eine ihm bekannte Routine im Adressraum des Treibers auf, um anzuzeigen, daß neue Daten verfügbar sind. So kann der Dev den Treiberprozeß ``anwerfen'' (falls er inaktiv war). Da Ausgabewarteschlangen benutzt werden, bietet der Dev vollständiges Write-Behind (Schreiben und sofort zurückkehren) für alle Terminalgeräte. Nur wenn die Ausgabewarteschlange voll ist, bewirkt Dev, daß ein Prozeß bei der Schreiboperation blockiert.

Die kanonische Warteschlange wird vollständig vom Dev verwaltet und wird für Eingabedaten im Editiermodus benutzt. Die Größe dieser Warteschlange bestimmt die Länge einer editierbaren Eingabezeile, die für ein Gerät zur Verfügung steht.

Alle Größenangaben für die Warteschlangen können vom Systemadministrator bestimmt werden, wobei die einzige Beschränkung darin besteht, daß die Summe aller Warteschlangen 64 Kilobyte nicht überschreiten darf. Die Standardwerte sind meist mehr als ausreichend, um die gängigen Hardwarekonfigurationen betreiben zu können. Sie können jedoch bei speziellen Speicher- oder Hardwareanforderungen die Werte ändern.

Gerätesteuerung

Gerätetreiber sind nur für den einfachen Transport von der Hardware in die Eingabewarteschlange, bzw. von der Ausgabewarteschlange, zur Hardware verantwortlich. Es ist der Dev der darüber wacht, ob und wann Übertragungen angehalten, Daten empfangen und Zeichen als Echo erscheinen, etc..

Um eine gute Antwortzeit zu gewährleisten, wird der Dev mit einer entsprechend hohen Priorität betrieben. Die Arbeitslast des Dev ist typischerweise sehr gering, so daß er das Gesamtverhalten des Systems nur minimal beeinflußt.

Die Treiber selbst verhalten sich wie gewöhnliche QNX-Prozesse - sie können mit verschiedenen Prioritäten betrieben werden, gerade so, wie es die bediente Hardware verlangt.

Gerätekontrolle auf unterster Stufe ist implementiert durch einen ``far call'' auf eine Funktion namens ioctl, die sich im Adressraum des Treibers befindet. Die meisten Treiber haben einen Standardsatz von ioctl Kommandos implementiert, die direkt vom Dev benutzt werden. Gerätespezifische ioctl-Kommandos können ebenfalls über den Dev an den Treiber gesandt werden, um QNX-Prozessen diese Funktionalität zur Verfügung zu stellen (mit der qnx_ioctl() Funktion).

Die QNX-Konsole

Systemkonsolen werden vom Dev.con bzw. Dev.ansi Treiberprozeß verwaltet. Als Konsole bezeichnen wir hier die Grafikkarte, den Bildschirm und die Systemtastatur.

QNX unterstützt das Verfahren von virtuellen Konsolen, um mehrere Sitzungen konkurrierend ausführen zu können. Der Dev.con Treiberprozeß verwaltet normalerweise mehrere I/O-Warteschlangen zum Dev, welche dem Anwenderprozeß namentlich als /dev/con1, /dev/con2, etc. zur Verfügung stehen. Aus der Sicht der Applikation sind dies echte zur Vefügung stehende Konsolen.

Natürlich existiert physikalisch nur ein Bildschirm und nur eine Tastatur, so daß jeweils nur eine der virtuellen Konsolen zu einem Zeitpunkt aktiv sein kann. Die Tastatur ist immer mit der gerade aktiven und sichtbaren Konsole ``verbunden''.

Konsolenspezifische Funktionen

Zusätzlich zur Implementierung des Standard-QNX-Terminals (wie im QNX Installation & Konfiguration Handbuch angegeben), bietet der Konsolentreiber einen Satz von Funktionen, die es einem Anwendungsprozeß ermöglicht, durch Nachrichten direkt mit ihm zu kommunizieren. Die Kommunikation wird mit der console_open() Funktion aufgebaut. Ist eine Verbindung hergestellt, hat ein QNX-Prozeß folgende Möglichkeiten:

Ein Prozeß kann:C-Funktion:
den Konsolenbildschirm direkt auslesen console_read()
direkt in den Konsolenbildschirm schreiben console_write()
sich asynchron benachrichtigen lassen, wenn wichtige Ereignisse eingetreten sind (z.B. angezeigte Daten oder die Größe hat sich verändert, der Cursor wurde bewegt oder die sichtbare Konsole wurde gewechselt, etc.) console_arm()
die Konsolengröße setzen console_size()
die sichtbare Konsole umschalten console_active()

Der QNX-Konsolentreiber läuft als normaler QNX-Prozeß. Eingabezeichen hingegen werden vom Tastatur-Kontroller entgegengenommen und vom Tastaturinterrupthandler direkt in die Eingabewarteschlange abgestellt. Ausgabedaten werden vom Dev.con konsumiert und angezeigt, während er als normaler Prozeß ausgeführt wird.

Serielle Geräte

Serielle Kommunikationskanäle werden vom Dev.ser Treiberprozeß verwaltet. Er ist in der Lage, mehr als einen physikalischen Kanal zu bedienen und bedient Terminalgeräte mit Namen wie /dev/ser1, /dev/ser2, etc.

Wenn der Dev.ser Prozeß gestartet wird, können Sie mit Kommandozeilenargumenten bestimmen, welche - und wie viele - serielle Ports installiert werden. Um nachzusehen, wieviel Ports auf einem QNX-System verfügbar sind, benutzen Sie das ls Werkzeug:

ls /dev/ser*

Der Dev.ser ist ein Beispiel für einen ausnahmslos interruptgetriebenen I/O-Server. Nachdem er die Hardware initialisiert hat, legt er sich schlafen. Empfangene Interrupts stellen Eingabedaten direkt in die Eingabewarteschlange ab. Das erste Ausgabezeichen eines unbenutzen Kanals wird zur Hardware übertragen, wenn der Dev einen ersten Anstoß des Treibers initiiert. Alle weiteren Zeichen werden übertragen, indem der Treiber auf Interrupts reagiert, die durch das vollständige Übertragen eines Zeichens von der Hardware ausgelöst werden.

Parallele Geräte

Parallele Druckerports werden vom Dev.par Treiberprozeß verwaltet. Wenn der Dev.par gestartet wird, kann man mit Kommandozeilenargumenten bestimmen, welche parallelen Ports installiert werden. Um nachzusehen, welche auf einem QNX-System verfügbar sind, benutzen Sie das ls Werkzeug:

ls /dev/par*

Der Dev.par ist ein reiner Ausgabe-Treiber, deshalb hat er keine Eingabe- bzw. kanonische Eingabewarteschlange. Sie können die Größe der Ausgabewarteschlange mit einem Kommandozeilenargument angeben. Dieser Ausgabepuffer kann sehr groß gewählt werden, wenn Sie möchten, daß eine Art Softwaredruckpuffer entsteht.

Der Dev.par ist ein Beispiel für einen I/O-Server, der ohne jeden Interrupt arbeitet. Der Druckerprozeß bleibt normalerweise RECEIVE-blockiert, bis Ausgabedaten für ihn verfügbar sind und er vom Dev angeworfen wird. Wenn Daten zum Drucken eintreffen, wechselt der Dev.par in eine ``busy-wait Schleife'' (mit relativ niedriger adaptiver Priorität), während er darauf wartet, daß die Druckerhardware ein Zeichen akzeptiert hat. Diese niedrig priorisierte ``busy-wait Schleife'' stellt sicher, daß das Systemverhalten nicht beeinflußt wird.

Gerätesubsystem-Performance

Die Abfolge von Ereignissen innerhalb des Gerätesubsystems wurde konstruiert, um den Overhead auf ein Minimum zu reduzieren und den Durchsatz auf ein Maximum zu bringen, für den Fall, daß ein Gerät im raw-Modus betrieben wird. Um dieses zu ermöglichen, werden die folgenden Regeln benutzt:

  • Interrupthandler stellen ihre Empfangsdaten direkt in die Speicherwarteschlange ab. Nur wenn eine Leseoperation ansteht und diese Leseoperation sofort befriedigt werden kann, plant der Interrupthandler den Dev für einen Start ein. In allen anderen Fällen kehrt der Interrupthandler zurück, ohne eine Proxy auszuliefern. Wenn über dies hinaus der Dev bereits läuft, muß kein Scheduling durchgeführt werden.
  • Wenn eine Leseoperation befriedigt wurde, transferiert Dev die Daten vom Raw Eingabepuffer direkt in den Empfangspuffer der Anwendung. Der angenehme Nebeneffekt ist, daß die Daten nur ein einziges Mal kopiert werden müssen.

Aus diesen Regeln resultiert - verbunden mit der extrem kleinen Interrupt- und Schedulinglatenzzeit in QNX - daß das Eingabemodell sehr effizient arbeitet und dennoch sehr kompakt ist.