Leider haben die Verleger jedoch in der gedruckten Version das mit Abstand wichtigste Bild aus dem Text entfernt und stattdessen einige irrelevante Bilder eingefuegt. In der Online Version wurden zwar keine Bilder hinzugefuegt, jedoch das wichtigste Bild fehlt auch hier. Daher erlaube ich mir, es hier zu zeigen ;)
EYCar: Linux steuert Roboter in EchtzeitLinuxroboticsvon Erik Thiele |
Es ist Mitternacht. In einem mit Elekronik vollgestopften Kellerraum spielt sich folgendes Szenario ab. Es werden Reflektoren aufgestellt. Die Koordinaten der Reflektoren werden dem EYCar, unserem Linux-Roboter eingegeben. Der EYCar sendet einen im Raum rotierenden Laserstrahl aus, der nur beim Überstreichen eines Katzenauges zurückgeworfen wird. Auf diese Weise werden die Winkel, unter denen die Katzenaugen relativ zum Fahrzeug stehen gemessen. Daraus kann das Fahrzeug seine Position errechnen. Es ist nun möglich, dem Fahrzeug eine Position vorzugeben, und es fährt diese an. Der Anwender sitzt am stationären Computer und kann dort über eine ansprechende grafische Oberfläche das Geschehen steuern. Der EYCar und der stationäre Computer kommunizieren über Funkgeräte.
Das ist die absolute Kurzfassung des EYCar-Projektes. Ich werde nun auf die Linux- spezifischen Gegebenheiten eingehen. Das System besteht aus 2 Computern. Einer davon steht auf einem Tisch, hat einen Bildschirm und eine Tastatur. Der andere ist in dem Alugehäuse auf dem EYCar untergebracht und hat weder Tastatur, noch Bildschirm. Beide Maschinen fahren Linux 2.0.30 mit diversen AX.25 Kernelpatches. (Das AX.25 Protokoll wird benutzt, um IP über Funkmodems (Packet Radio) zu realisieren) Der Rechner auf dem Fahrzeug ist ein AMD386SX-40 mit 8 MB Hauptspeicher. Er hat zusätzlich noch den RT-Linux 0.5 Kernel patch installiert. (Damit kann man Echtzeitprozesse auf unterster Ebene realisieren, mit wirklich wahnsinnig kurzen Reaktionszeiten)
Ein kleines Beispiel des Geschehens:
Das offene Ende wird direkt auf die serielle Schnittstelle gesteckt. Alles was so aussieht wie dieser kleine schwarze Klotz ist ein Baycom Modem, auch wenn es weder draufsteht, noch das Gerät unter diesem Namen verkauft wurde, wie dies hier zum Beispiel auch der Fall war (Dies ist die Packet Radio Komplettlösung von Conrad Electronic).
Nun. Er hat eine kleine Digital-IO Karte (die parallele Schnittstelle hat nicht genügend Pins). Daran angeschlossen sind 3 Schrittmotoren, (2 Motoren zum Antrieb, ein Motor dreht das Laserrohr), ein Laser und eine Gabellichtschranke zur Referenzierung des Motors des Laserrohres. Die verwendeten Schrittmotorsteuerungen sind eigentlich nur Verstärker. Der Computer muß also die Spulen der Motoren direkt ansteuern. Das heißt, daß bei einer Lauffrequenz von 100 Hertz der Computer auch 100 mal in der Sekunde die Spulen der Motoren neu einstellen muß. Die beiden Antriebsmotoren werden getrennt voneinander geregelt, die Geschwindigkeit ist frei wählbar, und die Motoren beschleunigen und bremsen geregelt. Letztendlich bekommt das Programm nur den Befehl "Fahre 100 Schritte mit dem rechten und -100 Schritte mit dem linken Motor", daraufhin beschleunigt es selbstständig die Motoren und bremst sie rechtzeitig wieder ab. Um die verschiedenen Drehzahlen zu realisieren braucht man verschiedene Frequenzen. Da ich aber den Timer nicht so genau einstellen kann (man kann seine Frequenz immer nur verdoppeln) benutze ich Interpolationstechniken wie bei Modplayern. Die Trägerfrequenz beträgt 8192 Hz. Man kommt also in Bereiche, wo normale Prozesse, die ihr Timing mit nanosleep()-Befehlen realisieren, absolut untauglich sind. Ein kleiner Festplatteninterrupt oder ein bischen FTP, und schon ist das Timing absolut im Eimer.
Außerdem können normale Prozesse auf der Festplatte ausgelagert werden, und das ist absolut indiskutabel. Ich habe daher den Regler als RT-Linux Echtzeitkernelmodul implementiert. Der gesamte Linuxkernel läuft nur noch nebenher, selbst wenn Teile des Kernels die Interrupts sperren, so tun sie dies nicht wirklich (dafür sorgt der RT-Linux Kernelpatch). Absolute Priorität haben die Echtzeitkernelmodule! Auf diese Weise laufen die Motoren einwandfrei, egal wieviele Compiler, Funkgeräte, Netscapes (auch noch ständig auf der Platte ausgelagert und wieder reingeladen) laufen.
Die nächste Aufgabe des Echtzeitmoduls ist die Vermessung der Winkel unter denen das Fahrzeug die Katzenaugen sieht. Hierfür steuert es den Motor für das Laserrohr auf eine konstante Drehzahl. Trifft nun der Laser auf ein Katzenauge, so wird über eine Elektronik ein IRQ10 ausgelöst. Die Echtzeitinterruptroutine meines Moduls liest SOFORT (und wirklich SOFORT !) die Systemuhr mit rt_get_time() aus. Warum mache ich das, ich weiß doch die Position des Rohres auch durch den Schrittmotor, der es ansteuert? Richtig, aber die Zeitmessung ist genauer. Ich benutze den Schrittmotor lediglich zur Erzeugung einer konstanten Drehzahl, den Winkel errechne ich aus der Zeit. Auch hier ist wieder jede noch so geringe Verzögerung beim IRQ10 tödlich. Ein normaler Kernelmodul könnte zum Beispiel durch einen Festplatteninterrupt verzögert werden, nicht jedoch ein RT-Linux Kernelmodul.
In letzter Zeit habe ich zufällig noch eine weitere Methode entdeckt, Realtime mit Linux zu machen. Gegen Realtime sprechen bei anständigen Betriebssystemen immer zwei Dinge:
Wann soll ich jetzt welche Art von Realtime machen?
An alle, die jetzt enthusiastisch zu programmieren beginnen, noch eine wichtige Warnung im voraus: lässt man den Modplayer in einem XTerm laufen, so nutzt das ganze Realtime nichts. Wenn der XTerm den stdout blockiert (und das ist bei etwas Load recht schnell der Fall), so stoppt der Modplayer (die linux-console hingegen blockiert so schnell nicht !). Ihr müsst also eure Anwendungen aufteilen! Zur Kommunikation der Programmteile ist NICHT Mutex-Locking zu empfehlen!!! Denn wenn das nicht-Realtime-Programm den Mutex lockt und dann ausgelagert wird und das nächste Mal in 10 Sekunden ein bischen CPU bekommt, so wird auch hier das Realtime Programm blockiert. Daher muß die Kommunikation zwischen Realtime und nicht-Realtime mittels Pipes realisiert werden! Da kann man sich nicht gegenseitig blockieren. Ein Riesenvorteil ist übrigens, daß dieses Verfahren bei jedermann funktioniert. Es ist ein Standard-Feature des Linuxsystems. Nachteil: Zum Einstellen der Realtime Prorität muß man root sein. Dies kann jedoch am Programmstart erledigt werden, und danach kann man sich zu nobody umwandeln.
Da ich mich sehr für Computermusik interressiere, überlege ich oft, wie man einen 100% ausfallsicheren Soundserver programmieren kann, der trotzdem eine geringe Reaktionszeit besitzt. Will man das kurzzeitige Stoppen der Audioausgabe unterbinden, vergrößert man normalerweise einfach den Audiopuffer und erledigt die Arbeit so weit im voraus, daß selbst eine längere Unterbrechung des Programms durch Swapping oder hohe Systemlast zu keinem Ausfall führt (selbiges Verfahren bei CD-Brenner Software!). Soll der Soundserver aber zum Beispiel einen "Gong" ausgeben, wenn der Anwender die Maustaste drückt, so kann der Audiopuffer nicht mehr groß gewählt werden, sondern er muss sehr klein gemacht werden, damit man den Mausgong auch zum richtigen Zeitpunkt und nicht erst 3 Sekunden danach vernimmt.
Nun aber wirkt sich wieder jede noch so kleine Pause des Soundservers unterbrechend auf die Audioausgabe aus. Es liegt also nahe, mit mlockall() den Speicher gegen Swappen zu schützen und mit sched_setscheduler() eine höhere Priorität anzulegen. Leider hat diese Sache einen Haken. Die höhere Priorität ist gewährleistet, das funktioniert auch prima, aber das Swappen ist problematisch. Angenommen das Programm macht einen malloc(), so muß das System dem Programm neuen Speicher zur Verfügung stellen. Dies kann unter Umständen sehr lange dauern, dann nämlich wenn kein weiterer freier Speicher da ist. Es gilt dann, einen anderen Prozess auszulagern, und währenddessen ist unser Soundserver dann blockiert. Ähnliche Probleme bekommt man, wenn man zuviel Stack (zum Beispiel durch Programmieren allzuvieler Rekursionen) benutzt (daher empfiehlt die Manpage von mlockall(), einmal zu Beginn eine sinnlose Stackbenutzung zu absolvieren, damit später kein neuer Stack mehr angefordert werden muss) Für das malloc()-Problem gibt es jedoch keine Lösung. (Man könnte natürlich am Programmbeginn mal eben 20 MB RAM malloc()en und dann einen eigenen malloc() programmieren, der innerhalb der 20 MB eine eigene Speicherverwaltung realisiert, und somit niemals RAM vom Kernel braucht, die Nachteile sind jedoch gravierend: erstens sind die 20 MB wirklich weg, man hat danach wirklich 20 MB weniger RAM in seinem Rechner, und zweitens kann man niemals mehr als 20 MB RAM im Soundserver benutzen)
Nun es gibt aber noch ähnliche Probleme ohne direkte Lösung: open() zum Beispiel benötigt Zeit. Bei der Netzwerkprogrammierung existieren einige Möglichkeiten, die entsprechenden Systemcalls (connect, accept, read, write) so einzustellen, daß sie niemals blockieren. Der Befehl zum Öffnen einer Datei jedoch blockiert, bis die Datei offen ist. (Man stelle sich eine 300 Baud Verbindung zu einem NFS Server vor und denke darüber nach, wie lange der open()-Befehl dann braucht :-)
Für mich ist das open() Problem identisch dem malloc() Problem. In beiden Fällen möchte ein Prozess, der seine Hauptschleife ständig durchlaufen MUSS, und sich keine Unterbrechung erlauben DARF, eine Aktion ausführen, die eben blockiert. Was liegt also näher, als einem anderen Prozess diese Arbeit aufzuladen. Sprich, der Soundserver befiehlt seinem Sklavenprozess, eine Datei zu öffnen, und der Sklave meldet irgendwann die Vollendung des Befehls. Meines Wissens ist jedoch das Öffnen einer Datei und das darauffolgende Übergeben des Handles bei normalen Prozessen NICHT möglich, höchstens bei malloc() könnte ich mir irgendwelche Trickreiche Shared Memory (SHM) Tricks denken.
Um trotzdem jemand anderem als mir selbst die Arbeit des Dateiöffnens aufzubürden, kommen also nur POSIX Threads in Frage. Dann steht nichts mehr im Wege. Ein Thread kann die Datei öffnen, während ein anderer in aller Ruhe weiterhin sein Realtime Gehabe treibt. Es ist sogar möglich, die verschiedenen Threads auf unterschiedlicher Priorität laufen zu lassen. So kann der Sklave zum Beispiel auf normaler Priorität laufen, während der eigentliche Soundserverthread in Echtzeit läuft. OK, das Problem mit dem Öffnen von Dateien währe also gelöst, und mit dem malloc() gehts genauso.
Der Soundserver wird so sehr komplex. Immerhin sind malloc() und open() nun plötzlich gespaltene Befehle, die irgendwann gestartet, und irgendwann später asynchron fertig sind, aber immerhin kann man hiermit einen wunderbaren Soundserver schreiben. (oder einen CD-Brenner, der braucht aber weder malloc(), noch open(), ist somit ein Kinderspiel.
Weiterhin sei gesagt, daß die von mir beschriebenen Dinge sehr gut in den Manpages der entsprechenden Befehle erklärt sind. Als Einstieg sei auf mlockall, sched_setscheduler und pthread_attr_setschedpolicy verwiesen. WAS? Diese Manpages existieren auf deinem System nicht? Dann wird es Zeit, daß Du auf Debian-1.3.1 umsteigst, das ist sowieso die beste aller Distributionen *räusper* ;-)
Ich muss noch etwas über die vielseitig gelobte libQT erzählen. Wie gesagt habe ich sie für mein EYCar Projekt verwendet, um eine GUI unter X zu realisieren. Dabei fand ich einen Bug, der binnen nur zwei Tagen von den Autoren intern gefixt war. Aber einen offiziellen Bugfix wollen sie definitiv nicht machen. Mittlerweile sind einige Bugs in der Bibliothek entdeckt worden (im Prinzip nichts Schlimmes, ganz normal) aber es werden keine neuen Releases gemacht. Das nächste Release wird neben den Fixes auch neue Features also wieder neue Bugs enthalten. Auf diese Weise wird die Lib niemals zuverlässig funktionieren. Die aktuelle Version ist nutzlos sofern einen die Bugs direkt betreffen. Einer ist so schlimm, daß die Arbeit an einem IRC Client kurzfristig eingestellt wurde. Selber fixen darf man die Bugs nicht, das ist aufgrund der Lizenz illegal. Da eine GUI eine grundlegende Angelegenheit ist (so wie der Linux Kernel) darf dies alles so nicht sein. Leider ist die libQT (ohne die Bugs) eine sehr gute Bibliothek mit einigen guten Gedanken, und sinnvolle Konkurrenz gibt es bisher wenig. Jedoch stecken auch in dem Prinzip der Bibliothek ein paar (meiner Meinung nach grundlegende) Haken, die mir persönlich etwas die Begeisterung genommen haben.
Weitere Infos |
http://www.erikyyy.de/ Homepage des Autors http://www.erikyyy.de/eycar/ Homepage des EYCar. Die Sourcecodes sind auch downloadbar. Enthalten auch Scripte zum Einrichten der Funkmodems http://www.troll.no/ Die libQT zur Erstellung grafischer Oberflächen http://luz.cs.nmt.edu/~rtlinux/ RT-Linux Webpage http://sunsite.unc.edu/LDP/HOWTO/AX25-HOWTO.html AX25-Howto http://www.linuxhq.com/patch/20-p0768.html QNX-style scheduling v1.06 for Linux 2.0 http://www.iit.edu/~linjinl/esep.html esep (Evolution Scheduling and Evolving Processes) |
Der Autor |
Erik Thiele hält nichts von Umlauten in der deutschen Sprache (sorry, daß wir sie ihm trotzdem untergejubelt haben), beschäftigt sich schon längere Zeit mit Linux, ist davon total begeistert und freut sich stets über Spenden ;-) Zu erreichen ist er unter (erikyyy at erikyyy dot de, Erik Thiele) oder im IRC als erikyyy. |
Copyright © 1997 Linux-Magazin Verlag