338
C UND LINUX DIE MÖGLICHKEITEN DES BETRIEBSSYSTEMS MIT EIGENEN PROGRAMMEN NUTZEN martin GRÄFE 4. Auflage

C Und Linux (4. Auflage)

Embed Size (px)

DESCRIPTION

Der Klassiekr fuer Anfaenger in Linux mit C

Citation preview

martin GRFE

C UND LINUXDIE MGLICHKEITEN DES BETRIEBSSYSTEMS MIT EIGENEN PROGRAMMEN NUTZEN4. Auflage

Grfe C und Linux

C

v

Bleiben Sie einfach auf dem Laufenden: www.hanser.de/newsletter Sofort anmelden und Monat fr Monat die neuesten Infos und Updates erhalten.

Martin Grfe

C und LinuxDie Mglichkeiten des Betriebssystems mit eigenen Programmen nutzen

4., vollstndig berarbeitete und erweiterte Auflage

Dr.-Ing. Martin Grfe, geboren 1968 in Hagen, studierte Elektrotechnik an der Universitt Dortmund. Dort war er nach Abschluss des Studiums als wissenschaftlicher Mitarbeiter ttig und promovierte 1998 auf dem Gebiet der Mikroelektronik. Bereits whrend des Studiums befasste sich Martin Grfe mit C-Programmierung unter Unix und seit 1995 schlielich auch mit Linux. Im Rahmen seiner Promotion und seiner Ttigkeit als Ingenieur entwickelte er verschiedene Programme zur Simulation elektronischer Schaltungen und bertragungssysteme.

Alle in diesem Buch enthaltenen Informationen, Verfahren und Darstellungen wurden nach bestem Wissen zusammengestellt und mit Sorgfalt getestet. Dennoch sind Fehler nicht ganz auszuschlieen. Aus diesem Grund sind die im vorliegenden Buch enthaltenen Informationen mit keiner Verpflichtung oder Garantie irgendeiner Art verbunden. Autoren und Verlag bernehmen infolgedessen keine juristische Verantwortung und werden keine daraus folgende oder sonstige Haftung bernehmen, die auf irgendeine Art aus der Benutzung dieser Informationen oder Teilen davon entsteht, auch nicht fr die Verletzung von Patentrechten und anderen Rechten Dritter, die daraus resultieren knnten. Autoren und Verlag bernehmen deshalb keine Gewhr dafr, dass die beschriebenen Verfahren frei von Schutzrechten Dritter sind. Die Wiedergabe von Gebrauchsnamen, Handelsnamen, Warenbezeichnungen usw. in diesem Buch berechtigt deshalb auch ohne besondere Kennzeichnung nicht zu der Annahme, dass solche Namen im Sinne der Warenzeichen- und Markenschutz-Gesetzgebung als frei zu betrachten wren und daher von jedermann benutzt werden drften.

Bibliografische Information der Deutschen Nationalbibliothek: Die Deutsche Nationalbibliothek verzeichnet diese Publikation in der Deutschen Nationalbibliografie; detaillierte bibliografische Daten sind im Internet ber http://dnb.ddb.de abrufbar.

Dieses Werk ist urheberrechtlich geschtzt. Alle Rechte, auch die der bersetzung, des Nachdruckes und der Vervielfltigung des Buches, oder Teilen daraus, vorbehalten. Kein Teil des Werkes darf ohne schriftliche Genehmigung des Verlages in irgendeiner Form (Fotokopie, Mikrofilm oder ein anderes Verfahren) auch nicht fr Zwecke der Unterrichtsgestaltung reproduziert oder unter Verwendung elektronischer Systeme verarbeitet, vervielfltigt oder verbreitet werden. 2010 Carl Hanser Verlag Mnchen Wien (www.hanser.de) Lektorat: Margarete Metzger Herstellung: Irene Weilhart Copy editing: Manfred Sommer, Mnchen Umschlagdesign: Marc Mller-Bremer, www.rebranding.de, Mnchen Umschlagrealisation: Stephan Rnigk Datenbelichtung, Druck und Bindung: Ksel, Krugzell Ausstattung patentrechtlich geschtzt. Ksel FD 351, Patent-Nr. 0748702 Printed in Germany ISBN 978-3-446-42176-9

Inhaltsverzeichnis1 Einfuhrung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.1 1.2 Warum gerade C? . . . . . . . . . . . . . . . . . . . . . . . . . . . . Bevor es losgeht . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.2.1 1.2.2 1.3 Paketverwaltung unter SuSE-Linux . . . . . . . . . . . . . . Paketinstallation bei Ubuntu . . . . . . . . . . . . . . . . . . 1 1 2 2 4 6 6 8 8 10 11 14 14 16 17 19 21 25 26 28 31 31 31 33

Die Werkzeuge . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.3.1 Der Editor die Qual der Wahl . . . . . . . . . . . . . . . . . 1.3.2 1.3.3 1.3.4 Der GNU C-Compiler gcc . . . . . . . . . . . . . . . . . . . . Ablaufsteuerung mit GNU make . . . . . . . . . . . . . . . . Fur die Fehlersuche: Die Debugger . . . . . . . . . . . . . . .

1.4

1.3.5 Integrierte Entwicklungsumgebungen . . . . . . . . . . . . . Der Umgang mit Compiler, Debugger und make anhand von Bei spielen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.4.1 Primzahlen berechnen . . . . . . . . . . . . . . . . . . . . . . 1.4.2 1.4.3 1.4.4 1.4.5 Fehlersuche mit dem gcc . . . . . . . . . . . . . . . . . . . . . Fehlersuche mit dem GNU Debugger . . . . . . . . . . . . . Funktionsbibliotheken verwenden . . . . . . . . . . . . . . . Quelltexte aufteilen . . . . . . . . . . . . . . . . . . . . . . . .

1.5

Weiterfuhrende Informationen . . . . . . . . . . . . . . . . . . . . . 1.5.1 Die Unix-Online-Hilfen man, xman und tkman . . . . 1.5.2 Ein Blick hinter die Kulissen: Die Include-Dateien . . . . . .

2 Arbeiten mit einer Entwicklungsumgebung . . . . . . . . . . . . . . . . 2.1 Anjuta . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.1.1 2.1.2 Ein neues Projekt anlegen . . . . . . . . . . . . . . . . . . . . Eingabe der Quelltexte . . . . . . . . . . . . . . . . . . . . . .

VI

Inhaltsverzeichnis

2.2 2.3

2.1.3 Kompilieren und Starten des Beispiels . . . . . . . . . . . . . KDevelop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Eclipse + C Development Tooling (CDT) . . . . . . . . . . . . . . . . 2.3.1 Plug-ins einbinden . . . . . . . . . . . . . . . . . . . . . . . . 2.3.2 Ein neues Projekt anlegen . . . . . . . . . . . . . . . . . . . .

35 36 39 40 40 43 43 44 44 45 47 48 48 50 51 53 60 60 61 67 67 67 68 69 70 74 75 77 79 79 80 80 81

3 Kommandozeilenprogramme . . . . . . . . . . . . . . . . . . . . . . . . . 3.1 Parameter und Ruckgabewert der Funktion main() . . . . . . . . . 3.1.1 Die Bedeutung des Ruckgabewertes von main() . . . . . . 3.1.2 3.1.3 3.2 Die Variablen argc und argv . . . . . . . . . . . . . . . . . . Auswerten der Kommandozeilenparameter . . . . . . . . .

3.1.4 Achtung: Platzhalter! . . . . . . . . . . . . . . . . . . . . . . . Konventionen fur Kommandozeilenprogramme . . . . . . . . . . . 3.2.1 3.2.2 3.2.3 Ein Muss: Die Hilfe-Option . . . . . . . . . . . . . . . . . . . Fehlermeldungen . . . . . . . . . . . . . . . . . . . . . . . . . Eigene Manpages erstellen . . . . . . . . . . . . . . . . . . . .

3.3 3.4

Programme mehrsprachig auslegen . . . . . . . . . . . . . . . . . . Ausgabesteuerung im Terminal-Fenster . . . . . . . . . . . . . . . . 3.4.1 3.4.2 ANSI-Steuersequenzen . . . . . . . . . . . . . . . . . . . . . Die ncurses-Bibliothek . . . . . . . . . . . . . . . . . . . . .

4 Dateien und Verzeichnisse . . . . . . . . . . . . . . . . . . . . . . . . . . 4.1 Die Arbeit mit Dateien . . . . . . . . . . . . . . . . . . . . . . . . . . 4.1.1 4.1.2 4.1.3 4.1.4 4.2 4.3 Gepufferte Ein-/Ausgabe . . . . . . . . . . . . . . . . . . . . stdin, stdout und stderr . . . . . . . . . . . . . . . . . . . Dateien offnen und schlieen . . . . . . . . . . . . . . . . . . Lesen aus und Schreiben in Dateien . . . . . . . . . . . . . .

4.1.5 Ein Beispiel: Zeilen nummerieren . . . . . . . . . . . . . . . Eigenschaften von Dateien oder Verzeichnissen auswerten . . . . . Verzeichnisse einlesen . . . . . . . . . . . . . . . . . . . . . . . . . .

5 Interprozesskommunikation . . . . . . . . . . . . . . . . . . . . . . . . . 5.1 Prozessverwaltung unter Linux . . . . . . . . . . . . . . . . . . . . . 5.2 Neue Prozesse starten . . . . . . . . . . . . . . . . . . . . . . . . . . 5.2.1 Shell-Programme aufrufen mit system() . . . . . . . . . . . 5.2.2 Die Funktionen der exec-Familie . . . . . . . . . . . . . . .

Inhaltsverzeichnis

VII

5.2.3 5.2.4 5.3

Einen Kind-Prozess erzeugen mit fork() . . . . . . . . . . . Warteschleifen . . . . . . . . . . . . . . . . . . . . . . . . . .

82 85 86 87 88 89 90 91 91 95

Signale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.3.1 Die Weckfunktion alarm() . . . . . . . . . . . . . . . . . . . 5.3.2 5.3.3 Einen Signal-Handler einrichten . . . . . . . . . . . . . . . . Auf die Beendigung eines Kind-Prozesses warten . . . . . .

5.4

5.3.4 Signale setzen mit kill() . . . . . . . . . . . . . . . . . . . . Datenaustausch zwischen Prozessen . . . . . . . . . . . . . . . . . . 5.4.1 5.4.2 Pipes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . FIFOs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

5.5

5.4.3 Shared Memory . . . . . . . . . . . . . . . . . . . . . . . . . . 97 Alternative Verfahren zur Erzeugung von Prozessen . . . . . . . . . 100 5.5.1 5.5.2 5.5.3popen() und pclose() . . . . . . . . . . . . . . . . . . . . . 100

Die fork()-Alternative clone() . . . . . . . . . . . . . . . 101 POSIX-Threads . . . . . . . . . . . . . . . . . . . . . . . . . . 103

6 Devices das Tor zur Hardware . . . . . . . . . . . . . . . . . . . . . . . 107 6.1 Das Device-Konzept von Linux . . . . . . . . . . . . . . . . . . . . . 107 6.1.1 6.1.2 6.2 Devices offnen und schlieen . . . . . . . . . . . . . . . . . . 108 Ungepuffertes Lesen und Schreiben . . . . . . . . . . . . . . 109

6.1.3 Devices steuern mit ioctl() . . . . . . . . . . . . . . . . . . 110 Das CD-ROM-Laufwerk . . . . . . . . . . . . . . . . . . . . . . . . . 111 6.2.1 6.2.2 Die CD auswerfen . . . . . . . . . . . . . . . . . . . . . . . 111 F higkeiten des Laufwerks auslesen . . . . . . . . . . . . . . 112 a

6.3

6.2.3 Audio-CDs abspielen . . . . . . . . . . . . . . . . . . . . . . 114 Ansteuerung einer Soundkarte . . . . . . . . . . . . . . . . . . . . . 121 6.3.1 6.3.2 OSS, ALSA und ESOUND . . . . . . . . . . . . . . . . . . . . 122 Der Mixer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122

6.4

6.3.3 Audiodaten aufnehmen und wiedergeben . . . . . . . . . . 126 Video for Linux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130 6.4.1 Eigenschaften des Devices . . . . . . . . . . . . . . . . . . . . 130 6.4.2 Bilder aufzeichnen . . . . . . . . . . . . . . . . . . . . . . . . 133 Die serielle Schnittstelle . . . . . . . . . . . . . . . . . . . . . . . . . 142 6.5.1 6.5.2 Terminal-Parameter einstellen . . . . . . . . . . . . . . . . . 143 Ein kleines Terminalprogramm . . . . . . . . . . . . . . . . . 145

6.5

6.6

Druckerausgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149

VIII

Inhaltsverzeichnis

6.7

Der Universal Serial Bus (USB) . . . . . . . . . . . . . . . . . . . . . . 154 6.7.1 Ansteuerung von USB-Ger ten anhand eines Beispiels . . . 156 a

7 Netzwerkprogrammierung . . . . . . . . . . . . . . . . . . . . . . . . . . 163 7.1 Einfuhrung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164 7.1.1 7.1.2 7.1.3 7.1.4 7.2 Begriffe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164 Vorbereitung . . . . . . . . . . . . . . . . . . . . . . . . . . . 166 Das Client-Server-Prinzip . . . . . . . . . . . . . . . . . . . . 169 Sockets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 170

Der TCP/IP-Client . . . . . . . . . . . . . . . . . . . . . . . . . . . . 171 7.2.1 Aufbau einer Verbindung . . . . . . . . . . . . . . . . . . . . 171 7.2.2 7.2.3 Ein Universal-Client . . . . . . . . . . . . . . . . . . . . . . 173 Rechnernamen in IP-Adressen umwandeln . . . . . . . . . . 176

7.3

Server-Programme . . . . . . . . . . . . . . . . . . . . . . . . . . . . 178 7.3.1 Die Funktionsweise eines Servers . . . . . . . . . . . . . . . 178 7.3.2 Ein interaktiver TCP/IP-Server . . . . . . . . . . . . . . . . . 180 7.3.3 Ein kleiner Webserver . . . . . . . . . . . . . . . . . . . . . . 184 Das User Datagram Protocol (UDP) . . . . . . . . . . . . . . . . . . . . 191 7.4.1 7.4.2 7.4.3 7.4.4 UDP-Nachrichten senden . . . . . . . . . . . . . . . . . . . . 191 Der UDP-Server . . . . . . . . . . . . . . . . . . . . . . . . . . 194 Pakete an alle Teilnehmer senden: Broadcast . . . . . . . . . 197 Multicast-Sockets . . . . . . . . . . . . . . . . . . . . . . . . . 199

7.4

7.5

7.4.5 UPnP Universal Plug And Play . . . . . . . . . . . . . . . . . 200 Noch ein Wort zur Sicherheit . . . . . . . . . . . . . . . . . . . . . . 204

8 Grasche Benutzerober chen . . . . . . . . . . . . . . . . . . . . . . . . 205 a 8.1 Die grasche Ober che X11 . . . . . . . . . . . . . . . . . . . . . . 205 a 8.2 Das Toolkit GTK+ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 206 8.2.1 GTK 1.2 versus GTK 2.0 . . . . . . . . . . . . . . . . . . . . . 206 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.2.7 8.2.8 GTK-Programme ubersetzen . . . . . . . . . . . . . . . . . . 207 Ein erstes Beispiel . . . . . . . . . . . . . . . . . . . . . . . . . 208 Das Callback-Prinzip . . . . . . . . . . . . . . . . . . . . . . . 210 Schalt chen (Buttons) . . . . . . . . . . . . . . . . . . . . . . 213 a Hinweistexte (Tipps) . . . . . . . . . . . . . . . . . . . . . . . 216 Widgets anordnen . . . . . . . . . . . . . . . . . . . . . . . . 216 Text-Labels . . . . . . . . . . . . . . . . . . . . . . . . . . . . 220

Inhaltsverzeichnis

IX

8.2.9 Dialogfenster . . . . . . . . . . . . . . . . . . . . . . . . . . . 221 8.2.10 Auswahlfelder . . . . . . . . . . . . . . . . . . . . . . . . . . 224 8.2.11 Eingabefelder fur Text und Zahlen . . . . . . . . . . . . . . . 228 8.2.12 Menus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 233 8.2.13 Pixmap-Graken darstellen . . . . . . . . . . . . . . . . . . . 238 8.2.14 Zeichen chen . . . . . . . . . . . . . . . . . . . . . . . . . . 244 a 8.2.15 Zeichen che mit Rollbalken . . . . . . . . . . . . . . . . . . 250 a 8.2.16 Dateiauswahlfenster . . . . . . . . . . . . . . . . . . . . . . . 252 8.2.17 Umlaute und Sonderzeichen . . . . . . . . . . . . . . . . . . 255 8.2.18 Wie geht es weiter? . . . . . . . . . . . . . . . . . . . . . . . . 255 8.3 Grak ohne X11 mit der SVGALIB . . . . . . . . . . . . . . . . . . . 256 8.3.1 Besonderheiten beim Arbeiten mit der libvga . . . . . . . . . 256 8.3.2 8.3.3 8.3.4 8.3.5 8.3.6 Ein erstes Beispiel . . . . . . . . . . . . . . . . . . . . . . . . . 257 Mit Perspektive: 3D-Funktionen zeichnen . . . . . . . . . . . 260 Ein kleines Malprogramm . . . . . . . . . . . . . . . . . . . . 262 Erweiterte Funktionen mit der libvgagl . . . . . . . . . . . . 266 Weitere Informationsquellen . . . . . . . . . . . . . . . . . . 268

9 Hardware-Programmierung . . . . . . . . . . . . . . . . . . . . . . . . . . 271 9.1 Hardware-nahe Programme schreiben . . . . . . . . . . . . . . . . . 271 9.1.1 9.1.2 9.2 Eigene Programme mit root-Rechten ausstatten . . . . . . . . 272 Zugriff auf I/O-Ports freischalten . . . . . . . . . . . . . . . 272

9.1.3 Zugriff auf die I/O-Ports . . . . . . . . . . . . . . . . . . . . 273 Ansteuerung des Parallelports . . . . . . . . . . . . . . . . . . . . . 274 9.2.1 9.2.2 Beschreibung des Parallelports . . . . . . . . . . . . . . . . . 274 Die Adresse des Parallelports suchen . . . . . . . . . . . . . 275

9.3

9.2.3 Ein Beispiel: LED-Lauflicht . . . . . . . . . . . . . . . . . . 276 Modem-Steuerleitungen abfragen . . . . . . . . . . . . . . . . . . . . 279

10 Beispielprojekte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 283 10.1 WebCam: Video-Ubertragung per HTTP . . . . . . . . . . . . . . . . 283 10.1.1 Wie die Bilder laufen lernen . . . . . . . . . . . . . . . . . . . 284 10.1.2 Strukturierung der Quelltexte . . . . . . . . . . . . . . . . . . 284 10.1.3 Die HTTP-Authentizierung . . . . . . . . . . . . . . . . . . 298 10.2 Telefonbuch mit automatischer Anwahl . . . . . . . . . . . . . . . . 300 10.2.1 Ziel des Projektes . . . . . . . . . . . . . . . . . . . . . . . . . 300

X

Inhaltsverzeichnis

10.2.2 Strukturierung des Projektes . . . . . . . . . . . . . . . . . . 301 10.2.3 Das Hauptprogramm . . . . . . . . . . . . . . . . . . . . . . 301 10.2.4 Funktionen zur Ansteuerung des Modems . . . . . . . . . . 304 10.2.5 Die Benutzerschnittstelle . . . . . . . . . . . . . . . . . . . . 307 10.2.6 To Do . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 312 Anhang . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 315 A1 Daten zum Buch im Internet . . . . . . . . . . . . . . . . . . . . . . . 315 A2 Das X11-Toolkit XView . . . . . . . . . . . . . . . . . . . . . . . . . . 315 A3 Aufbau einer WAV-Audiodatei . . . . . . . . . . . . . . . . . . . . . 316 A4 Aufbau einer AU-Audiodatei . . . . . . . . . . . . . . . . . . . . . . 317 A5 Linux-Programmierung unter Windows: Cygwin . . . . . . . . . . 317

Literaturverzeichnis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 319 Stichwortverzeichnis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 321

VorwortSeit der 1. Auflage dieses Buches sind nun fast acht Jahre vergangen. In dieser Zeit hat sich Linux auf verschiedenen Gebieten weiterentwickelt: Fur den An wender ist die Unterstutzung von USB- und anderen Plug&Play-Ger ten hinzu a gekommen, fur den Programmierer hat eine Standardisierung der unterschiedli chen Linux-Distributionen stattgefunden; es wurde die so genannte Linux Standard Base (kurz LSB) entwickelt. Beiden Entwicklungen wird in dieser 4. Auflage Rechnung getragen. S mtliche a Quelltexte wurden an die LSB angepasst und das Kapitel uber die Ansteuerung von Ger ten wurde um das Thema USB erweitert. Daruber hinaus sind in dem a Kapitel Netzwerkprogrammierung Beispiele zu UDP, Broadcast und Multicast erg nzt worden. a Dieses Buch wendet sich sowohl an den Programmier-Einsteiger, der Grundkenntnisse in der Programmiersprache C besitzt, als auch an den fortgeschrit tenen Programmierer, der die vielf ltigen Moglichkeiten des Betriebssystems in a eigenen Programmen nutzen mochte. Dabei geht es vor allem um die Linux spezischen Themen, wie z. B. die Ansteuerung von Devices. Durch zahlreiche einfache Beispielprogramme soll der Einstieg in diese Themen erleichtert werden. Als Programmiersprache kommt ausschlielich ANSI-C zum Einsatz. Haiger, im Fruhjahr 2010 Martin Gr fe a

Kapitel 1

Einfuhrung 1.1 Warum gerade C? Unter Linux ist mittlerweile eine Vielzahl von Programmiersprachen verfugbar, angefangen von Pascal und Fortran uber Skript- oder Interpretersprachen wie TCL und Perl bis hin zu objektorientierten Compilersprachen wie C++ und Java. Jede dieser Programmiersprachen hat ihre Vor- und Nachteile, C kommt jedoch eine besondere Bedeutung zu, da fast das gesamte Betriebssystem in ANSI-C geschrieben ist.1 Mit den Kernel-Quellen stehen dadurch s mtliche fur die systema nahe Programmierung erforderlichen Include-Dateien unter C zur Verfugung. Aus diesem Grund lassen sich die Moglichkeiten des Betriebssystems (und der Hardware) mit C so vollst ndig wie mit keiner anderen Programmiersprache nuta zen.2 Da C eine kompakte, relativ maschinennahe Programmiersprache ist, sind C Programme efzient und schonen die System-Ressourcen. Fur das Ausfuhren der Programme ist weder ein Interpreter noch eine Laufzeitumgebung wie bei Java erforderlich. Trotzdem sind die Quelltexte portabel viele der Beispielprogramme in diesem Buch laufen auch unter kommerziellen Unix-Varianten (z. B. Solaris von Sun oder HPUX von Hewlett Packard) und mit Cygwin3 sogar unter WindowsTM . Mit den Kernel-Quellen und vielen Open Source-Programmen hat der Programmierer Zugriff auf eine nahezu unbegrenzte Menge an Quelltexten, aus denen er sich den einen oder anderen Programmierkniff abschauen kann und darf . Denn in der Welt der offenen Quelltexte muss zum Gluck das Rad nicht immer wieder neu erfunden werden.1

Mit Ausnahme einiger Hardware-naher oder zeitkritischer Programmteile, die in Assembler programmiert wurden. Auer vielleicht in Assembler, was aber keine echte Alternative zu Hochsprachen darstellt. siehe Anhang A5 ab Seite 317

2 3

2

1 Einfuhrung

1.2 Bevor es losgeht . . .. . . mussen die notwendigen Tools und Dateien installiert sein. In der Anfangs zeit von Linux, als man nach der Installation erst einmal den Kernel nach eigenen Wunschen neu kompilierte, waren die Werkzeuge fur die C-Programmierung fester Bestandteil der Linux-Distributionen und wurden in der Regel automatisch mit installiert. Inzwischen ist das Kompilieren des Kernels dank der Modularisierung nicht mehr notwendig, und so werden bei vielen Distributionen die Pakete zur Software-Entwicklung nicht mehr automatisch installiert. Bei einigen auf CD erh ltlichen Distributionen sind diese Pakete nicht einmal mehr enthalten, sona dern mussen aus dem Internet nachgeladen werden. Um die Beispiele in diesem Buch ubersetzen zu konnen, benotigen Sie das Paket mit dem C-Compiler gcc sowie das Programm make. Auerdem werden fur das Einbinden von Funktionsbibliotheken in eigene Programme die zugehorigen Include-Dateien benotigt, die h ug in separaten Paketen enthalten sind. Beispiel: a Die Funktionsbibliothek libncurses wird bei den meisten Distributionen als Vor einstellung installiert. Wenn Sie diese Bibliothek in einem eigenen Programm benutzen wollen, benotigen Sie zus tzlich das zugehorige Entwicklungspaket. Je a nach Distribution heit dieses Paket beispielsweise oderncurses-dev-Version

ncurses-devel-Version. Im Folgenden soll exemplarisch fur die Distributionen SuSE und Ubuntu ge zeigt werden, wie Pakete nachinstalliert werden konnen.

1.2.1 Paketverwaltung unter SuSE-LinuxIn der Linux-Distribution von SuSE1 ist das Tool YaST (Abkurzung fur Yet Another Setup Tool) enthalten, das fur alle wichtigen Systemeinstellungen zust ndig ist. Dieses kann entweder aus dem Menu des Windowmanagers oder a als Benutzer root aus einer Shell mit dem Kommando /sbin/yast2 aufgeru fen werden. Durch einen Klick auf das Icon Software installieren oder loschen in der Rubrik Software wird die Paketverwaltung geoffnet (siehe Abbildung 1.1). Findet man das gesuchte Paket nicht unter den angezeigten Paketgruppen, kann man den Filter (oben links) auf Suche umstellen und ein entsprechendes Stichwort eingeben. Die Pakete der Distribution von SuSE liegen im RPM-Format (Abkurzung fur RedHat Packet Manager) vor. Daher konnen einzelne Pakete auch mit dem Be fehl rpm -i Paket-Datei installiert werden. 1

SuSE steht fur Software- und Systementwicklung. So heit das kleine Unternehmen, das diese Dis tribution zusammenstellt und inzwischen von der Firma Novell aufgekauft wurde.

1.2 Bevor es losgeht . . .

3

Abbildung 1.1: Die Paketverwaltung unter SuSE mit YaST

4

1 Einfuhrung

1.2.2 Paketinstallation bei UbuntuUbuntu-Linux ist von der Linux-Distribution Debian abgeleitet und verwendet die gleichen Pakete. Anders als bei SuSE sind die Pakete daher im Debian-eigenen DEB-Format. Fur die Auswahl und Installation der Pakete bringt der GNOME Desktop unter Ubuntu ein spezielles Tool mit, das uber das GNOME-Menu auf gerufen werden kann (siehe Abbildung 1.2).

Abbildung 1.2: Offnen der GNOME-Paketverwaltung bei Ubuntu-Linux

In der Startansicht bietet das Werkzeug nur das Installieren oder Entfernen gan zer Anwendungen an, ohne die einzelnen Pakete im Detail aufzulisten. Uber den Menupunkt Datei/Erweitert l sst sich die Darstellung erweitern, sodass a die einzelnen Pakete aufgelistet werden konnen (siehe Abbildung 1.3). Ahnlich wie bei YaST unter SuSE-Linux ist auch hier die Moglichkeit gegeben, nach be stimmten Paketen anhand von Stichworten zu suchen (Schalt che Suche unten a links). Wenn Sie Pakete von Hand installieren wollen, so gibt es dafur bei Ubuntu zwei Kommandozeilenprogramme: apt-get und dbkg: sudo dpkg -i Paket-Datei sudo apt-get install Paket-Datei

1.2 Bevor es losgeht . . .

5

Abbildung 1.3: Erweiterte Ansicht fur die GNOME-Paketverwaltung

6

1 Einfuhrung

1.3 Die WerkzeugeIn diesem Abschnitt stellen wir in kurzer Form die fur das Programmieren erfor derlichen Werkzeuge vor. In Abschnitt 1.4 wird der Umgang mit diesen Werkzeugen dann anhand von Beispielen erl utert. Den Schwerpunkt bilden dabei die fur a Unix und Linux ublichen Kommandozeilen-Tools. Den Umgang mit einer inte grierten Entwicklungsumgebung beschreibt Kapitel 2.

1.3.1 Der Editor die Qual der WahlUm ein Programm zu schreiben, benotigt man naturlich zun chst einen Editor, a mit dem man den Quelltext eingibt. Bei Verwendung einer integrierten Entwicklungsumgebung (siehe Abschnitt 1.3.5) ist bereits ein solcher Editor in diese Umgebung eingebaut, doch viele Programmierer verwenden stattdessen ihren Lieb lingseditor wovon es unter Linux eine ganze Menge gibt. Dabei kann man zwei Kategorien unterscheiden: Editoren, die auf der Konsole bzw. in einem Terminalfenster (wie XTerm) laufen, und Editoren, die uber eine eigene grasche Be nutzerober che verfugen. Letztere sind in der Regel komfortabler, weil sie uber a Syntax-Highlighting verfugen, benotigen aber auch weit mehr Ressourcen. Editoren fur die Textkonsole: vim (steht fur VI improved) emacs pico joe jedit ... Editoren mit grascher Ober che: a kate (Bestandteil von KDE) gedit (Bestandteil von GNOME) nedit xemacs ... Der Editor kate bietet auerdem die Moglichkeit, C-Funktionen einzuklap pen, um den Quelltext ubersichtlicher darzustellen (siehe Abbildung 1.4). Nicht alle hier erw hnten Editoren sind in jeder Linux-Distribution enthalten. a Ggf. mussen die zugehorigen Pakete erst aus dem Internet geladen und gem a Abschnitt 1.2 installiert werden.

1.3 Die Werkzeuge

7

Abbildung 1.4: Zwei Editoren fur Programmierer: nedit und kate

8

1 Einfuhrung

1.3.2 Der GNU C-Compiler gccKernstuck der Software-Entwicklung ist der C-Compiler selbst, also der gcc bzw. g++ (fur C++-Programme). Der gcc ist ein so genannter Cross-Compiler, mit dem man im Grunde auch Programme fur andere Betriebssysteme oder Hardware Plattformen (also andere Prozessoren) entwickeln kann. Der einfachste Aufruf des Compilers lautet:gcc Quelltext

So aufgerufen, wird der Quelltext kompiliert, assembliert und gelinkt, sodass ein ausfuhrbares Programm entsteht. Dieses Programm wird voreingestellt unter dem Dateinamen a.out abgespeichert. In der Regel wird man diese Voreinstel lung nicht verwenden, sondern einen anderen, zweckm igeren Namen w hlen a a 1 Dies geschieht mit Hilfe der Option -o: wollen. gcc Quelltext -o Ausgabedatei

Beispiel:gcc hello.c -o hello

Bei diesem Aufruf fuhrt der gcc zwei Schritte durch: das eigentliche Kompilieren (Ubersetzen) und das Linken zu einem ausfuhrbaren Programm. Letzterer sorgt z.B. dafur, dass Funktionsaufrufe wie printf() mit den entsprechenden Funk tionen aus der dynamischen Bibliothek libc verknupft werden. Wird ein Pro gramm auf mehrere Quelltexte aufgeteilt, so mussen die einzelnen Programmtei le zun chst nur ubersetzt werden, ohne den Linker aufzurufen. Dazu wird beim a Ubersetzen die Option -c angegeben. Der Compiler erzeugt in diesem Fall nur eine Objektdatei, die automatisch die Endung .o erh lt. Ein Beispiel hierzu na det sich in Abschnitt 1.4.5. Der gcc besitzt eine Vielzahl weiterer Optionen, von denen wir in diesem Buch nur einen kleinen Teil benotigen. Eine vollst ndige Beschreibung erh lt man mit a a man gcc. Die Reihenfolge der Parameter und Optionen ist beim gcc bis auf wenige Ausnahmen beliebig.

1.3.3 Ablaufsteuerung mit GNU make Fur das Ubersetzen kleinerer Programme benotigt man in der Regel nur den C Compiler wie im vorherigen Abschnitt beschrieben. Bei umfangreicheren Projekten sollten Sie den Quelltext in mehrere Teile zerlegen. Dadurch werden die Da teien nicht nur ubersichtlicher, es ist dann auch moglich, bei Anderungen nur die jenigen Dateien neu zu ubersetzen, die ge ndert wurden. Genau hier setzt das a1

Sie sollten fur erste Versuche nicht den Namen test w hlen, da ein gleichnamiges Programm schon a Bestandteil der Shell ist!

1.3 Die Werkzeuge

9

Programm make an. Es pruft, ob sich die Quellen eines Programmteils ge ndert a haben, und ubersetzt diesen Teil dann neu. Das Tool make benotigt dazu eine Datei, in der die Abh ngigkeiten der Quell a und Zieldateien und die Anweisungen (Compiler-Aufrufe) eingetragen sind. Ein Eintrag in dieser Datei, dem so genannten Makele, sieht wie folgt aus: Zieldatei: Quelldatei1 Quelldatei2 . . . Anweisung1 Anweisung2 ...hello.c gcc hello.c -o hello

Beispiel:hello:

Alle Anweisungszeilen mussen mit einem oder mehreren Tabulatoren ( echte Tabs, keine Leerzeichen!) eingeruckt sein, w hrend die Zieldatei immer am Zei a lenanfang stehen muss. Eine solche Make-Datei kann beliebig viele Zieldateien mit den zugehorigen Quelldateien und Anweisungen enthalten. Zur Veranschau lichung sei auf das Beispiel in Abschnitt 1.4.5 verwiesen. Wird die Make-Datei Makele oder makele genannt, so kann make ohne Parameter aufgerufen werden. Andernfalls lautet der Aufruf:make -f Make-Datei

Sind in dem Makele mehrere Zieldateien angegeben, kann durch die Eingabe vonmake Zieldatei

gezielt eine dieser Dateien erzeugt werden, wobei make auch hier automatisch die Abh ngigkeiten pruft und ggf. weitere, fur die angegebene Zieldatei erforderliche a Dateien neu erzeugt. Ohne Angabe der Zieldatei wird immer die erste Datei im Makele erzeugt. An dieser Stelle sei noch darauf hingewiesen, dass es sich bei dem Ziel nicht unbedingt um eine Datei handeln muss. So ndet sich in Makeles h ug ein Eintrag a der folgenden Form:clean: rm -f *.o

Mit dem Aufruf make clean werden dann Objekt-Dateien, die man nicht mehr benotigt, geloscht. Man beachte, dass hier keine Quelldateien angegeben sind, was dazu fuhrt, dass die Anweisung immer ausgefuhrt wird. Fur eine ausfuhrliche Anleitung siehe auch man make.

10

1 Einfuhrung

1.3.4 Fur die Fehlersuche: Die Debugger Nur selten l uft ein Programm auf Anhieb einwandfrei. Schnell schleichen sich a Fehler ein, im Programmiererjargon Bugs (Wanzen1 ) genannt. Zur Lokalisie rung und Beseitigung der Bugs greift man zu einem Debugger (Entwanzer). Unter Linux hat der Programmierer in die Wahl zwischen dem textbasierten GNU Debugger gdb dem Urvater der Debugger unter Linux und verschiede nen graschen Front-Ends. Ursprunglich gab es eine relativ rudiment re gra a sche Ober che fur den GNU Debugger namens xxgdb. Dieses Projekt wura de aber vor geraumer Zeit durch ein von Grund auf neu gestaltetes Tool ersetzt, den DDD (Abkurzung fur Data Display Debugger, siehe Abbildung 1.5). Der DDD ist kein eigenst ndiger Debugger sondern eine grasche Benutzerober che a a fur den GNU Debugger gdb. Das Tool wurde ubrigens in Deutschland entwickelt!

Abbildung 1.5: Ein elektronischer Kammerj ger: der DDD a

Um einen Fehler in einem Programm mit Hilfe des Debuggers zu nden, muss das Programm Zusatzinformationen enthalten, mit deren Hilfe der Debugger das1

Dieser Ausdruck stammt noch aus der Zeit der Relais-Computer. Hier hatte sich einmal eine Wanze zwischen die Relaiskontakte verirrt und dadurch Rechenfehler verursacht.

1.3 Die Werkzeuge

11

ausfuhrbare Programm mit dem zugehorigen Quelltext in Verbindung bringen kann. Diese Zusatzinformationen fugt der gcc mit Hilfe der Option -g ein: gcc -g hello.c -o hello

Anschlieend kann der Debugger aufgerufen werden, z. B.:ddd hello

Hier konnen Sie nun so genannte Breakpoints setzen, das Programm schrittwei se ausfuhren und den Inhalt von Variablen anzeigen. Kommt es zur Laufzeit des Programms zu einem Fehler, der die Ausfuhrung sofort abbricht beispielsweise eine Speicher-Zugriffsverletzung (Segmentation fault) oder eine Division durch null , so zeigt der Debugger die entsprechende Zeile im Quelltext an, die zu diesem Fehler gefuhrt hat. Ubrigens: nach erfolgreicher Fehlerbeseitigung lassen sich die fur die Ausfuhrung des Programms nicht notwendigen Debug-Zusatzinformationen mitstrip hello

wieder entfernen, ohne das Programm neu zu ubersetzen. Dadurch reduziert sich die Groe des Programms offtmals erheblich.

1.3.5 Integrierte EntwicklungsumgebungenAls Alternative zur direkten Verwendung der bisher vorgestellten Werkzeuge gibt es die Moglichkeit, mit einer integrierten Entwicklungsumgebung1 zu arbei ten. Dabei handelt es sich um ein Programm, das neben einem Quelltext-Editor auch eine grasche Schnittstelle fur Compiler, Debugger usw. bietet. Insbesonde re Umsteiger aus der Windows-Welt nden mit Hilfe solcher Programme h ug a leichter den Einstieg in die Linux-Programmierung. Man sollte jedoch beachten, dass Entwicklungsumgebungen keine Compiler, sondern eben nur Umgebungen sind und zum Ubersetzen und Linken des Quelltextes wieder auf den C-Compiler gcc zuruckgreifen. Der Vorteil solcher Programme ist, dass das Wechseln zwischen den Werkzeugen Editor, Compiler, Debugger usw. entf llt. Tritt beispielsweise beim Ubersetzen des a Programms ein Fehler auf, wird automatisch die entsprechende Zeile im Quelltext markiert.

1

auch als IDE fur Integrated Development Environment bezeichnet

12

1 Einfuhrung

Abbildung 1.6: KDevelop Entwicklungsumgebung des KDE

Abbildung 1.7: Das Entwicklungs-Framework Eclipse

1.3 Die Werkzeuge

13

Abbildung 1.8: Die GNOME-Entwicklungsumgebung Anjuta

Abbildung 1.9: Entwicklungsumgebung a la Turbo-Pascal: xwpe `

14

1 Einfuhrung

Unter Linux sind verschiedene Entwicklungsumgebungen frei verfugbar; einige dieser Linux-IDEs sind: KDevelop (Entwicklungsumgebung des KDE), siehe Abbildung 1.6 Eclipse + C Development Tooling (kurz CDT), siehe Abbildung 1.7 Anjuta (Entwicklungswerkzeug des GNOME-Projektes), siehe Abbildung 1.8xwpe (X-Window Programming Environment)

Alle vier Programme sind mausgesteuert, menugefuhrt und mit einer mehr oder weniger umfangreichen Online-Hilfe ausgestattet, die uber den entsprechenden Menupunkt aufgerufen werden kann. Die aktuellen Versionen von KDevelop, Eclipse (inkl. CDT) und Anjuta sind recht umfangreich und benotigen mehr als 50 MByte. KDevelop bringt es mit den zu gehorigen Dokumentationspaketen sogar auf mehrere 100 MByte ein Grund, bei nicht so leistungsstarken Rechnern doch auf die schlanken Kommandozeilen Programme zuruckzugreifen. Eine Ausnahme bildet das nicht so bekannte Tool xwpe (Abbildung 1.9), das deutlich weniger Ressourcen benotigt, aber auch nicht so komfortabel ist wie bei spielsweise KDevelop. Die Entwicklungsumgebung Eclipse f llt etwas aus der Reihe: Das in Java a programmierte Tool bildet eine Art Framework. Fur die Entwicklung von Java-Programmen wird zus tzlich das Paket JDT (Java Development Tooling) a benotigt, fur C-Programme entsprechend das Paket CDT. In Kapitel 2 beschreiben wir die Arbeit mit einer integrierten Entwicklungsumgebung anhand der Programme Anjuta und KDevelop eingehender.

1.4 Der Umgang mit Compiler, Debugger und make anhand von Beispielen H ug sagt ein Beispiel mehr als tausend Worte; auch bei der Beschreibung von a Programmierwerkzeugen ist das nicht anders. Aus diesem Grund wird in den folgenden Abschnitten die Handhabung der zuvor beschriebenen Werkzeuge anhand einfacher Beispiele demonstriert.

1.4.1 Primzahlen berechnenDas folgende kleine C-Programm berechnet die Primzahlen von 1 bis 100, Zahlen also, die sich nur durch 1 und sich selbst teilen lassen.1 Es soll im Folgenden dazu dienen, das Ubersetzen und die Fehlersuche mit den bereits vorgestellten Programmierwerkzeugen zu verdeutlichen.1

Mathematisch exakt betrachtet, sind Primzahlen diejenigen Zahlen, die genau zwei Teiler besitzen.

1.4 Der Umgang mit Compiler, Debugger und make anhand von Beispielen

15

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28

/* primzahl.c */ # include int ist_primzahl(int zahl) { int teiler=2; while (teiler*teiler = 0) switch (option) { case h : printf("Usage: %s [-o output-file] " "[input-file ...]\n", argv[0]); return(0); case o : out_filename = optarg; break; case ? : return(1); /* unbekannte Option */

3.1 Parameter und Ruckgabewert der Funktion main()

47

22 23 24 25 26 27 28 29 30 31

} for (i=optind; i cp primzahl.c cp: Fehlende Zieldatei u Versuchen Sie cp --help fr weitere Informationen.

Die Fehlermeldung gibt hier nicht nur unmissverst ndlich den Grund des Fehlers a an, sondern auch den Namen des Programms selbst. Zus tzlich wird sogar auf a die eingebaute Hilfe-Option verwiesen.Fehlermeldungen sollten immer an den Fehlerausgabekanal (stderr, siehe auch Kapitel 4) geleitet werden, damit auch beim Umleiten der Standardausgabe in eine Datei mittels > der Anwender die Fehlermeldung sofort zu sehen bekommt.

Fur gewohnlich werden in C-Programmen Fehlermeldungen mit Anweisungen der Formfprintf(stderr, "%s: Too many arguments.\n", argv[0]);

ausgegeben. Optional kann der Programmname (argv[0]) mit Hilfe der Funktion basename(argv[0]) um Pfadangaben bereinigt werden, sodass z.B. statt /home/martin/bin/my prog: Too many arguments. hier nur my prog: Too many arguments. ausgegeben wird. Diese Funktion ist in der IncludeDatei string.h deklariert. Bei Fehlermeldungen im Zusammenhang mit dem Offnen, Lesen oder Schreiben von Dateien ist es auerdem sinnvoll, die Funktion perror() aufzurufen, um den genauen Grund des Dateizugrifffehlers anzugeben:

3.2 Konventionen fur Kommandozeilenprogramme

51

void perror(const char *s);

Diese Funktion gibt die als Parameter angegebene Zeichenkette in den StandardFehlerkanal aus, fugt einen Doppelpunkt an und gibt dahinter die Beschreibung der Fehlerursache z.B. Datei oder Verzeichnis nicht gefunden aus. Wird der Funktion perror() eine leere Zeichenkette ubergeben, so erfolgt nur die Ausgabe der Fehlerursache, ein Doppelpunkt wird in diesem Fall nicht vorangestellt. Soll die Fehlerursache nicht (nur) ausgegeben, sondern z.B. bei Programmen mit grascher Bedienober che in einem Fenster dargestellt werden, so kann der a Fehlertext auch uber die Funktion strerror() ermittelt werden. Als Parameter mussen Sie hier die globale Variable errno angeben: # include # include char *error_text = strerror(errno);

Wie bereits in Abschnitt 3.1.1 beschrieben, sollte im Falle eines Fehlers, der zum Abbruch des Programms fuhrt, ein entsprechender Ruckgabewert = 0 gesetzt werden. Tritt der Fehler nicht direkt in main(), sondern innerhalb einer Unterfunktion auf, so kann dies mit der Funktionexit(Rckgabewert); u

geschehen.

3.2.3 Eigene Manpages erstellenZu einem guten Programm gehort auch eine man-Hilfeseite oder auch Man page. Solche Dateien nden sich in der Regel in dem Verzeichnis /usr/man oder /usr/local/man. In der Umgebungsvariablen MANPATH sind die Ver zeichnisse eingetragen, die das Programm man durchsucht. Zu dem vollst ndia gen Pfad gehort das Unterverzeichnis, dessen Name sich aus man + Sekti onsnummer zusammensetzt. So ndet sich z. B. der Hilfetext zum Shell-Befehl sleep in Sektion 1 unter: /usr/man/man1/sleep.1

Am Ende des Dateinamens ist noch einmal die entsprechende Sektionsnummer angeh ngt. H ug sind die Dateien mit gzip komprimiert, was an der Endung a a .gz zu erkennen ist. Manpages sind reine Textdateien und konnen mit jedem Editor erstellt werden. Neben dem eigentlichen Text enthalten die Dateien Steuersequenzen, die u.a. die

52

3 Kommandozeilenprogramme

Formatierung beeinussen. Sie mussen am Zeilenanfang stehen und beginnen mit einem .. Des Weiteren gibt es einige Steuersequenzen, die mit \ beginnen und z. B. Texthervorhebungen bewirken. Diese Sequenzen mussen nicht zwingend am Zeilenanfang stehen. Die wichtigsten fasst Tabelle 3.1 zusammen.Tabelle 3.1: Beschreibung einiger Steuersequenzen fur man-Hilfeseiten Sequenz.\" .TH .SH .fi .nf \fR \fB \fI \-

Beschreibung Kommentarzeile Titel, Kopf- und Fuzeile festlegen Uberschrift fur den n chsten Abschnitt a ab hier: Blocksatz ab hier: keine Formatierung normaler Text Fettschrift kursiv (oder unterstrichen) Gedankenstrich

Der Sequenz fur Titel, Kopf- und Fuzeile konnen bis zu funf Angaben folgen: .TH "Name" "Sektion" "Datum" "Author / Version" "Thema"

Beispiel:.TH SLEEP 3 "Apr. 1993" GNU "Linux Programmers Manual"

Die Steuerungssequenz fur eine Uberschrift benotigt als einzige Angabe den Uberschrift-Text. Als Beispiel soll hier eine kurze Manpage fur das Primzahl Programm aus Kapitel 1 dienen: 1 2 3 4 5 6 7 8 9 10 11 12 13 14.\" primzahl.1 .TH primzahl 1 "Feb. 2002" "M. Grfe" "C und Linux" a .SH NAME primzahl \- Primzahlen von 1 bis 100 berechnen. .SH SYNTAX primzahl .SH BESCHREIBUNG .fi Das Programm \fBprimzahl\fR berechnet alle Primzahlen zwischen 1 und 100. Es dient als Beispielprogramm fr u das erste Kapitel des Buches "\fIC und Linux\fR". .nf .SH AUTOR Martin Grfe ([email protected]) a

Als primzahl.1 abgespeichert l sst sich diese Hilfeseite mit a man -l primzahl.1

3.3 Programme mehrsprachig auslegen

53

formatieren und anzeigen (Abbildung 3.2). Die Option -l ermoglicht es, direkt den Dateinamen der Manpage anzugeben, die dargestellt werden soll. Ist die Manpage fertiggestellt, kann sie mit gzip komprimiert und in das entsprechende Verzeichnis (z.B.: /usr/local/man/man1) kopiert werden. Danach l sst sie sich a jederzeit mitman primzahl

anzeigen.

Abbildung 3.2: Ergebnis der selbst geschriebenen Manpage

3.3 Programme mehrsprachig auslegenDie Zeiten, in denen alle Computer-Programme und auch die Betriebssysteme ausschlielich in Englisch waren, sind l ngst vorbei. Auch die aktuellen a Linux-Distributionen pr sentieren sich hierzulande in deutschem Gewand, a d. h. Menus und Dialog-Boxen sind zum Groteil deutschsprachig. Die Unterstutzung l nderabh ngiger Formate wird bei Linux unter dem Begriff a a locale zusammengefasst. Das gleichnamige Shell-Programm liefert Informationen zu den aktuellen Einstellungen:

54

3 Kommandozeilenprogramme

> locale LANG=de DE LC CTYPE=de DE LC NUMERIC=de DE LC TIME=de DE LC COLLATE=de DE LC MONETARY=de DE LC MESSAGES=de DE LC ALL=

Mit Hilfe der Umgebungsvariablen LANG kann allgemein die Auswahl der Lan dessprache erfolgen, z.B.:> export LANG=en US

Das Kurzel en steht hier fur englisch, die Erweiterung US kennzeichnet, dass es sich um nordamerikanisches Englisch handelt. Diese Angabe kann noch um die Spezikation des ISO-Zeichensatzes erg nzt werden, z.B.: a> export LANG=de DE.ISO-8859-1

Die von Linux unterstutzten L ndereinstellungen ndet man als Verzeichnisse a unter dem Pfad /usr/share/locale. Die Auswahl der Sprach-/L ndereinstellung erfolgt in C-Programmen mit Hilfe a der Funktion setlocale():char *setlocale(int category, char *locale);

wobei category eine der Kategorien LC COLLATE, LC CTYPE, LC MESSAGES, LC MONETARY, LC NUMERIC, LC TIME oder LC ALL fur alle Kategorien sein muss. Der Parameter locale muss entweder eine gultige L nderkennzeichnung wie a a de DE enthalten oder eine leere Zeichenkette ("") sein, wenn die L nderkenn zeichnung von der Umgebungsvariablen LANG ubernommen werden soll. Das folgende kleine Beispiel soll die Wirkung des setlocale()-Aufrufs verdeutlichen: 1 2 3 4 5 6 7 8/* sprache.c */ # include # include int main()

3.3 Programme mehrsprachig auslegen

55

9 10 11 12 13 14 15 16

{ setlocale(LC_NUMERIC, "en_US"); printf("PI=%.3f\n", 3.141593); setlocale(LC_NUMERIC, "de_DE"); printf("PI=%.3f\n", 3.141593); return(0); }

Nach dem Ubersetzen mit gcc sprache.c -o sprache kann das Programm aufgerufen werden:> sprache PI=3.142 PI=3,142

Offensichtlich fuhrt die gleiche printf()-Anweisung in Zeile 13 des Quelltextes zu einem anderen Ergebnis als in Zeile 11. Das Dezimal-Trennzeichen wird in die sem Beispiel von . auf , umgeschaltet. Erreicht wird das durch Andern der Locale Category LC NUMERIC, die wie der Name schon sagt das Zahlenformat bei Ein- und Ausgaben steuert.Auf manchen Systemen wird die Funktionsbibliothek libintl nicht automatisch eingebunden, so dass das Einbinden der Funktionen zur Internationalisierung von Programmen zu einer Fehlermeldung fuhrt. In diesem Fall muss beim Ubersetzen des Quelltextes die Bibliothek explizit angegeben werden, also z. B. gcc sprache.c -lintl -o sprache.

Anstatt die L ndereinstellung wie in dem Beispiel explizit vorzugeben, sollten a Programme diejenige Einstellung verwenden, die der Benutzer mit Hilfe der Umgebungsvariable LANG voreingestellt hat, indem als Parameter locale eine leere Zeichenkette angegeben wird:setlocale(LC_ALL, "");

Die Funktion setlocale() liefert als Ruckgabewert einen Zeiger auf die Zei chenkette mit der entsprechenden Landeskennung. Alle von der libc generierten Meldungen, z.B. mit Hilfe der Funktion perror(), werden automatisch in der durch die Umgebungsvariable LANG vorgegebenen Sprache ausgegeben. Doch wie kann man das Gleiche fur Meldungen erreichen, die mittels printf() oder fprintf() ausgegeben werden? Dies ist Aufgabe der C-Funktionen gettext() und dgettext():

56

3 Kommandozeilenprogramme

char *gettext(char *msgid); char *dgettext(char *textdomain, char *msgid);

Beide Funktionen dienen dazu, eine Meldung, hier als msgid bezeichnet, in die Landessprache gem LC MESSAGES zu ubersetzen. Naturlich handelt es sich a hierbei nicht um echte Ubersetzungsfunktionen, vielmehr muss der Programmierer zuvor die ubersetzten Texte in einer separaten Datei abgelegt haben. Die Texte werden nach der Textdomain gegliedert, in der Regel handelt es sich dabei um den Programmnamen selbst. Doch schauen wir uns zun chst das folgende kleine Proa gramm an: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15/* sprache2.c */ # include # include # include int main() { setlocale(LC_MESSAGES, ""); printf("%s\n", dgettext("grep", "out of memory")); return(0); }

Nach dem Ubersetzen mit gcc sprache2.c -o sprache2 kann das Pro gramm mit verschiedenen Einstellungen fur die Umgebungsvariable LANG gestar tet werden:1> export LANG=de DE > sprache2 Speicher ist alle. > export LANG=en US > sprache2 out of memory > export LANG=fr FR > sprache2 Mmoire puise. e e e

Das Programm greift mit der Anweisung1

Sollte dieses Beispiel auf Ihrem System immer nur die Meldung out of memory liefern, so stellen Sie bitte sicher, dass das Programm grep installiert ist.

3.3 Programme mehrsprachig auslegen

57

dgettext("grep", "out of memory")

auf die Textdomain grep zu und damit auf die Ubersetzungsdateien des Programms grep. Diese Dateien werden nach der Meldung (msgid) out of memory durchsucht, und wenn diese Meldung gefunden wird, liefert dgettext() das Pendant in der eingestellten Sprache. Findet dgettext() die Meldung in der entsprechenden Ubersetzungdatei nicht, gibt die Funktion einen Zeiger auf die Meldung selbst (also auf msgid) zuruck. Daher sollte als msgid im mer der entsprechende Text in Englisch verwendet werden, sodass bei einer nicht unterstutzten Sprache englische Meldungen erscheinen. Bei den Ubersetzungsdateien, den so genannten Message-Object-Dateien, handelt es sich um spezielle Bin rdateien, die fur gewohnlich unter a /usr/share/locale/L nderkennung/LC MESSAGES/Textdomain.mo a

stehen, also z.B.:/usr/share/locale/de/LC MESSAGES/grep.mo

Wird als L nderkennung de DE angegeben, sucht das System zun chst im enta a sprechenden Verzeichnis nach der betreffenden Message-Object-Datei. Bleibt die Suche ohne Erfolg, wird automatisch die L nderkennung auf de reduziert und a erneut versucht, den entsprechenden Pfad zu offnen. Auf diese Weise l sst sich a a z.B. fur schweizerisches Deutsch de CH ein anderer Text einstellen, w hrend fur andere Dialekte (z.B. de AT fur Osterreich), fur die keine speziellen Meldungen vorgesehen sind, auf die Datei unter der ubergeordneten L nderkennung de a zugegriffen wird. Bitte verstehen Sie das Beispielprogramm nur als Test! Sie sollten mit eigenen Programmen nicht die Message-Object-Dateien anderer Programme nutzen. Message-Object-Dateien erstellenFur das Erstellen eigener Message-Object-Dateien sind die Pakete gettext und gettext-devel erforderlich. Stellen Sie sicher, dass diese Pakete installiert sind.

Als Beispiel fur die Erzeugung und Verwendung eigener Message-Object-Dateien soll das kleine Programm sprache3 dienen, das abh ngig von der eingestellten a Landessprache die Meldung Hello world! bzw. Hallo Welt! ausgibt:

58

3 Kommandozeilenprogramme

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

/* sprache3.c */ # include # include # include int main() { bindtextdomain("sprache3", "."); textdomain("sprache3"); setlocale(LC_MESSAGES, ""); printf("%s\n", gettext("Hello world!")); printf("%s\n", gettext("This is a test.")); return(0); }

Neben den bereits zuvor erl uterten Aufrufen der Funktion setlocale() und a gettext() nden sich hier die Funktionen bindtextdomain() (Zeile 11) und textdomain() (Zeile 12). Mit der bindtextdomain()-Anweisung wird hier festgelegt, dass die Message-Object-Dateien zur Textdomain sprache3 nicht unter /usr/share/locale, sondern im aktuellen Verzeichnis ( .) zu nden sind. Die Anweisung textdomain("sprache3"); deniert sprache3 als aktuelle Textdomain fur alle folgenden gettext()-Aufrufe. Mit Hilfe des Programms xgettext konnen aus dem Quelltext alle Aufrufe von gettext() und dgettext() in eine Portable-Message-Datei extrahiert werden:xgettext -o sprache3.po sprache3.c

Das Programm xgettext erzeugt daraufhin die (editierbare) Portable-MessageDatei sprache.po mit folgendem Inhalt: 1 # SOME DESCRIPTIVE TITLE. 2 # Copyright (C) YEAR THE PACKAGES COPYRIGHT HOLDER 3 # This file is distributed under the same license as the PA 4 # FIRST AUTHOR , YEAR. 5 # 6 #, fuzzy 7 msgid "" 8 msgstr "" 9 "Project-Id-Version: PACKAGE VERSION\n" 10 "Report-Msgid-Bugs-To: \n" 11 "POT-Creation-Date: 2010-01-31 14:58+0100\n"

3.3 Programme mehrsprachig auslegen

59

12 13 14 15 16 17 18 19 20 21 22 23 24 25

"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=CHARSET\n" "Content-Transfer-Encoding: 8bit\n" #: Quelltexte/Kapitel3/sprache3.c:14 msgid "Hello world!" msgstr "" #: Quelltexte/Kapitel3/sprache3.c:15 msgid "This is a test." msgstr ""

In den ersten Zeilen dieser Datei sind einige Kommentare vorbereitet, unter anderem zum Autor und Erstellungsjahr. Hier konnen Sie bei echten Software Projekten Ihre Daten eintragen. Ab Zeile 9 folgen automatisch generierte Informationen zur Datei. Wichtig sind hier die Zeilen 16 und 17, die die Codierung der Datei angeben. In Zeile 16 sollten Sie als Zeichensatz ISO-8859-1 eintragen, also 16 "Content-Type: text/plain; charset=ISO-8859-1\n" Was jetzt noch fehlt, ist die eigentliche Ubersetzung der Textmeldungen. Dazu mussen Sie hinter dem Schlusselwort msgstr in den Zeilen 21 und 25 jeweils die Ubersetzung der Texte in den vorangegangenen Zeilen eintragen, fur Deutsch z. B.: 19 20 21 22 23 24 25#: Quelltexte/Kapitel3/sprache3.c:14 msgid "Hello world!" msgstr "Hallo Welt!" #: Quelltexte/Kapitel3/sprache3.c:15 msgid "This is a test." msgstr "Dies ist ein Test."

Aus der Portable-Message-Datei kann nun mit Hilfe des Programms msgfmt die Message-Object-Datei sprache.mo generiert und im vorgesehenen Ver zeichnis abgelegt werden:> > > > msgfmt -o sprache3.mo sprache3.po mkdir de mkdir de/LC MESSAGES mv sprache3.mo de/LC MESSAGES

Danach spricht das Programm sprache3 deutsch (und englisch):

60

3 Kommandozeilenprogramme

> export LANG=de DE > sprache3 Hallo Welt! Dies ist ein Test. > export LANG=en US > sprache3 Hello world! This is a test.

Wird ein Programm korrekt installiert, sollten die zugehorigen MO-Dateien in die entsprechenden Verzeichnisse des Linux-Systems kopiert werden, also z. B./usr/share/locale/L nderkennung/LC MESSAGES/ a

Dann kann der bindtextdomain()-Aufruf entfallen ( sprache3.c, Zeile 11).

3.4 Ausgabesteuerung im Terminal-FensterIn den bisherigen Beispielen wurden Textausgaben im Terminal-Fenster durch einfache printf()-Anweisungen erreicht. Manchmal ist es jedoch von Vorteil, bestimmte Ausgaben hervorzuheben, z.B. durch Fettschrift oder durch Verwendung einer anderen Schriftfarbe. Auch das Positionieren von Ausgaben an bestimmten Stellen innerhalb des Terminal-Fensters kann hilfreich sein. In den folgenden Abschnitten werden zwei Moglichkeiten aufgezeigt, eine solche Ausga besteuerung innerhalb eines Terminal-Fensters, d.h. ohne Verwendung einer graschen Benutzerschnittstelle, zu erreichen.

3.4.1 ANSI-SteuersequenzenEine sehr einfache Moglichkeit, Textausgaben hervorzuheben und zu positionie ren, ist die Verwendung von Steuersequenzen nach dem ANSI-Standard. Es handelt sich dabei insofern um einen Standard, als auch Drucker sowie andere Betriebssysteme beispielsweise DOS bei Verwendung des Treibers ANSI.SYS die Sequenzen (zumindest teilweise) verstehen. Diese Steuersequenzen werden fast ausschlielich mit einem ESC-Zeichen (ASCII-Code 27dez bzw. 33okt ) eingeleitet, weshalb sie auch als Escape-Sequenzen bezeichnet werden. Als Drucker nur bedingt grakf hig waren, stellten diese Sea quenzen die einzige Moglichkeit dar, Textbereiche zu unterstreichen, in Fett oder Kursiv zu drucken oder die Schriftart zu wechseln. Tabelle 3.2 zeigt eine Auswahl moglicher ESC-Sequenzen, die jedoch nicht alle von s mtlichen Terminalprogrammen unterstutzt werden. Diese Steuersequenzen a lassen sich direkt mit einer printf()-Anweisung ausgeben, z. B.:printf("\033[31mDieser Text ist rot.\033[0m\n");

3.4 Ausgabesteuerung im Terminal-Fenster Tabelle 3.2: ANSI-Steuersequenzen fur die Terminal-/Druckerausgabe ESC-Sequenz\033[m \033[0m \033[1m \033[4m \033[30m \033[31m \033[32m \033[33m \033[34m \033[35m \033[36m \033[40m \033[41m \033[42m \033[43m \033[44m \033[45m \033[46m \033[SpalteG \033[ZeileH \007 \011 \014

61

Beschreibung normaler Text normaler Text Fettschrift unterstreichen Schriftfarbe Schwarz Schriftfarbe Rot Schriftfarbe Grun Schriftfarbe Gelb Schriftfarbe Blau Schriftfarbe Violett Schriftfarbe Turkis Hintergrundfarbe Schwarz Hintergrundfarbe Rot Hintergrundfarbe Grun Hintergrundfarbe Gelb Hintergrundfarbe Blau Hintergrundfarbe Violett Hintergrundfarbe Turkis Cursor horizontal positionieren Cursor vertikal positionieren Signalton Tabulator (horizontal) Seitenvorschub (Terminal-Fenster loschen)

3.4.2 Die ncurses-Bibliothek Vielleicht haben Sie sich schon einmal gefragt, wie ein Editor-Programm in der Art des vi (siehe Seite 6) arbeitet, das ohne die grasche Ober che X11 l uft. Die a a im vorherigen Abschnitt beschriebenen ESC-Sequenzen reichen fur eine derartige Bildschirmsteuerung bei Weitem nicht aus. Fur diesen Zweck gibt es unter Unix die curses -Bibliothek, die unter Linux in der weiterentwickelten und frei kopierbaren Version n curses vorliegt. Neben der Cursor-Positionierung und der Texthervorhebung bietet diese Funktionsbibliothek auch relativ komplexe Funktionen wie das Verschieben (Scrollen) des Fensterinhaltes um n Zeilen, das Einfugen oder Loschen von Zeichen innerhalb einer Zeile mit Verschieben der Zeichen rechts vom Cursor und das Abspeichern des gesamten Fensterinhaltes in eine Datei. Die vollst ndige Behandlung der ncurses-Bibliothek wurde den Rahmen dieses a Buches sprengen; ich mochte hier lediglich einen Einstieg in die Programmierung solcher Anwendungen geben. Die vollst ndige Liste aller Funktionen der ncursesa Bibliothek erhalten Sie mit man ncurses und man -k curses.

62

3 Kommandozeilenprogramme

Als Einstieg sei hier das folgende Programm betrachtet, das das freie Bewegen des Cursors uber das Terminal-Fenster sowie die Eingabe von Zeichen an der gerade aktuellen Cursor-Position erlaubt wie dies bei einem Editor-Programm der Fall ist. Gleichzeitig wird oben links immer die aktuelle Cursor-Position angezeigt: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40/* curses_bsp.c */ # include # include void print_pos(WINDOW *win) { int x, y; getyx(win, y, x); attron(A_REVERSE); mvprintw(0, 0, "(%2d, attroff(A_REVERSE); move(y, x); return; } int main() { int c, x, y; WINDOW *win; /* Cursor-Position darst. */

/* aktuelle Pos. abfragen */ /* inverse Darstellung ein */ %2d)", x, y); /* inverse Darstellung aus */ /* Cursor wieder an alte Pos. */

/* Hauptprogramm */

if ((win = initscr()) == NULL) return(1); cbreak(); noecho(); keypad(win, TRUE); /* Sondertasten auswerten */ move(1, 0); print_pos(win); while ((c = getch()) != KEY_END) /* Ende = Abbruch */ { switch(c) { case KEY_UP: getyx(win, y, x); move(y-1, x); break; case KEY_DOWN: getyx(win, y, x); move(y+1, x);

3.4 Ausgabesteuerung im Terminal-Fenster

63

41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64

break; getyx(win, y, x); move(y, x-1); break; case KEY_RIGHT: getyx(win, y, x); move(y, x+1); break; case KEY_DC: delch(); break; case KEY_BACKSPACE: getyx(win, y, x); move(y, x-1); delch(); break; case KEY_IC: insch( ); break; case KEY_HOME: getyx(win, y, x); move(y, 0); break; case KEY_F(1): clear(); move(1, 0); break; default: if (c < KEY_MIN) addch(c); } print_pos(win); case KEY_LEFT: } endwin(); return(0); }

Um dieses Programm zu ubersetzen, muss die ncurses-Bibliothek mit der Option -l explizit eingebunden werden: gcc curses bsp.c -lncurses -o curses bsp

Wenn Sie das Programm starten, wird der Inhalt des Terminal-Fensters (oder der Linux-Konsole) geloscht, und oben links erscheint die aktuelle Cursor-Position ( 0, 1) invers dargestellt. Sie konnen den Cursor jetzt mit Hilfe der Cursor Tasten beliebig uber das Fenster bewegen und an der aktuellen Position Text ein geben. Auch das Loschen von Zeichen mit Entf oder des gesamten Inhalts mit F1 ist moglich. Durch Drucken der Taste Ende wird das Programm beendet. Schauen wir uns zun chst das Hauptprogramm (Zeile 20 bis 64) an. Bevor ira gendwelche Ein- oder Ausgaben uber Funktionen der ncurses-Bibliothek erfol gen konnen, muss das Ausgabefenster initialisiert werden. Dies geschieht mit der Funktion initscr() in Zeile 25, die bei erfolgreicher Ausfuhrung einen Zei ger auf die WINDOW-Struktur der Bibliothek zuruckliefert. Mit den Funktionen cbreak() (Zeile 27) und noecho() (Zeile 28) wird die Betriebsart eingestellt; in diesem Fall soll jeder Tastendruck sofort an das Programm weitergeleitet werden und keine automatische Ausgabe des eingegebenen Zeichens erfolgen. Als letzte Kongurationseinstellung wird in Zeile 29 die Auswertung von Sondertasten mit

64

3 Kommandozeilenprogramme

der Funktion keypad() aktiviert. Mit der move()-Anweisung in Zeile 31 wird der Cursor in die Position y = 1, x = 0 gebracht. Als N chstes erfolgt ein Aufruf der Funktion print pos(), die in den Zeilen 8 a bis 18 deniert wird und die aktuelle Cursor-Position oben links in dem Fenster ausgibt. In der darauf folgenden while()-Schleife (Zeile 34 bis 60) werden mittels getch() so lange Zeichen von der Tastatur eingelesen, bis die Ende-Taste gedruckt wird. Dabei wird mit der switch()-Funktion in Zeile 36 bis 58 gepruft, ob es sich bei der Eingabe um eine der vier Cursortasten oder eine der Sondertasten Entfernen, Loschen (Backspace), Einfugen, Pos1 oder Funktionstaste 1 handelt. Ei ne vollst ndige Liste der von der ncurses-Bibliothek unterstutzten Sondertasten a erh lt man mit agrep KEY /usr/include/curses.h

In Zeile 57 werden alle Zeichen, deren Wert kleiner als der erste Sondertastencode (KEY MIN) ist, an der aktuellen Position ausgegeben.Die Funktion getyx() liefert die aktuelle Cursor-Position in die Variablen x und y zuruck. Es handelt sich bei dieser Funktion um ein Makro, daher kann der Inhalt der Variablen x und y ver ndert werden, ohne dass ein Zeiger auf die a Variablen ubergeben wird; es wird also kein vorangestelltes & benotigt.

Vor Ende des Hauptprogramms muss noch das ncurses-Fenster wieder ge schlossen werden. Dies geschieht mit der Funktion endwin() in Zeile 62 des Quelltextes. Die Funktion print pos(), die in den Quelltextzeilen 8 bis 18 deniert ist, sichert zun chst mit getyx() die aktuelle Cursor-Position in die Variablen x und a y (Zeile 12). Diese Position wird dann mit Hilfe von mvprintw() an der festen Position (0,0) also links oben ausgegeben (Zeile 14). Danach wird der Cursor mit move() wieder an die ursprungliche Stelle verschoben (Zeile 16). Mit den Funktionen attron() attroff() (Zeile 13 und 15) konnen Textattribute ein- und ausgeschaltet werden. Tabelle 3.3 zeigt einige der moglichen Attribute. Tabelle 3.3: Einige Textattribute der ncurses-Bibliothek AttributA A A A A A STANDOUT UNDERLINE REVERSE BLINK DIM BOLD

Beschreibung normaler Text unterstreichen inverser Text (Wei auf Schwarz) blinkender Text reduzierter Kontrast Fettschrift

3.4 Ausgabesteuerung im Terminal-Fenster

65

Bei Verwendung der ncurses-Bibliothek sollten alle Ein- und Ausgaben uber die ncurses-eigenen Funktionen erfolgen, also z.B. uber printw() und nicht etwa uber printf().

Farbige Textdarstellung mit der ncurses-Bibliothek Naturlich kann mit der ncurses-Bibliothek auch farbiger Text ausgegeben werden sofern das Terminal oder die Console Farben unterstutzen. Das folgende kleine Programm gibt Text mit unterschiedlicher Vorder- und Hintergrundfarbe aus: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34/* curses_bsp2.c - Farbdarstellung mit ncurses */ # include # include int main() { int x, y; WINDOW *win; if ((win = initscr()) == NULL) return(1); start_color(); /* Farbausgabe initialisieren */ cbreak(); noecho(); init_pair(1, COLOR_BLACK, COLOR_WHITE); init_pair(2, COLOR_YELLOW, COLOR_BLUE); init_pair(3, COLOR_RED, COLOR_CYAN); color_set(1, NULL); /* Hintergrund wei */ for (y=0; y 0) if (option == h) { printf("Usage: number [-o output-file] " "input-file ...\n"); return(0); } else if (option == o) output_file = optarg; else return(1); if (optind == argc) { fprintf(stderr, "number: Missing file name. " "Type number -h for help.\n"); return(1); } if (output_file == NULL) out_stream = stdout;

4.2 Eigenschaften von Dateien oder Verzeichnissen auswerten

75

36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60

else if ((out_stream = fopen(output_file, "w")) == NULL) { perror("number: Cant open output file"); return(1); } line = 1; for (i=optind; id_name); closedir(directory); return(0); }

Kapitel 5

InterprozesskommunikationLinux ist ein Multi-Tasking-Betriebssystem, d. h. mehrere Prozesse werden (scheinbar) gleichzeitig abgearbeitet. Fur den Anwender bedeutet dies, dass er mehrere Anwendungen gleichzeitig nutzen kann z. B. eine Textverarbeitung, w hrend a ein MP3-Decoder Musik abspielt sogar w hrend andere Anwender weitere Proa gramme auf dem gleichen System laufen lassen. Dem Programmierer eroffnet sich durch die Multi-Tasking-F higkeit die Moglich a keit, verschiedene Aufgaben eines Programms auf mehrere Prozesse aufzuteilen. Um diese Moglichkeit sinnvoll nutzen zu konnen, muss er sich auch mit der Kom munikation der Prozesse, also dem Datenaustausch zwischen verschiedenen Prozessen, auseinandersetzen.

5.1 Prozessverwaltung unter LinuxNeue Prozesse (auch als Tasks oder Threads bezeichnet) konnen unter Linux nicht aus dem Nichts entstehen, sondern werden von einem Eltern-Prozess (engl.: pa rent process) gestartet mit Ausnahme des INIT-Prozesses, der beim Hochfahren des Systems gestartet wird und selbst keinen Eltern-Prozess besitzt. Eltern- und Kind-Prozess (engl.: child process) bleiben miteinander verknupft. So liefert die C-Funktion getppid() die Prozess-ID des Eltern-Prozesses, und das Beenden eines Kind-Prozesses wird dem zugehorigen Eltern-Prozess durch ein Signal angezeigt. Jeder Prozess erh lt eine eindeutige Identikationsnummer: die Prozess-ID. Jede a ID wird nur einmal vergeben. Selbst wenn der zugehorige Prozess beendet wurde, wird dessen ID nicht neu belegt. Die Reihenfolge der IDs entspricht der Reihenfolge, in der die Prozesse erzeugt wurden: je junger ein Prozess, desto groer seine ID. Eine Liste aller existierenden Prozesse erh lt man mit ps -A. An erster Stela

80

5 Interprozesskommunikation

le steht hier der Prozess init, der immer die Prozess-ID 1 besitzt. Ganz unten in der Liste steht in der Regel der Prozess, in dem das Programm ps selbst l uft. a Man erkennt daran, dass jedes aus einer Shell heraus gestartete Programm einen eigenen Prozess erh lt und nicht etwa in dem Shell-Prozess l uft.1 a a

5.2 Neue Prozesse startenBevor man sich mit Interprozesskommunikation befasst, muss man wissen, wie ein neuer Kind-Prozess erzeugt wird. In diesem Abschnitt werden zwei Methoden zur Erzeugung eines neuen Prozesses vorgestellt. Auerdem wird angerissen, was beim Erzeugen eines Prozesses geschieht und was man als Programmierer dabei beachten muss.

5.2.1 Shell-Programme aufrufen mit system()Manchmal kann es sinnvoll sein, innerhalb eines eigenen Programms ein anderes Programm zu starten, um dessen Funktionalit t fur das eigene Programm zu a nutzen. Beispiel: Sie berechnen mit Ihrem Programm eine Reihe von Zahlen, die Sie auch gern grasch darstellen wurden. Sie konnen dann naturlich die grasche Ausgabe selbst programmieren, was im Allgemeinen einen nicht unerheblichen Aufwand bedeutet, oder rufen von Ihrem Programm aus ein bereits existierendes Programm zur Visualisierung von Daten (z.B. gnuplot) auf. Dies ist mit Hilfe der Funktionint system(char *command);

moglich. Die Funktion system() ubernimmt hier gleich mehrere Aufgaben: sie startet eine neue Shell /bin/sh (in einem eigenen Prozess) und l sst diese Shell a das in command angegebene Kommando ausfuhren. Ferner wartet system() auf die Beendigung des Kind-Prozesses, sodass Ihr Programm erst danach fortgesetzt wird. Als Ruckgabewert liefert system() entweder 127 fur den Fall, dass die Shell nicht gestartet werden kann, oder den Ruckgabewert des ausgefuhrten Kommandos. Wurde das Kommando fehlerfrei ausgefuhrt, sollte system() den Wert 0 zuruck geben. Das folgende Beispielprogramm zeigt die Verwendung der Funktion system(). Es stellt mit Hilfe des Programms gnuplot fur funf Sekunden eine Sinus-Kurve dar (dazu mussen Sie naturlich das Paket gnuplot installiert haben):

1

Es sei denn, man stellt ein exec voran.

5.2 Neue Prozesse starten

81

1 2 3 4 5 6 7 8 9 10 11 12

/* sinus.c */ # include # include int main() { system("echo plot sin(x); pause 5 | gnuplot"); return(0); }

5.2.2 Die Funktionen der exec-FamilieEigentlich gehort dieser Abschnitt gar nicht in das Kapitel Interprozesskom munikation, da die Funktionen der exec-Familie keine neuen Prozesse erzeugen. Sie werden jedoch h ug in Verbindung mit fork() (s. u.) als Ersatz fur die zuvor a beschriebene Funktion system() verwendet (vgl. hierzu auch man 3 system). Es gibt insgesamt sechs Funktionen der exec-Familie, die sich vor allem durch Abweichungen in der Art der Parameter unterscheiden:int int int int int int execl(char *path, char *arg, ...); execlp(char *file, char *arg, ...); execle(char *path, char *arg , ..., char *envp[]); execv(char *path, char *argv[]); execvp(char *file, char *argv[]); execve(char *path, char *argv[], char *envp[]);

Alle diese Funktionen fuhren ahnlich wie die Funktion system() ein Pro gramm aus, dessen Name in dem Parameter file bzw. path angegeben ist. Im Unterschied zu system() wird keine neue Shell gestartet, sondern das auszufuhrende Programm l uft in dem gleichen Prozess, der die exec-Funktion a aufgerufen hat. Bei Beendigung des Programms wird auch dieser Prozess beendet, d.h. der Prozess kehrt nach erfolgreicher Ausfuhrung einer exec()-Funktion nicht mehr in das ursprungliche Programm zuruck. Lediglich bei Auftreten eines Fehlers, weil z.B. das angegebene Programm nicht existiert, kehren die exec()Funktionen mit dem Ruckgabewert 1 zuruck. Man erkennt, dass der Funktionsname jeweils aus exec plus eine Endung aus ein oder zwei Buchstaben gebildet wird. Ein l in der Endung bedeutet da bei, dass die Argumente (also die Kommandozeilenparameter) der aufzurufenden Funktion als Liste ubergeben werden, d.h. fur jedes Argument eine Zeichenkette. Das erste Argument muss dabei den Programmnamen selbst enthalten! Ein v dagegen bedeutet, dass die Argumente als Vektor ubergeben werden, und zwar

82

5 Interprozesskommunikation

in der Form, wie sie auch die Funktion main() erh lt (vgl. Abschnitt 3.1.2). Auch a hier entspricht das erste Argument, also argv[0], dem Namen des aufzurufenden Programms. Ein p in der Endung des Funktionsnamens weist darauf hin, dass diese Funkti on alle in der Umgebunsvariablen PATH eingetragenen Pfade nach dem angegebenen Programm durchsucht, w hrend die Funktionen ohne ein p in der Endung a den vollst ndigen Pfad des auszufuhrenden Programmes benotigen (daher wura de der Parameter bei diesen Funktionen als path und nicht als file bezeichnet). Das e in der Endung der Funktionen execle() und execve() bedeutet, dass diese Funktionen zus tzlich zu den Argumenten fur das aufzurufende Programm a einen Vektor mit Umgebungsvariablen benotigen, wie sie an das Programm wei tergegeben werden sollen. Die anderen Funktionen behalten die aktuellen Umgebungsvariablen bei. Die beiden folgenden Programmausschnitte sollen die Verwendung der execFunktionen demonstrieren: Beispiel 1:# include # include char *argv[4], *env[2]; argv[0] = "/bin/ls"; argv[1] = "--color"; argv[2] = "/home/martin"; argv[3] = NULL; env[0] = "LS_COLORS=fi=00:di=01"; env[1] = NULL; execve("/bin/ls", argv, env); perror("execve() failed");

Beispiel 2:execlp("ls", "ls", "-F", "/home/martin", NULL);

5.2.3 Einen Kind-Prozess erzeugen mit fork()Die zentrale Funktion, mit der unter Linux/Unix ein Kind-Prozess erzeugt wird, ist fork(). Die Funktion hat keinen Parameter; sie liefert als Ruckgabewert die Prozess-ID des neu erzeugten Kind-Prozesses. In dem Kind-Prozess selbst liefert die Funktion eine 0 als Ruckgabewert; konnte kein Kind-Prozess erzeugt werden, ist der Ruckgabewert 1:

5.2 Neue Prozesse starten

83

# include # include int child_pid; if ((child_pid = fork()) == 0) printf("Dies ist der Kind-Prozess.\n"); else if (child_pid > 0) printf("Kind-Prozess %d wurde erzeugt.\n", child_pid); else perror("fork() failed");

Doch was genau passiert beim Aufruf der Funktion fork()? Linux legt eine neue Task-Struktur an, die eine Kopie der Struktur des Eltern-Prozesses darstellt. Dieser Prozess erh lt eine neue Prozess-ID, benutzt jedoch zun chst den gleichen a a Speicher wie der Eltern-Prozess. Erst wenn einer der beiden Prozesse in den Speicher schreibt, also z.B. eine Variable ver ndert, wird der entsprechende Speichera bereich dupliziert und fur beide Prozesse getrennt weitergefuhrt. Diese Metho de bezeichnet man als copy-on-write. Dadurch wird erreicht, dass beide Prozesse scheinbar getrennten Speicher benutzen, ohne bereits bei der Erzeugung des Kind-Prozesses den gesamten Speicher des Eltern-Prozesses kopieren zu mussen. Nach Ausfuhrung der Funktion fork() existieren also zwei Prozesse, die sich zun chst allein durch die Prozess-ID unterscheiden und durch den Ruckgabea wert von fork(). Bei jedem Schreib-Zugriff wird jedoch der Unterschied beider Prozesse groer. Das folgende Beispielprogramm new child soll diesen Vor gang veranschaulichen: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17/* new_child.c - einen Kind-Prozess erzeugen */ # include # include int main() { int child_pid, test; test = 1; printf("\t\ttest=%d, &test=%p\n", test, &test); if ((child_pid = fork()) == 0) { sleep(1);

84

5 Interprozesskommunikation

18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34

printf("Kind-Prozess:\ttest=%d, &test=%p\n", test, &test); test = 2; printf("Kind-Prozess:\ttest=%d, &test=%p\n", test, &test); } else { printf("Eltern-Prozess:\ttest=%d, &test=%p\n", test, &test); sleep(2); printf("Eltern-Prozess:\ttest=%d, &test=%p\n", test, &test); } return(0); }

Wenn wir dieses Programm ubersetzen und ausfuhren, erhalten wir die Ausgabe: test=1, test=1, test=1, test=2, test=1, &test=0xbffff8c0 &test=0xbffff8c0 &test=0xbffff8c0 &test=0xbffff8c0 &test=0xbffff8c0

Eltern-Prozess: Kind-Prozess: Kind-Prozess: Eltern-Prozess:

Man sieht, dass der Inhalt der Variablen test durch die Anweisung in Zeile 19 des Quelltextes innerhalb des Kind-Prozesses von ursprunglich 1 auf 2 ver ndert a wird; im Eltern-Prozess bleibt jedoch auch danach der alte Wert erhalten, obwohl die Adresse der Variablen (also die Speicherstelle) im Eltern- und Kind-Prozess die gleiche zu sein scheint! Man muss sich hier ins Ged chtnis rufen, dass Linux a ein Betriebssystem mit einer virtuellen Speicherverwaltung ( virtual memory) ist. Es handelt sich bei den Adressen also nicht um physikalische Speicherstellen, sondern um Offsets innerhalb einer Seite ( Page) des virtuellen Speichers. So bleibt die Adresse fur beide Prozesse konstant; durch den Schreibzugriff wird jedoch die entsprechende Seite des virtuellen Speichers kopiert und die Anderung nur in der Kopie durchgefuhrt. Anhand dieses Beispielprogramms l sst sich ein weiterer interessanter Effekt dea monstrieren. Leiten Sie dazu die Ausgabe des Programms in eine Datei um, und a geben Sie diese dann aus (die Benutzereingaben sind schr g dargestellt):> new child > ausgabe > cat ausgabe test=1, &test=0xbffff8c0 Kind-Prozess: test=1, &test=0xbffff8c0

5.2 Neue Prozesse starten

85

Kind-Prozess:

test=2, test=1, Eltern-Prozess: test=1, Eltern-Prozess: test=1,

&test=0xbffff8c0 &test=0xbffff8c0 &test=0xbffff8c0 &test=0xbffff8c0

Die Reihenfolge der Ausgaben hat sich ver ndert. Auerdem erfolgt die Ausgabe a vor Erzeugung des Kind-Prozesses (Quelltext Zeile 13) doppelt! Was ist passiert? Sobald der Standard-Ausgabekanal durch das Umlenk-Symbol > in eine Datei geleitet wird, erfolgt eine Pufferung der Daten (vgl. Abschnitt 4.1.1); ohne Umleiten der Ausgabe ist das nicht der Fall. Durch die Pufferung werden die Zeichen nicht sofort in die angegebene Datei geschrieben, sondern zun chst im Puffer zwia schengespeichert so auch die Ausgabe der printf()-Anweisung in Zeile 13. Nach Erzeugung des Kind-Prozesses benutzen beide Prozesse den gleichen Puffer weiter. Schreibt einer der beiden Prozesse weitere Zeichen in den Puffer, wird dieser zuvor dupliziert, d.h. sein gesamter Inhalt kopiert. Dadurch liegt die Ausgabe der printf()-Anweisung jetzt in zwei getrennten Puffern, die beide mit der Datei ausgabe verknupft sind. Sowohl Eltern- als auch Kind-Prozess schreiben jetzt weitere Zeilen in ihren jeweiligen Puffer, ohne dass wirklich Zeichen in die angegebene Datei geschrieben werden. Erst wenn einer der beiden Prozesse beendet wird, dieser also beim return(0) in Zeile 33 angelangt ist, wird sein Pufferinhalt in die Datei geschrieben. Dadurch enth lt die Ausgabedatei zun chst alle Ausgaben des Kinda a Prozesses (der aufgrund der kurzeren Wartezeit bei der Funktion sleep() in Zei le 17 immer vor dem Eltern-Prozess beendet wird), gefolgt von allen Ausgaben des Eltern-Prozesses beide jeweils angefuhrt von der Ausgabe der printf() Anweisung in Zeile 13. Will man das Duplizieren des Pufferinhaltes bei Erzeugung eines neuen KindProzesses verhindern, kann man mit Hilfe der Funktion fflush() (siehe Abschnitt 4.1.4) den Pufferinhalt unmittelbar vor Erzeugen des Kind-Prozesses in die Datei schreiben und den Puffer selbst leeren.

5.2.4 WarteschleifenBeim Programmieren unter Betriebssystemen, die nicht Multitasking-f hig sind, a ist es ublich, mit Hilfe von Warteschleifen auf bestimmte Ereignisse etwa das Drucken einer Taste zu warten. Unter Linux sollte man jedoch nach Moglichkeit auf solche Warteschleifen verzichten und stattdessen den Ablauf des Programms mit Hilfe von Signalen (siehe n chsten Abschnitt) steuern. Jede Warteschleife koa stet Rechenzeit, die sonst fur andere Prozesse zur Verfugung stehen wurde. Soll ein Programm nur eine bestimmte Zeit lang warten, ohne dies von einem Ereignis abh ngig zu machen, konnen dazu die Funktionen sleep() und usleep() a verwendet werden. Diesen Funktionen wird als Parameter die zu wartende Zeit in Sekunden bzw. Mikrosekunden (das u in usleep() steht fur und damit

86

5 Interprozesskommunikation

fur 10 6 ) ubergeben. Beachten Sie jedoch, dass das Eintreffen eines nicht geblock ten Signals (siehe Abschnitt 5.3) diese Funktionen ungeachtet der verbleibenden Wartezeit sofort beendet. L sst sich einmal eine Warteschleife nicht oder nur mit viel Aufwand umgehen, a sollte die Schleife einen Aufruf der Funktion sched yield() enthalten. Diese Funktion unterbricht die Ausfuhrung des Programms und stellt den Prozess an das Ende der Liste der auszufuhrenden Prozesse. Dadurch wird der Rechenzeitbe darf der Warteschleife deutlich reduziert. Als Beispiel sei das folgende Programm betrachtet, das wartet, bis eine CD-ROM oder Audio-CD in das CD-Laufwerk eingelegt wird:1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18/* cdwait.c - Auf das Einlegen einer CD-ROM warten. */ # include # include # include int main() { int fd; while ((fd = open("/dev/cdrom", O_RDONLY)) == -1) sched_yield(); close(fd); return(0); }

5.3 SignaleEin wichtiges Hilfsmittel fur die Ablaufsteuerung von Prozessen und fur die Kommunikation zwischen Prozessen sind die Signale. Linux kennt 31 verschiedene Signale, die in der Include-Datei/usr/include/bits/signum.h

aufgelistet sind. Hier eine Ubersicht uber die wichtigsten Signale:

1

Zum Ausfuhren des Programms mussen Sie uber Lesezugriff (r) auf das Device /dev/cdrom verfugen.

5.3 Signale

87

SignalnameSIGINT SIGKILL SIGUSR1 SIGUSR2 SIGALRM SIGTERM SIGCHLD

Signal-Nr. 2 9 10 12 14 15 17

Bedeutung Unterbrechungsanforderung (Ctrl-C) Prozess beenden (nicht blockierbar) benutzerdeniert, zur freien Verwendung benutzerdeniert, zur freien Verwendung fur die Weckfunktion Prozess beenden (blockierbar) Kind-Prozess wurde beendet

Ein Prozess kann einem anderen Prozess ein Signal schicken, um ihn z.B. auf ein Ereignis aufmerksam zu machen.

5.3.1 Die Weckfunktion alarm()Wenn ein Prozess auf ein Ereignis wartet, ist es h ug erforderlich, eine maximaa le Wartezeit zu realisieren, fur den Fall, dass das Ereignis nicht eintrifft. Um ein solches Timeout-Verhalten zu realisieren, kann die Funktion alarm() verwendet werden:unsigned int alarm(unsigned int seconds);

Die Funktion startet einen Zeitgeber, der nach Ablauf der mit seconds angegebenen Zeit (in Sekunden) dem Prozess das Signal SIGALRM schickt. Als Ruckgabe wert liefert alarm() die Restzeit, die bei dem vorangegangenen alarm()-Aufruf noch verblieben ist, bzw. 0, wenn zuvor kein Aufruf der Funktion alarm() erfolgte. Als Voreinstellung fuhrt das Signal SIGALRM zum Abbruch des Programms (Pro zesses) mit Ausgabe der Meldung Der Wecker klingelt (bzw. Alarm clock) und einem Ruckgabewert ungleich 0. Erg nzt man das Beispiel-Programm aus Abschnitt 5.2.4 um die Funktion a alarm(), kann damit ein Timeout fur das Einlegen einer CD realisiert werden: 1 2 3 4 5 6 7 8 9 10 11 12/* cdwait2.c - Auf das Einlegen einer CD-ROM warten. */ # include # include # include int main() { int fd;

88

5 Interprozesskommunikation

13 14 15 16 17 18 19 20

alarm(10);

/* Timeout: 10 Sekunden */

while ((fd = open("/dev/cdrom", O_RDONLY)) == -1) sched_yield(); close(fd); return(0); }

5.3.2 Einen Signal-Handler einrichtenIn dem vorangegangenen Beispiel wird das Programm durch das Signal SIGALRM beendet. Mit Hilfe der Funktion signal() l sst sich die Reaktion eines Prozesses a auf ein bestimmtes Signal modizieren:1signal(int signum, void *handler());

Diese Funktion richtet fur das Signal mit der Nummer signum einen so genannten Signal-Handler ein. Der Parameter handler() kann dabei entweder eine der Konstanten SIG IGN und SIG DFL oder der Name einer benutzerdenierten Funktion sein. SIG IGN (IGN steht fur ignore) bewirkt, dass das Signal blockiert wird, also keine Auswirkung zeigt, w hrend SIG DFL (DFL steht fur default) den fur a das entsprechende Signal voreingestellten Signal-Handler einrichtet. Die Funktion signal() liefert als Ruckgabewert den zuvor mit diesem Signal verknupften Signal-Handler bzw. die Konstante SIG ERR, falls der Signal-Handler nicht eingerichtet werden konnte. Bei Verwendung einer benutzerdenierten Funktion als Signal-Handler muss diese vom Typ void sein und einen Parameter vom Typ int haben. Mit diesem Parameter wird dem Signal-Handler die Signalnummer ubergeben. Damit besteht die Moglichkeit, den gleichen Signal-Handler fur verschiedene Signale zu benutzen und dann anhand des Parameters festzustellen, welches Signal tats chlich gesetzt a wurde. Im folgenden Programmbeispiel wurde der Quelltext aus dem vorangegangenen Abschnitt um das Einrichten eines Signal-Handlers fur das Signal SIGALRM erg nzt: a 1 2 3 4 5 61

/* cdwait3.c - Auf das Einlegen einer CD-ROM warten. */ # include # include Die Deklaration der Funktion signal() ist hier etwas vereinfacht dargestellt, die exakte Deklaration erh lt man mit man 2 signal. a

5.3 Signale

89

7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31

# # # #

include include include include

void my_handler(int signum) { fprintf(stderr, "cdwait3: timeout\n"); exit(1); } int main() { int fd; signal(SIGALRM, my_handler); alarm(10); /* Signal-Handler einrichten */ /* Timeout: 10 Sek. */

while ((fd = open("/dev/cdrom", O_RDONLY)) == -1) sched_yield(); close(fd); return(0); }

Wird dieses Programm gestartet und innerhalb der darauf folgenden 10 Sekunden keine CD eingelegt, so gibt das Programm die Meldung cdwait3: timeout aus und bricht mit dem Ruckgabewert 1 ab.

5.3.3 Auf die Beendigung eines Kind-Prozesses wartenH ug ist es erforderlich, dass ein Eltern-Prozess auf die Beendigung eines a Kind-Prozesses wartet, z.B. wenn in dem Kind-Prozess mit execlp() (vgl. Abschnitt 5.2.2) ein Programm ausgefuhrt wird und der Eltern-Prozess erst danach fortfahren soll. Zu diesem Zweck stehen die Funktionen wait() und waitpid() zur Verfugung: pid_t wait(int *status); pid_t waitpid(pid_t pid, int *status, int options);

Die Funktion wait() wartet auf die Beendigung eines Kind-Prozesses und liefert dessen Prozess-ID als Ruckgabewert bzw. 1 bei einem Fehler. Die Funktion waitpid() wartet auf die Beendigung des Kind-Prozesses mit der in pid angegebenen Prozess-ID. Beide Funktionen speichern Statusinformationen zu dem

90

5 Interprozesskommunikation

beendeten Prozess in die Variable status, sofern hier nicht die Konstante NULL angegeben wurde. Als options kann entweder eine 0 oder eine Kombination aus den Konstanten WNOHANG und WUNTRACED angegeben werden. WNOHANG bewirkt, dass lediglich gepruft wird, ob der angegebene Prozess beendet wurde, ohne je doch auf dessen Beendigung zu warten. Der Ruckgabewert des beendeten Kind-Prozesses kann aus der Statusinformation mit Hilfe des Makros WEXITSTATUS() ermittelt werden. Das folgende Programm, das in einem Kind-Prozess den Shell-Befehl ls auf ruft, soll die Handhabung von wait() und WEXITSTATUS() verdeutlichen: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25/* my_ls.c - "ls" in einem Kind-Prozess ausfuehren */ # # # # include include include include

int main(int argc, char *argv[]) { pid_t pid; int status; if ((pid = fork()) == 0) { /* Kind-Prozess */ execlp("ls", "ls", "-F", argv[1], NULL); perror("my_ls: execlp() failed"); return(1); } wait(&status); /* Eltern-Prozess */ printf("Child process exited with return code %d.\n", WEXITSTATUS(status)); return(0); }

5.3.4 Signale setzen mit kill()Ein Kind-Prozess kann dem Eltern-Prozess nicht nur seine Beendigung signali sieren. Man kann Signale auch zur Kommunikation zwischen Eltern- und KindProzess verwenden, etwa um ein bestimmtes Ereignis zu melden. Grunds tzlich a lassen sich aber nur Signale an einen Prozess des gleichen Benutzers schicken, es sei denn, das Programm l uft mit root-Privilegien. a Das Setzen von Signalen erfolgt mit der Funktion kill():

5.4 Datenaustausch zwischen Prozessen

91

int kill(pid_t pid, int signum);

wobei pid die Prozess-ID des Prozesses angibt, bei dem das Signal signum gesetzt werden soll. Kann es gesetzt werden, gibt kill() eine 0 zuruck, anderenfalls eine 1 (z. B. wenn der angegebene Prozess nicht existiert). Durch Verwendung des Signals SIGKILL oder SIGTERM ist es mit Hilfe dieser Funktion auch moglich, einen anderen Prozess vorzeitig zu beenden.

5.4 Datenaustausch zwischen ProzessenWie wir bereits in Abschnitt 5.2.3 gesehen haben, konnen Eltern- und Kind Prozess nicht ohne weiteres uber Variablen Informationen austauschen sobald einer der Prozesse in den zun chst gemeinsamen Speicher schreibt, wird dieser a dupliziert und ist fortan fur beide Prozesse getrennt. Daher bedarf es anderer Ver fahren, um einen Datenaustausch zwischen Prozessen zu realisieren, der uber das Setzen von Signalen hinausgeht. Linux bietet hierfur drei verschiedene Moglichkeiten an: Pipes, FIFOs und Shared Memory. Diese werden in den folgenden Abschnitten vorgestellt und erl utert. a

5.4.1 PipesEine Pipe ist eine Art virtuelle Datei, dargestellt uber zwei Datei-Deskriptoren (siehe Abschnitt 4.1.1) einer zum Lesen und der andere zum Schreiben. Erzeugt wird eine Pipe mit Hilfe der gleichnamigen Funktion:int pipe(int filedes[2]);

Als Parameter wird ein Feld aus zwei Datei-Deskriptoren (Integer-Variablen) benotigt, in das die Funktion pipe() die erzeugten Deskriptoren schreibt. pipe() liefert den Ruckgabewert 0 bei Erfolg oder 1 bei einem Fehler. Zur Verdeutlichung der Anwendung von pipe() sei das folgende Programm betrachtet, bei dem der Eltern-Prozess eine Zeichenkette an den Kind-Prozess sen det: 1 2 3 4 5 6 7 8 9/* pipe.c - Interprozesskommunikation mit einer Pipe */ # include # include # include int main()

92

5 Interprozesskommunikation

10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41

{ int fd[2], l; char buffer[80]; if (pipe(fd) != 0) { perror("pipe: pipe() failed"); return(1); } if (fork() == 0) { close(fd[1]); /* Kind-Prozess */ if ((l = read(fd[0], buffer, 79)) == -1) perror("pipe: read() failed"); else { buffer[l] = \0; printf("Received string: %s\n", buffer); } close(fd[0]); return(0); } close(fd[0]); sleep(1); write(fd[1], "Test!", 5); wait(NULL); close(fd[1]); return(0); } /* Eltern-Prozess */

In Zeile 14 wird zun chst eine Pipe erzeugt; die zugehorigen Datei-Deskriptoren a werden in das Feld fd[] geschrieben. Danach wird in Zeile 20 mit fork() der Kind-Prozess gestartet. In dem Kind-Prozess wird der zweite Datei-Deskriptor der Pipe geschlossen (Zeile 22), weil dieser nur vom Eltern-Prozess benotigt wird. Das Schlieen von fd[1] im Kind-Prozess hat wegen des copy-on-writeMechanismus (vgl. Abschnitt 5.2.3) keinen Einuss auf den gleichen Deskriptor im Eltern-Prozess. Analog wird im Eltern-Prozess der dort nicht benotigte De skriptor fd[0] geschlossen (Zeile 34). In Zeile 23 wird jetzt innerhalb des Kind-Prozesses aus der Pipe gelesen. Die Funktion read() verh lt sich ahnlich wie fread(), benotigt anstelle eines Streams jea doch einen Datei-Deskriptor (eine genaue Beschreibung der Funktionen read()

5.4 Datenaustausch zwischen Prozessen

93

und write() folgt in Kapitel 6). Nach Ausgabe der Zeichenkette mit printf() in Zeile 28 wird der zweite Datei-Deskriptor der Pipe geschlossen (Zeile 30) und der Kind-Prozess beendet. Der Eltern-Prozess wartet in diesem Beispiel zun chst eine Sekunde lang (Zeia le 35) und schreibt danach eine Zeichenkette in die Pipe (Zeile 36). Anschlieend wartet der Eltern-Prozess auf die Beendigung des Kind-Prozesses (Zeile 37), bevor der zweite Datei-Deskriptor geschlossen und das Programm beendet wird (Zeile 39 und 40).Fur eine bidirektionale Kommunikation zwischen zwei Prozessen mussen zwei Pipes geoffnet werden.

Die Verwendung von Pipes als Standardein- und -ausgabe Wenn mit Hilfe einer Funktion der exec-Familie (siehe Abschnitt 5.2.2) in einem Kind-Prozess ein externes Programm aufgerufen wird, sollen h ug die Eina und Ausgaben dieses Programms uber den Eltern-Prozess laufen. Dazu gibt es die Moglichkeit, die Standardein- und -ausgabe eines Prozesses durch je eine Pipe zu ersetzen. Das folgende Programm ruft in einem Kind-Prozess das Programm sort auf und schickt eine Zeichenkette, bestehend aus mehreren Zeilen, an die Standardeingabe von sort. Die Standardausgabe von sort wird wiederum vom Eltern-Prozess gelesen und ausgegeben: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21/* pipe2.c - Pipes als Standardein- und -ausgabe */ # include # include # include int main() { int fd1[2], fd2[2], l; char buffer[80]; if ((pipe(fd1) != 0) || (pipe(fd2) != 0)) { perror("pipe2: pipe() failed"