Systemnahe Software (Systemnahe Software II)

Größe: px
Ab Seite anzeigen:

Download "Systemnahe Software (Systemnahe Software II)"

Transkript

1 Systemnahe Software (Systemnahe Software II) F. Schweiggert, A. Borchert, M. Grabert und J. Mayer 22. Mai 2006 Fakultät Mathematik u. Wirtschaftswissenschaften Abteilung Angewandte Informationsverarbeitung (SAI) Vorlesungsbegleiter (gültig ab Sommersemester 2005) UNIVERSITÄT ULM DOCENDO CURANDO S CIENDO Hinweise: Das Urheberrecht ( c ) liegt bei den Autoren Auf eine detaillierte Unterscheidung zwischen BSD Unix und System V Unix wird hier verzichtet. Die enthaltenen Beispiel-Programme wurden zum großen Teil unter Linux entwickelt und sind weitestgehend unter Solaris getestet (für konstruktive Hinweise sind die Autoren dankbar). Die Beispiele sollen jeweils gewisse Aspekte verdeutlichen und erheben nicht den Anspruch von Robustheit und Zuverlässigkeit. Man kann alles anders und besser machen. Details zu den behandelten bzw. verwendeten System-Calls sollten jeweils im Manual bzw. den entsprechenden Header-Files nachgelesen werden. Sehr nützliche Hinweise zur Verwendung des C-Compilers finden sich unter der von Dr. Andreas Borchert erstellten Seite i

2 ii

3 Inhaltsverzeichnis 1 Das Unix-Prozess-Subsystem Theorie der Prozesse Prozess-Baum Prozessmanagement Synchrone und asynchrone Prozesse CPU-intensive Prozesse Prozesszustände und Zustandsübergänge Virtueller Adressraum Das proc-filesystem Prozesse unter Unix System Call fork System Call exit System Call exec System Call wait n-damen-problem mit Prozessen Datei-Umlenkung Eine kleine Shell (tinysh) Allgemeines Anforderungen Grundlegender Ablauf Einschub: stralloc.h, libowfat-bibliothek Einlesen der Kommandozeile readline() Zerlegen in Tokens tokenizer() Hauptprogramm unserer Shell Bootstrapping klassisch Der init-prozess Signale Einführung Signalbehandler Reaktion auf Signale: signal() Wecksignale mit alarm() Das Versenden von Signalen Reaktion auf Signale: sigaction() Die Zustellung von Signalen Signale als Indikatoren für terminierte Prozesse Signalbehandlung in einer (Mini-)Shell Überblick der Signale aus dem POSIX-Standard iii

4 iv INHALTSVERZEICHNIS 4 Inter-Prozess-Kommunikation (IPC) Einführung IPC - Client-Server Beispiel System Calls dup(), dup2() Unnamed Pipes Client-Server mit Unnamed Pipes Standard I/O Bibliotheksfunktion Über Standarddeskriptoren in Pipe schreiben / lesen Termination von Pipeline-Verbindungen Wie die Shell eine Pipeline macht SIGPIPE Netzwerk-Kommunikation Übersicht Ethernet Internetworking - Konzept & Grundlegende Architektur Transport-Protokolle Ports UDP Berkeley Sockets Grundlagen Ein erstes Beispiel: Timeserver an Port Die Socket-Abstraktion Die Socket-Programmierschnittstelle Vorbemerkungen Überblick/Einordnung Erzeugung eines Socket Benennung eines Socket Aubau einer Kommunikationsverbindung Client-Beispiel: Timeclient für Port Überblick: Gebrauch von TCP-Ports Der Datentransfer Terminierung einer Kommunikationsverbindung Verbindungslose Kommunikation Feststellen gebundener Adresse Socket-Funktionen im Überblick Konstruktion von Adressen Socket-Adressen Socket-Adressen der UNIX-Domäne Socket-Adressen in der Internet-Domäne Byte-Ordnung Spezifikation von Internet-Adressen Hostnamen Lokale Hostnamen und IP-Adressen Portnummern und Dienste Gepufferte Ein- / Ausgabe für Netzwerkverbindungen Ein kleiner TCP-Server (concurrent) mit Eingabe-Pufferung Ein- / Ausgabe von Paketen für Netzwerkverbindungen Parallele Sitzungen

5 INHALTSVERZEICHNIS v 7 Threads Einführung Grundlegende Funktionen: Erzeugen und Beenden pthread_create() pthread_join() pthread_self() pthread_detach() pthread_exit() Beispiel: Matrix-Multiplikation TCP Echo Server mit Threads Thread-sichere Funktionen Echo Client mit Threads Synchronisation Mutual Exclusion Anhang 243 Literatur 245 Abbildungsverzeichnis 247 Beispiel-Programme 249 Index 249

6 vi INHALTSVERZEICHNIS

7 Kapitel 1 Das Unix-Prozess-Subsystem 1.1 Theorie der Prozesse Die Ausführung eines Programms heißt Prozess. Der Kontext eines Prozesses ist seine Ausführungsumgebung. Dazu gehören der Adressraum, in dem u.a. der Programmtext (als Maschinencode) und die Daten untergebracht sind ein Satz Maschinenregister einschließlich der Stackverwaltung (Stack-Zeiger, Frame- Zeiger) und dem PC der Program Counter verweist auf die nächste auszuführende Instruktion weitere Statusinformationen, die vom Betriebssystem verwaltet werden wie z.bsp. offene I/O-Verbindungen Die Kombination aus Adressraum und Statusinformationen wird bei Unix als Prozess bezeichnet. Zu einem Prozess gehoeren mindestens ein Satz Maschinenregister einschließlich einem PC (program counter). Es können aber auch mehrere sein; in diesem Fall spricht man von Threads: es existieren mehrere Ausführungsfäden, die parallel abgearbeitet werden. Unix verwaltet allerdings für einzelne Threads auch Statusinformationen, so dass bei einem Prozess auch manchmal von einer Rechtegemeinschaft gesprochen wird. Ausführbare Programme sind z.b.: vom Compiler generierter Objektcode ( a.out ) Datei mit einer Folge von Shell-Kommando s chmod+x) Das Betriebssystem verwaltet eine endliche Menge von Prozessen und versucht, die vorhandenen Resourcen (Speicher, Rechenzeit, I/O-Operationen) fair auf die einzelnen Prozesse zu verteilen. Ein Prozess folgt bei der Ausführung einer genau festgelegten Folge von Anweisungen, die in sich abgeschlossen sind. Schutzmechanismen des Betriebssystems und der Hardware verhindern, dass ein Prozess auf Anweisungen oder Daten außerhalb seines Adressraums zugreift. Die gesamte Kommunikation mit anderen Prozessen (IPC Interprocess Communication) oder mit dem Kernel muss über System Calls erfolgen. (Fast) alle Prozesse entstehen aus einem bereits existierenden Prozess durch Aufruf von int fork(). 1

8 2 KAPITEL 1. DAS UNIX-PROZESS-SUBSYSTEM Jeder Prozess besitzt eine Prozessnummer (PID). als eindeutige Identifikation, die einen Index in die Prozesstabelle darstellt. In dieser Tabelle verwaltet das Betriebssystem die wichtigsten Daten aller Prozesse. Mit der Funktion int getpid() kann ein Prozess seine eigene PID abfragen. (Fast) jeder Prozess hat einen Erzeuger (siehe oben: fork()), der sog. parent process Dessen PID kann ein Prozess mit der Funktion int getppid() erhalten. 1 # include < stdio. h> 2 # include < stdlib. h> 3 # include <unistd. h> 4 5 int main() { 6 Programm 1.1: Prozess-ID abfragen (pids.c) 7 printf ( "My pid is %d, that of my parent ist %d\n", 8 getpid (), getppid ()); 9 exit (0); 10 } Ausführung: spatz$ echo $$ # PID der Shell 4223 spatz$ gcc -Wall -o pids pids.c spatz$ pids My pid is 4236, that of my parent ist 4223 spatz$ Anm.: Die Shell-Variablen $ enthält die PID der aktuellen Shell wird einer Shell-Variablen ein $ vorangestellt, so wird diese von der Shell durch ihren Wert substituiert. Jeder Prozess gehört zu einer Prozessgruppe, die ebenfalls durch eine kleine positive Integerzahl identifiziert wird (process group ID). Ist die process ID eines Prozesses gleich der process group ID, so wird dieser Prozess als process group leader bezeichnet. Mit kill (Shell-Kommando und auch System Call) können Signale an alle Prozesse einer process group gesandt werden. Bestimmung der process group ID: Funktion int getpgrp(); in BSD4.3: int getpgrp(int pid); ist das Argument pid 0, liefert diese Funktion die process group ID des aktuellen Prozessen, ansonsten die der Prozessgruppe des über pid angegebenen Prozesses. Die Zuordnung zu einer Prozessgruppe kann geändert werden: unter System V: int setpgrp(); damit wird der aufrufende Prozess zum process group leader; als process group ID wird die process ID geliefert. unter 4.3.BSD: int setpgrp(int pid, int pgrp); ist pid 0, so ist der aktuelle Prozess gemeint, ansonsten muss der angesprochene Prozess dieselbe effektive user ID wie der aktuelle Prozess haben oder der aktuelle Prozess muss Superuser Privilegien haben.

9 1.1. THEORIE DER PROZESSE 3 Jeder Prozess kann Mitglied einer terminal group sein, die selbst wieder über eine positive ganze Zahl (terminal group ID) identifiziert wird. Diese ist die process ID des process group leader, der das Terminal geöffnet hat - typischerweise die login shell. Dieser Prozess wird control process für dieses Terminal genannt. Jedes Terminal hat nur einen control process. Die terminal group ID identifiziert das Kontroll-Terminal für jede Prozessgruppe. Das Kontroll- Terminal wird benutzt, um Signale abzuarbeiten (von der Tastatur aus erzeugt, oder wenn die login shell terminiert). Wenn der Prozess, der gleichzeitig Kontroll-Prozess eines Terminals und Prozessgruppenführer ist (typischerweise die login shell), ein exit zum Beenden aufruft, so wird das hangup Signal an alle Prozesse dieser Gruppe gesandt. Das Kontrollterminal wird automatisch referenziert durch /dev/tty. Ein Dämon-Prozess hingegen ist ein Hintergrundprozess ohne kontrollierendes Terminal. (z.b. httpd) Terminierung eines Prozesses: Prozesse können sich jederzeit mit exit() beenden und dabei einen Statuswert (Exit-Status oder Beendigungsstatus) im Bereich von 0 bis 255 angeben. In C-Programmen kann die exit-funktion auch implizit aufgerufen werden: ein return in der main-funktion führt zu einem entsprechenden exit, wenn das Ende der main-funktion erreicht wird, so entspricht dies einem exit(0). Ein Exit-Wert 0 deutet eine erfolgreiche Terminierung an, andere Werte, so EXIT_FAILURE, werden als Misserfolg gewertet (allerdings Programm abhängig: egrep liefert 1, wenn keine Treffer vorliegen). Diese Konventionen orientieren sich zwar an UNIX, sind aber auch Bestandteil von ISO/IEC 9899:1999 (C99-Standard).

10 4 KAPITEL 1. DAS UNIX-PROZESS-SUBSYSTEM 1.2 Prozess-Baum Hierarchie und Vererbung (siehe Abbildung 1.1 parent process pid1 = fork() (pid1 > 0) child process (pid1 == 0) pid2 = fork() child process (pid2 == 0) (pid2 > 0) Abbildung 1.1: Prozess-Hierarchie Vererbung: Prozesse vererben Teile ihrer Umgebung an ihre Kindprozesse: die Standard-Dateiverbindungen den aktuellen Katalog Umgebungsvariablen Auch die Regeln für die Weitergabe von Informationen folgen streng der hierarchischen Vererbungslehre. Elternprozesse können ihren Kindern Teile ihrer Umgebung als Kopie mitgeben. Die umgekehrte Richtung ist nicht möglich. Prozesse können Daten untereinander nur über IPC- Mechanismen (interprocess communication) austauschen. Eine Ausnahme bildet der Beendigungsstatus, den ein Prozess bei seiner Beendigung an seinen Erzeugerprozess zurückgeben kann (exit() wait()). Auf Shell-Eeben kann der Exit-Status des zuletzt terminierten Programm über die Variable? abgefragt werden: spatz$ egrep stdio *.c longrun.c:# include <stdio.h> pids.c:# include <stdio.h> spatz$ echo $? 0 spatz$ egrep stdin *.c swg@spatz:~/skripte/soft2.05/1/progs> echo $? 1 spatz$

11 1.3. PROZESSMANAGEMENT Prozessmanagement Kommando ps - Ausgabe der Prozesstabelle Das Kommando ps gibt alle wesentlichen Informationen über die aktuelle Prozesshierarchie ( Prozesstabelle) aus. Je nach Option (und Betriebssystemversion) besteht die Ausgabe von ps aus mehr oder weniger Informationen zu den einzelnen Prozessen. Die Ausgabe ist, wie bei ls, in tabellarischer Form. spatz$ ps -o pid,comm,ppid,tty,stat,start_time PID COMMAND PPID TT STAT START 3875 bash 3866 pts/0 Ss 09: gv 3875 pts/0 S 09: gs 3934 pts/0 S 15: ps 3875 pts/0 R+ 15:26 spatz$ Bedeutung: PID COMMAND PPID TT STAT die eindeutige Prozessnummer (process id) der zum Prozess gehörende Kommandoname die Prozessnummer des Erzeugers Name des Terminals (Windows), an dem der Prozess gestartet wurde Prozessstatus R für Running (runnable, on run queue, S für Sleeping, T für Traced/Stopped, Z für Zombie (tot, aber nicht vom Vater beerdigt), s für Session Leader, + für Mitglied in der im Vordergrund laufenden Prozessgruppe Mehr dazu siehe in den jeweiligen Manualseiten! Kommando top Darstellung der CPU-intensiven Prozesse man top Kommando kill Prozesse abbrechen Mit dem Kommando kill werden Signale an Prozesse verschickt. Es gibt Signale, von Prozessen ignoriert werden können. Nicht ignoriert werden können das Signal mit der Nummer 9 (SIGKILL) gewaltsames Beenden und das Signal 23 (SIGSTOP Anhalten. Letzteres kann mit ctrl-z über die Tastatur erzeugt werden. Siehe signal.h, man kill oder kill -l). Auf diesem Weg lassen sich Hintergrundprozesse abbrechen, falls sie blockiert sind oder ungewöhnlich viel CPU -Zeit verbrauchen (Endlosschleife?). Ein blockiertes Terminal lässt sich durch ein kill-kommando für die betreffende Login-Shell, von einem anderen Terminal aus, im Normalfall wieder in einen benutzbaren Zustand zurückbringen. Die Login-Shell ist in der Spalte COMMAND als -sh oder schlicht als - aufgeführt. Als gewöhnlicher Benutzer darf man nur seine eigenen Prozesse mit kill beenden. Als Super-User darf man alle Prozesse beenden; von dieser Möglichkeit sollte man aber nur dann Gebrauch machen, wenn man sich über die Konsequenzen im klaren ist!

12 6 KAPITEL 1. DAS UNIX-PROZESS-SUBSYSTEM 1.4 Synchrone und asynchrone Prozesse Vordergrundverarbeitung In der Regel verarbeitet die Shell Kommandos synchron. Sie startet ein Kommando ( erzeugt Prozess) und erst nach dessen Beendigung meldet sie sich mit dem Prompt (beginnt sie mit dem nächsten Kommando). Der Erzeugerprozess wartet auf das Ende seines Kindprozesses. Hintergrundverarbeitung Kommandos, deren Abarbeitung länger dauert (etwa in der Statistik oder Numerik), blockieren bei synchroner Abarbeitung den interaktiven Betrieb. Schließt ein &-Zeichen das Kommando ab, wartet die Shell (Erzeugerprozess) nicht auf das Ende des gerade gestarteten Prozesses. Sie ist statt dessen sofort bereit, das nächste Kommando entgegenzunehmen. Fußangeln Hintergrundkommandos, die von der Standardeingabe lesen wollen, erhalten das Signal SIGTTIN der Prozess wird angehalten, existiert aber weiterhin! Damit wird verhindert, dass sich ein Vordergrundprozess (meistens die Shell selbst) und mehrere aktive Hintergrundprozesse um die Eingabe vom Terminal streiten. 1 # include < stdio. h> 2 # include < stdlib. h> 3 4 int main() { 5 char buf [256]; 6 puts ("Eingabe: " ); 7 fgets ( buf, 255, stdin ); 8 printf ( "gelesen : %s\n", buf ); 9 exit (0); 10 } Programm 1.2: Von stdin lesen (read-stdin.c) Mit dem Kommando fg kann ein via SIGTTIN angehaltener Prozess in den Vordergrund geholt werden: spatz$ gcc -Wall read-stdin.c spatz$ a.out & Eingabe: [2] 5150 [2]+ Stopped a.out spatz$ ps -o pid,comm,ppid,tty,stat PID COMMAND PPID TT STAT 3875 bash 3866 pts/0 Ss 5150 a.out 3875 pts/0 T 5153 ps 3875 pts/0 R+ spatz$ fg 2 a.out Und jetzt? gelesen: Und jetzt? spatz$

13 1.5. CPU-INTENSIVE PROZESSE 7 Am.: Die in eckigen Klammern angegebene Zahl bezeichnet einen sog. Job: mehrere zu einer Einheit zusammengefasste Prozesse (z.bsp. in einer Pipeline). Ein beliebiges Hintergrundkommando terminiert automatisch mit der Terminalsitzung, aus der es gestartet wurde: es erhält das SIGHUP-Signal, voreingestellte Reaktion darauf ist Termination. Dies lässt sich für Programme mit extrem langer Laufzeit auch beim Start verhindern: nohup kommando & 1.5 CPU-intensive Prozesse Prozesse laufen in einer bestimmten Prioritätsklasse diese wird vom Erzeuger-Prozess geerbt. Die Priotitätsstufen gehen von -20 (höchste Stufe bis 19 niedrigste Stufe). Mit dem Kommando nice bzw. der Funktion nice() kann die Priorität eines Prozesses verändert (herabgestuft) werden! Die Erhöhung der Priorität verlangt Privilegien! 1 # include <unistd. h> 2 # include < stdio. h> 3 # include < stdlib. h> 4 Programm 1.3: Priorität verändern (priority.c) 5 int main() { 6 printf ( "My priority is %d!\n", nice (0)); 7 perror ( "nice " ); 8 printf ( "And now it s 5 points decreased to %d!\n", nice(5)); 9 perror ( "nice " ); 10 printf ( "And now it s 8 points decreased to %d!\n", nice(8)); 11 perror ( "nice " ); 12 printf ( "Let s try to increase it by 5 to %d!\n", nice( 5)); 13 perror ( "nice " ); 14 exit (0); 15 } Ausführung von Programm 1.3: My priority is 0! nice: Success And now it s 5 points decreased to 5! nice: Success And now it s 8 points decreased to 13! nice: Success Let s try to increase it by 5 to -1! nice: Permission denied

14 8 KAPITEL 1. DAS UNIX-PROZESS-SUBSYSTEM Ausführung von Programm 1.3 als root: My priority is 0! nice: Success And now it s 5 points decreased to 5! nice: Success And now it s 8 points decreased to 13! nice: Success Let s try to increase it by 5 to 8! nice: Success Üblicherweise nutzt man das Kommando nice beim Start eines Programmes: spatz$ nice -n 3 a.out My priority is 3! nice: Success And now it s 5 points decreased to 8! nice: Success And now it s 8 points decreased to 16! nice: Success Let s try to increase it by 5 to -1! nice: Permission denied spatz$

15 1.6. PROZESSZUSTÄNDE UND ZUSTANDSÜBERGÄNGE Prozesszustände und Zustandsübergänge system call 1 return 9 exit() 2 preempt sleep reschedule 7 4 wakeup 3 enough memory swap out swap in swap out 8 fork() 6 5 wakeup not enough memory Abbildung 1.2: Prozess Zustandsmodell 1 Ausführung im User Mode (Prozess arbeitet in seinem Adressraum) 2 Ausführung im Kernel Mode (Prozess arbeitet im Kernel-Adressraum) 3 Prozess wartet auf Zuteilung der CPU durch Scheduler 4 Prozess schläft im Hauptspeicher, wartet auf bestimmtes Ereignis, das ihn aufweckt 5 Prozess ist ready to run, aber ausgelagert - muss vom Swapper (Prozess 0) in Hauptspeicher geholt werden, bevor der Kern ihn zuteilen kann 6 Prozess schläft im Sekundärspeicher (ausgelagert) 7 Prozess geht vom Kernel Mode in User Mode, aber der Kern supendiert ihn und vollzieht einen Kontext-Switch (anderer Prozess kommt zur Ausführung (bis auf Kleinigkeiten identisch mit 3) 8 Prozess neu erzeugt (Startzustand für jeden Prozess - ausgenommen Prozess 0) 9 Prozess hat exit -Aufruf ausgeführt, existiert nicht weiter, hinterlässt aber Datensatz (Exit- Code, Statistik-Angaben) für Vater-Prozess - Endzustand Übergänge System Calls und Kernel-Serviceroutinen, sowie äußere Ereignisse (Interrupts durch Uhr, HW, SW,...) können Zustandswechsel bei den Prozessen bewirken. Ein Context Switch zwischen Prozessen ist nur beim Zustandswechsel des aktiven Prozesses von Zustand (2) Running in Kernel Mode nach (4) Asleep möglich. Beim Context

16 10 KAPITEL 1. DAS UNIX-PROZESS-SUBSYSTEM Switch wählt der Scheduler den Prozess mit der größten Priorität (Zur Erinnerung: Kommando nice!) aus der Ready-Liste (alle Prozesse im Zustand (3) Ready to Run ) aus und befördert ihn in den Zustand (2). Der Scheduler teilt ihm die CPU zu und bringt ihn damit zur Ausführung. 1.7 Virtueller Adressraum Bei einfachen Prozessoren gibt es keinen Unterschied zwischen physischen und virtuellen Speicheradressen und damit auch keine scharfe Trennlinie zwischen Betriebssystem und Anwendung. Dies macht nicht nur die Speicheraufteilung unübersichtlich, es führt auch dazu, dass ein Absturz einer Anwendung, beispielsweise hervorgerufen durch einen ungültigen Zeigerzugriff, das gesamte System beeinträchtigen kann. Bessere Prozessoren besitzen eine Speicherverwaltung (memory management unit, abgekürzt MMU), die die Einrichtung virtueller Speicherumgebungen ermöglicht. Anwendungen verwenden dann virtuelle Speicheradressen, die mittels einer vom Betriebssystem konfigurierten Funktion in physische Speicheradressen abgebildet werden. virtuelle Adresse Abbildungstabelle der MMU physische Adresse Abbildung 1.3: Virtuelle und physische Adressen Abbildung 1.3 zeigt schematisch, wie typischerweise virtuelle Adressen in physische Adressen abgebildet werden. Um die Abbildung effizient (in der Hardware) durchführen zu können, wird der gesamte Speicher in Form von Seiten organisiert. Typische Seitengrößen liegen zwischen 4 und 64 Kilobyte. Entsprechend lassen sich virtuelle Adressen in zwei Teile zerlegen: Der niedrigwertige Teil spezifiziert nur die Position innerhalb einer Seite, der höherwertige gibt die Seitennummer an. In einer Tabelle der MMU können nun die Adressen physischer Seiten den Seitennummern virtueller Adressen zugeordnet werden. Mehrere Abbildungstabellen können parallel nebeneinander existieren: Eine für das Betriebssystem selbst und eine für jede Anwendung. Ein Wechsel lässt sich effizient realisieren, indem ein spezielles Hardware-Register verwendet wird, das auf die derzeitig gültige Abbildungstabelle verweist.

17 1.7. VIRTUELLER ADRESSRAUM 11 Die Abbildungstabellen einer MMU bieten typischerweise noch Platz für Zugriffsrechte. So kann geregelt werden, welche Seiten aus der Sicht einer gegebenen Abbildungstabelle vorhanden, lesbar, schreibbar und ausführbar sind. Die Gesamtheit aus Abbildungstabelle und Zugriffsrechten wird als virtueller Adressraum bezeichnet. Kommt es bei einem Zugriff zu einem Fehler durch die MMU, kann das Betriebssystem entscheiden, ob es sich dabei um einen Fehler der Anwendung handelt oder ob es den Zugriff nach einigen Manipulationen doch noch ermöglicht. Viele Tricks sind auf diese Weise möglich: Zwei oder mehr Adressräume können gemeinsam auf die gleiche Bibliothek zugreifen. Hierbei ist es sinnvoll, für den gemeinsamen Bereich nur Lese- und Ausführungsrechte einzuräumen. Teile eines belegten Adressraumes können auf die Platte ausgelagert werden. Bei einem Zugriff kommt es zuerst zu einem Fehler der MMU, der dann vom Betriebssystem abgefangen wird, um den Bereich wieder von Platte einzulesen und zur Verfügung zu stellen. Kopien von Speicherbereichen werden verzögert angefertigt, indem der zunächst noch gemeinsam gehaltene Bereich mit einem Schreibschutz versehen wird. Sobald eine Schreiboperation erfolgt, wird die betroffene Seite dupliziert und bei beiden Kopien werden dann die Schreibrechte wieder zurückgegeben. Der Kernel unterteilt den virtuellen Adressraum eines Prozesses in logische (abstrakte) Bereiche (regions) zusammenhängender Adressen; diese werden als verschiedene Objekte behandelt, die als Ganzes geschützt (protected) bzw. geteilt (shared) werden können Text, Daten, Stack bilden typischerweise separate Bereiche Der Kernel unterhält eine Tabelle mit Verweisen auf die aktiven Bereiche (u.a. Information, wo diese im physischen Speicher liegen) Jeder Prozess unterhält eine private per process region table (z.b. in der u area) Context (Abb. 1.4, S. 12) Der UNIX-Jargon unterteilt den Context in drei disjunkte Bereiche: Text, User Data und System Data. Davon hält der Kernel die für ihn wichtigen Daten ( System- Daten ) an zwei definerten Plätzen in seinem Adressraum - in der Prozesstabelle und in der User Area ( per process data region ) Text und User-Daten Bereich bilden den User Adressraum. Context Switch Ein Context Switch findet statt, wenn der Scheduler beschließt, dass ein anderer Prozess zur Ausführung kommen soll. Der Kernel hält den laufenden Prozess an, sichert dessen Context und lädt den Context des nächsten Prozesses. Bei jedem Wechsel sichert der Kernel soviel Informationen, dass er später zu dem unterbrochenen Prozess zurückkehren und dessen Ausführung fortsetzen kann. Für den Prozess bleibt die Unterbrechung völlig transparent. Ein Context Switch kann nur stattfinden, während der Prozess im Kernel Mode arbeitet. Ein Wechsel des Execution Mode ist kein Context Switch.

18 12 KAPITEL 1. DAS UNIX-PROZESS-SUBSYSTEM Prozess Tabelle für alle Prozess zusammen je Prozess resident auslagerbar (swappable) System Daten Kernel Adressraum (Programm ) Text Daten Benutzer Adressraum Abbildung 1.4: Prozess-Kontext Einträge in der Prozesstabelle: Prozesszustand (siehe obiges Beispiel bg.c: Zustand T) Information, wo der Prozess und die u area im Haupt-/Sekundärspeicher liegen sowie über Speicherbedarf ( context switch) Verwandtschaftsverhältnisse scheduling Parameter diverse Uhren ( time) u.a.m. Mehr dazu: man ps! Einträge in der u area - Reale und effektive UserID - Vektor zur Signalverarbeitung - Hinweis auf login terminal - Fehlereintrag für Fehler bei einem Systemaufruf - Rückgabewert von Systemaufrufen - Aktueller Katalog, Aktuelle Wurzel - User File Descriptor Table - u.a.m.

19 1.8. DAS PROC-FILESYSTEM Das proc-filesystem Das Verzeichnis /proc ist ein Pseudo-Dateisystem, das als Schnittstelle zu den Datenstrukturen des Kernels dient und den direkten Zugriff auf /dev/kmem erspart. Mehr dazu: man proc

20 14 KAPITEL 1. DAS UNIX-PROZESS-SUBSYSTEM

21 Kapitel 2 Prozesse unter Unix 2.1 System Call fork Syntax # include <sys/types.h> # include <unistd.h> pid_t fork (void) /* create new processes */ /* returns PID and 0 on success or -1 on error */ Beschreibung Der System Call fork erzeugt einen neuen Prozess, indem er den Context des ausführenden Prozesses dupliziert siehe Abb. 2.1 Der neu erzeugte Prozess wird als Kind-Prozess (child process), der aufrufende als Erzeugeroder Vater- oder Eltern-Prozess ( parent process) bezeichnet (die weibliche Bezeichnung Mutter -Prozess ist wenig gebräuchlich!). fork wird einmal aufgerufen und kehrt im Erfolgsfall zweimal zurück: der Aufrufer erhält als Rückgabewert die PID des erzeugten Prozesses, der Kindprozess erhält 0; im Fehlerfall (z.b. bereits zuviele Prozesse erzeugt) kehrt er einmal mit -1 zurück. 15

22 16 KAPITEL 2. PROZESSE UNTER UNIX UFDT user stack u area open Files current dir data Kernel Stack KIT shared text user stack u area open Files current dir changed root data Kernel Stack Abbildung 2.1: Ein neuer Prozess und sein Erzeuger

23 2.1. SYSTEM CALL FORK 17 Beispiel 1 # include < stdio.h> 2 # include <unistd. h> 3 # include < stdlib.h> 4 5 int main() 6 { 7 int pid = fork (); 8 Programm 2.1: Ein neuer Prozess mit fork() (fork1.c) 9 if ( pid < 0 ) { 10 perror ( " fork () can t fork a child" ); 11 exit (1); 12 } if ( pid > 0 ) { 15 printf ( "Parent: created process %d, my pid is %d\n", 16 pid, ( int ) getpid ()); 17 } 18 else / pid == 0 / { 19 printf ( "Child: after fork, my pid is %d\n", (int) getpid ()); 20 printf ( "Child: my parent is %d\n", (int) getppid ()); 21 } printf ( " finished (PID = %ld)\n", getpid ()); 24 exit ( 0 ); 25 } Ausgabe: Parent: created process 4851, my pid is 4850 finished (PID = 4850) Child: after fork, my pid is 4851 Child: my parent is 4850 finished (PID = 4851) Der Vater-Prozess terminiert hier (rein zufällig ) vor dem Sohn, der Init-Prozess erbt den verlorenen Sohn. Das Geschehen ist nicht deterministisch, hängt u.a. von der Systemauslastung ab!

24 18 KAPITEL 2. PROZESSE UNTER UNIX Oder so: Erzeuger schläft ein bisschen, bevor er terminiert 1 # include < stdio.h> 2 # include <unistd. h> 3 # include < stdlib.h> 4 Programm 2.2: fork() zum zweiten (fork2.c) 5 int main() 6 { 7 int pid ; 8 switch ( pid = fork ()) { 9 case 1: 10 perror ( " fork () can t fork a child"); 11 exit (1); 12 case 0: 13 printf ( "Child: after fork, my pid is %d\n", (int) getpid ()); 14 printf ( "Child: my parent is %d\n", (int) getppid ()); 15 break; 16 default : 17 sleep (5); 18 printf ( "Parent : created process %d, my pid is %d\n", 19 pid, ( int ) getpid ()); 20 break; 21 } 22 printf ( "%d: finished\n", getpid ()); 23 exit (0); 24 } Ausgabe: Child: after fork, my pid is 4870 Child: my parent is : finished Parent: created process 4870, my pid is : finished

25 2.1. SYSTEM CALL FORK 19 Vererbt werden: real user ID real group ID effective user ID effective group ID process group ID terminal group ID root directory current working directory signal handling settings file mode creation mask Unterschiede: process ID parent process ID eigene File Deskriptoren (Kopie) Zeit bis zu einem ggf. gesetzten alarm ist beim Kind auf 0 gesetzt Gebrauch: 1. Ein Prozess erzeugt von sich selbst eine Kopie, so dass diese die eine oder andere Operationen ausführt ( Server). 2. Ein Prozess will ein anderes Programm zur Ausführung bringen - der Kind-Prozess führt via exec ein neues Programm aus (überlagert sich selbst) ( Shell)

26 20 KAPITEL 2. PROZESSE UNTER UNIX 2.2 System Call exit Der eigentliche SystemCall ist _exit(): Syntax: #include <unistd.h> void _exit(int status); Meist verwendet man eine entsprechende Bibliotheksfunktion, die auch noch etwas aufräumt: Syntax #include <stdlib.h> void exit( int status ) /* terminate process */ /* does NOT return */ /* 0 <= status < 256 */ Beschreibung Mit dem System Call exit beendet ein Prozess aktiv seine Existenz. Von diesem System Call gibt es keine Rückkehr in den User Mode. Unterscheide: System Call _exit und C-Bibliotheks-Funktion exit C-Funktion leert erst alle Puffer und ruft dann den System Call auf. Der Kernel gibt den User Adressraum (Text und User-Daten) des Prozesses frei, sowie auch einen Teil des System-Daten Bereichs (User Area). Übrig bleibt nur der Eintrag in der Prozesstabelle, der den Beendigungsstatus und die Markierung des Prozesses als Zombie enthält, bis ihn der init-prozess erbt und abräumt. Beispiel: 1 # include < stdio.h> 2 # include <unistd. h> 3 # include < stdlib.h> 4 Programm 2.3: Beenden mit exit() (exit1.c) 5 int main() { 6 printf ( "This is some text to write on stdout"); 7 exit (0); 8 } 1 # include < stdio.h> 2 # include <unistd. h> 3 # include < stdlib.h> 4 Programm 2.4: Beenden mit _exit() (exit2.c) 5 int main() { 6 printf ( "This is some text to write on stdout"); 7 _exit (0); 8 }

27 2.2. SYSTEM CALL EXIT 21 Manchmal ist es praktisch, selbst vor dem (selbst gewollten) Beenden noch einiges zu erledigen: Funktion atexit() 1 # include < stdio.h> 2 # include <unistd. h> 3 # include < stdlib.h> 4 5 void do_on_exit (void){ 6 printf ( "That s all BaBa\n"); 7 } 8 void bye(void) { 9 printf ( "That was all, folks\n" ); 10 } 11 Programm 2.5: Beenden mit eigenem Aufräumen (exit3.c) 12 int main( int argc, char argv) { 13 if ( ( atexit ( do_on_exit ) == 0 ) && ( atexit (bye) == 0)){ 14 printf ( " functions to be called at normal termination set\n"); 15 printf ( "================================================\n"); 16 } 17 if ( argc == 1 ) 18 printf ( " exit 1\n"), exit (1); 19 if ( argc == 2 ) 20 printf ( " exit 2\n"), exit (2); 21 printf ( "end of text reached\n"); 22 exit (0); 23 } spatz$ gcc -Wall exit3.c spatz$ a.out functions to be called at normal termination set ================================================ exit 1 That was all, folks That s all -- BaBa spatz$ a.out functions to be called at normal termination set ================================================ end of text reached That was all, folks That s all -- BaBa spatz$

28 22 KAPITEL 2. PROZESSE UNTER UNIX 2.3 System Call exec Syntax #include <unistd.h> extern char ** environ; int execl( char *path, /* path of program file */ char *arg0, /* 1st argument (cmd name) */ char *arg1,..., /* 2nd,... argument */ char *argn, /* last argument */ (char *) 0 ); int execlp( char *filename, /* name of program file */ char *arg0, char *arg1,... char *argn, (char *) 0 ); int execle( char *path, /*path of program file */ char *arg0, char *arg1,... char *argn, (char *) 0, char **envp /* pointer to environment */ ); int execv( char *path, /* path of program file */ char *argv[]; /* pointer to array of argument */ ); int execvp( char *filename, /* name of program file */ char *argv[]; /* pointer to array of argument */ ); int execve( char *path, /* path of program file */ char *argv[], /* pointer to array of argument */ char *envp[], /* pointer to environment */ ); /* all return with -1 on error only */ Beschreibung Die exec - System Calls überlagern im Context des ausführenden Prozesses den Text und den User-Daten Bereich mit dem Inhalt der Image-Datei. Anschließend bringen sie die neuen Instruktionen zur Ausführung.

29 2.3. SYSTEM CALL EXEC 23 Zusammenhang execlp(file, arg,...,0) execl(path,arg,..., 0) execle(path, arg,...,0,envp) create argv create argv create argv execvp(file,argv) convert file to path execv(path,argv) add envp execve(path,argv,envp) system call Abbildung 2.2: Die exec()-familie 1. Die drei Funktionen in der oberen Reihe enthalten jedes Kommando-Argument als separaten Parameter, der NULL-Zeiger ( (char *) 0 -!!!) schließt die variable Anzahl ab (kein argc!). Die drei Funktionen in der unteren Reihe fassen die Kommando-Argumente in einen Parameter argv zusammen, das Ende wird entsprechend wieder durch den NULL-Zeiger definiert. 2. Die zwei Funktionen in der linken Spalte definieren die Programm-Datei nur durch den Datei-Namen; diese wird über die Einträge in der Umgebungsvariable PATH gesucht (konvertiert in vollen Pfadnamen). Falls diese nicht gesetzt ist, wird als default-suchpfad :/bin:/usr/bin genommen. Enthält das Argument path einen slash, so wird die Variable PATH nicht verwendet. 3. Bei den vier Funktion in den beiden linken Spalten wird das Environment über die externe Variable environ an das neue Programm übergeben. Die beiden Funktionen in der rechten Spalte spezifizieren explizit eine Environment-Liste (muss ebenfalls mit einem NULL- Zeiger abgeschlossen sein).

30 24 KAPITEL 2. PROZESSE UNTER UNIX Vererbung (erhalten bleibt) bei exec(): process ID parent process ID process group ID terminal group ID time left until an alarm clock signal root directory current working directory file mode creation mask real user ID real group ID file locks Mögliche Änderungen mit exec(): set user ID / set group ID - bit der neuen Datei gesetzt effective user ID auf user ID des Besitzers der Programm-Datei effective group ID auf group ID des Besitzers der Programm-Datei Signale: Terminieren bleibt Terminieren Ignorieren bleibt Ignorieren Speziell abgefangene Signale werden wegen des Überlagerns auf Terminieren gesetzt

31 2.3. SYSTEM CALL EXEC 25 Beispiel: 1 # include < stdio.h> 2 # include <unistd. h> 3 # include < stdlib.h> 4 Programm 2.6: Neues Programm ausführen (exec1.c) 5 void exec_test () / execl version / { 6 printf ( "The quick brown fox jumped over " ); 7 fflush ( stdout ); 8 execl ( "/bin/echo", "echo", "the", "lazy", "dogs.", 9 (char ) 0 ); 10 perror ( " execl can t exec /bin/echo" ); 11 } int main() { 14 int pid ; pid = fork (); 17 if ( pid < 0) { 18 perror ( "fork () can t fork"); 19 exit (1); 20 } 21 if ( pid > 0) 22 printf ( "PP: Parent PID: %d / Child PID: %d\n", 23 ( int ) getpid (), pid ); 24 else / pid == 0 / { 25 printf ( "CP: Child PID: %d\n", (int) getpid () ); 26 exec_test (); 27 } 28 exit (0); 29 } Ausgabe: CP: Child-PID: 4881 The quick brown fox jumped over PP: Parent-PID: 4880 / Child-PID: 4881 the lazy dogs. Lässt man in Programm 2.6 den Funktionsaufruf fflush() weg (siehe Programm 2.7, S. 26), so passiert folgendes: the lazy dogs. PP: Parent-PID: 4914 / Child-PID: 4915

32 26 KAPITEL 2. PROZESSE UNTER UNIX 1 # include < stdio.h> 2 # include <unistd. h> 3 # include < stdlib.h> 4 5 Programm 2.7: Programm 2.6 ohne fflush() (exec2.c) 6 void exec_test () / execl version / { 7 printf ( "The quick brown fox jumped over " ); 8 // fflush ( stdout ); 9 execl ( "/bin/echo", "echo", "the", "lazy", "dogs.", 10 (char ) 0 ); 11 perror ( " execl can t exec /bin/echo" ); 12 } int main() { 15 int pid ; pid = fork (); 18 if ( pid < 0) { 19 perror ( "fork () can t fork"); 20 exit (1); 21 } 22 if ( pid > 0) 23 printf ( "PP: Parent PID: %d / Child PID: %d\n", 24 ( int ) getpid (), pid ); 25 else / pid == 0 / { 26 printf ( "CP: Child PID: %d\n", (int) getpid () ); 27 exec_test (); 28 } 29 exit (0); 30 } Erläuterung: Wenn in eine Datei oder in eine Pipe geschrieben wird, erledigt printf() die Ausgabe gepuffert (in Blöcken zu 512 Bytes), Bei der Ausgabe ans Terminal (s.o.) kann die Ausgabe auch dann bruchstückhaft (oder gar nicht) erscheinen, wenn Zeilenpufferung stattfindet. Normalerweise führt dies zu keinen Problemen; der letzte Puffer wird automatisch entleert, wenn der Prozess endet. In obigem Beispiel war der Prozess noch nicht beendet, als der exec-aufruf erfolgte der im Benutzerdatensegment liegende Puffer wurde überlagert, bevor er entleert werden konnte.

33 2.3. SYSTEM CALL EXEC 27 1 # include < stdio. h> 2 # include <unistd. h> 3 4 int main() { 5 char argv [4]; 6 argv [0] = "my ls"; 7 argv [1] = "?"; 8 argv [2] = ".c" ; 9 argv [3] = NULL; 10 execvp ( " ls ", argv ); 11 perror ( "exevp"); 12 return 1; 13 } Programm 2.8: Argumentvektor übergeben (exec3.c) Für das Programm 2.10 brauchen wir das Programm 2.9 zur Ausgabe des Environment: 1 # include < stdio. h> 2 3 int main() { 4 extern char environ ; 5 while( environ!= NULL) 6 printf ( "%s\n", environ ++); 7 return 0; 8 } Programm 2.9: Environment ausgeben (env.c) 1 # include < stdio. h> 2 # include <unistd. h> 3 4 int main() { 5 char envp [2]; 6 envp[0] = "x=1"; 7 envp[1] = NULL; 8 execle ( "./env", "env", NULL, envp); 9 perror ( " execle " ); 10 return 1; 11 } Programm 2.10: Umgebung ändern (exec4.c) Übersetzen und Ausführen: spatz$ gcc -Wall -o env env.c spatz$ gcc -Wall exec4.c spatz$ a.out x=1 spatz$ echo $x # und jetzt? spatz$

34 28 KAPITEL 2. PROZESSE UNTER UNIX Bei den Nicht-exec?e() -Varianten wird die Umgebung über environ übergeben (siehe Programm # include < stdio. h> 2 # include <unistd. h> 3 4 int main() { 5 extern char environ ; 6 char envp [2]; 7 envp[0] = "x=100"; 8 envp[1] = NULL; 9 environ = envp; 10 execl ( "./env", "env", NULL); 11 perror ( " execl " ); 12 return 1; 13 } Programm 2.11: Umgebung ändern bei execl() (exec5.c)

35 2.4. SYSTEM CALL WAIT System Call wait Syntax #include <sys/types.h> #include <sys/wait.h> pid_t wait( int * status ); pid_t waitpid(pid_t pid, int *status, int options); Beschreibung von wait() (zu waitpid() siehe Manuals) Falls der Aufrufer von wait() keinen Kind-Prozess erzeugt hat, kehrt wait() mit -1 zurück. Kehrt zurück, falls ein Kind bereits vorher terminierte Ansonsten blockiert wait() (wird vom Kernel suspendiert), bis einer der erzeugten Prozesse terminiert. In diesem Fall liefert wait() die PID des terminierten Kind-Prozesses. Über das Argument von wait() erhält der Prozess Informationen über den terminierten Kind-Prozess (Abb. 2.3) Termination durch Argument von exit() 0x00 exit() 0x00 Signalnummer core Flag Signal Signalnummer 0x7F Prozess angehalten (nur in Spezialfällen) NB: Signal-Nummern sind größer 0! Abbildung 2.3: Information über Kind-Prozess

36 30 KAPITEL 2. PROZESSE UNTER UNIX Ein erstes Beispiel mit wait(): 1 # include <sys/types. h> 2 # include <sys/wait. h> 3 # include <unistd. h> 4 # include < stdlib.h> 5 # include < stdio.h> 6 7 int main() { 8 pid_t pid ; 9 10 if ( ( pid = fork () ) < 0 ) 11 perror ("fork" ), exit (1); 12 Programm 2.12: Auf Kind-Prozess warten (wait1.c) 13 if ( pid == 0) { // Child 14 printf ("Child my pid is %d\n", (int) getpid()); 15 sleep (10); 16 return 0; 17 } else { 18 // Parent 19 int status, signal_nr, exit_st ; 20 pid_t child_pid = wait(& status ); 21 if ( child_pid == 1) 22 perror ( "wait" ), exit (2); 23 signal_nr = status & 0x3f ; 24 exit_st = ( status >> 8) & 0 xff ; 25 printf ("Parent child with pid %d finished\n", (int) child_pid ); 26 printf ("SignalNr: %d ExitSt: %d\n", signal_nr, exit_st ); 27 return 0; 28 } 29 } Makros erleichtern die Dekodierung: (aus /usr/include/sys/wait.h) WIFEXITED(status): liefert TRUE, wenn der Kindprozess normal terminierte (freiwilliges exit() oder _exit() bzw. Rückkehr aus der main()-funktion (siehe Programm 2.13, 31) WIFSIGNALED(status): liefert TRUE, wenn der Kindprozess durch ein nicht abgefangenes Signal zur Termination kam WEXITSTATUS(status: liefert den durch exit() oder return in der main()-funktion gesetzten Exit-Status kann nur benutzt werden, wenn WIFEXITED(status) TRUE lieferte WTERMSIG(status: liefert die Nummer des Signals, durch das der Kindprozess zur Termination kam kann nur benutzt werden, wenn WEXITSTATUS(status) TRUE lieferte

37 2.4. SYSTEM CALL WAIT 31 1 # include < stdio.h> 2 # include < stdlib.h> 3 # include <unistd. h> 4 # include <sys/wait. h> 5 Programm 2.13: Auf Kind-Prozess warten (forkandwait.c) 6 int main() { 7 pid_t child, pid ; 8 int stat ; 9 10 child = fork (); 11 if ( child == 1) { 12 perror ( "unable to fork" ); exit (1); 13 } 14 if ( child == 0) { / child process / 15 srand ( getpid ()); char randval = rand (); 16 printf ( "Child is going to exit with %d\n", randval & 0xFF); 17 exit ( randval ); 18 } / parent process / 21 pid = wait(& stat ); 22 if ( pid == child ) { 23 if (WIFEXITED(stat)) { 24 printf ( " exit code of child = %d\n", WEXITSTATUS(stat)); 25 } else { 26 printf ( " child terminated abnormally\n"); 27 } 28 } else { 29 perror ( "wait" ); 30 } 31 return 0; 32 } waitpid(): pid_t waitpid(pid_t pid, int * status, int options); Parameter pid: < 1 auf alle Kind-Prozesse, warten, deren Prozessgruppen-ID gleich dem Betrag von pid ist 1 auf alle Kind-Prozesse warten (wie wait()) 0 auf alle Kind-Prozesse warten, deren Prozessgruppen-ID gleich der der Aufrufers ist > 0 auf den Kindprozess mit der angegebenen pid warten Parameter options: bitweises ODER folgender Konstanten WNOHANG: nicht blockieren, falls kein Kindprozess terminierte

38 32 KAPITEL 2. PROZESSE UNTER UNIX WUNTRACED: auch bei gestoppten Kindprozessen zurückkehren Ansonsten wird der dritte Parameter einfach auf 0 gesetzt! Beispiel mit waitpid() 1 # include <sys/types. h> 2 # include <sys/wait. h> 3 # include <unistd. h> 4 # include < stdlib.h> 5 # include < stdio.h> 6 7 int main() { 8 pid_t pid ; 9 Programm 2.14: Warten mit waitpid() (waitpid1.c) 10 if ( ( pid = fork () ) < 0 ) 11 perror ("fork" ), exit (1); if ( pid == 0) { 14 printf ("Child my pid is %d\n", (int) getpid()); 15 sleep (20); 16 printf ("Child going to terminate!\n"); 17 return 0; 18 } else { 19 // Parent 20 pid_t child_pid ; 21 int status ; 22 if (( child_pid = waitpid ( pid, & status, WUNTRACED )) < 0) 23 perror ( "wait" ), exit (2); 24 // WUNTRACED: return for children which are stopped, and 25 // whose status has not been reported printf ("Parent child with pid %d finished, ", (int) child_pid ); 28 if WIFEXITED(status) 29 printf ( "with exit value %d\n", WEXITSTATUS(status) ); 30 if WIFSIGNALED(status) 31 printf ( "caused by signal %d\n", WTERMSIG(status) ); 32 if WIFSTOPPED(status) 33 printf ( "not really, is stopped by %d and now dead!\n", 34 WSTOPSIG(status) ); 35 sleep (50); 36 printf ("Parent going to terminate!\n"); 37 return 0; 38 } 39 } Aufgaben des Kernel, wenn ein Prozess exit() aufruft: Wartet der Erzeuger-Prozess mit wait(), so wird er von der Termination des (eines) Kind- Prozesses benachrichtigt (wait() kehrt zurück). Im Argument von wait() wird der exit-status (das Argument in exit()) geliefert, als Rückgabewert die PID des Kind-Prozesses.

39 2.4. SYSTEM CALL WAIT 33 Wartet der Erzeuger-Prozess nicht, so wird der Kind-Prozess als Zombie-Prozess markiert. Der Kernel gibt dessen Ressourcen frei, bewahrt aber den exit-status (Beendigungsstatus) in der Prozesstabelle auf. Sind process ID, process group ID, terminal group ID des terminierenden Prozesses alle gleich, so wird das Signal SIGHUP an jeden Prozess mit gleicher process group ID gesandt. Wenn der Erzeuger vor dem Kind-Prozess terminiert: Die PPID der Kind-Prozesse wird auf 1 gesetzt, der init-prozess erbt die Waisen in Programm 2.15 wird ein Waisenkind erzeugt. Übersetzung und Ausführung liefert: spatz$ gcc -Wall orphan.c spatz$ a.out Hi, my parent is 5174 spatz$ My parent is now 1 spatz$ Der init-prozess terminiert nie, und wenn doch, so wäre es im Multiuser-Betrieb nicht mehr möglich, dass sich weitere Benutzer anmelden. In 4.3BSD würde es zu einem automatischen reboot kommen. 1 # include < stdio.h> 2 # include < stdlib.h> 3 # include <unistd. h> 4 Programm 2.15: Ein Prozess wird zum Waisenkind (orphan.c) 5 int main() { 6 pid_t child ; 7 child = fork (); 8 if ( child == 1) { 9 perror ( "fork" ); exit (1); 10 } 11 if ( child == 0) { 12 printf ( "Hi, my parent is %d\n", (int) getppid ()); 13 sleep (5); 14 printf ( "My parent is now %d\n", (int) getppid ()); 15 } 16 sleep (3); 17 exit (0); 18 } Wenn ein Prozess mit exit() terminiert, so wird dem Erzeuger das Signal SIGCLD (SIGCHLD) zugeleitet (default-reaktion: ignorieren). In System V ist es möglich, dass ein Prozess verhindert, dass seine Kind-Prozesse zu Zombies werden; dazu hat er nur den Aufruf signal(sigcld, SIG_IGN)

40 34 KAPITEL 2. PROZESSE UNTER UNIX abzusetzen; der exit-status der Kindprozesse wird bei deren Termination nicht weiter aufbewahrt. Programm 2.16, S. 34, demonstriert die Erzeugung eines Zombies. Der neu erzeugte Prozess verabschiedet sich sofort mit exit(), während der übergeordnete Prozess mit Hilfe eines sleep()- Aufrufes sich für 60 Sekunden zur Ruhe legt. Während dieser Zeit verbleibt der Unterprozess im Zombie-Status, wie das ps-kommando belegt: spatz$ genzombie & 4792 [1] 4791 spatz$ ps -f UID PID PPID C STIME TTY TIME CMD swg :27 pts/3 00:00:00 bash -i swg :27 pts/3 00:00:00 genzombie swg :27 pts/3 00:00:00 [genzombie] <defunct> swg :28 pts/3 00:00:00 ps -f spatz$ ps -f # 1 Minute spaeter UID PID PPID C STIME TTY TIME CMD swg :27 pts/3 00:00:00 bash -i swg :28 pts/3 00:00:00 ps -f [1]+ Done genzombie spatz$ 1 # include < stdio.h> 2 # include < stdlib.h> 3 # include <unistd. h> 4 5 int main() { 6 pid_t child = fork (); 7 if ( child == 1) { 8 perror ( "fork" ); exit (1); 9 } 10 if ( child == 0) exit (0); 11 / parent : / 12 printf ( "%d\n", child ); 13 sleep (60); 14 exit (0); 15 } Programm 2.16: Erzeugen eines Zombie-Prozesses (genzombie.c)

41 2.5. N-DAMEN-PROBLEM MIT PROZESSEN n-damen-problem mit Prozessen Ein etwas trickreicheres Beispiel ist in Programmtext 2.18 zu finden. Beim n-damen-problem geht es darum, n Damen auf einem n n Schachbrett so unterzubringen, dass sie sich gegenseitig nicht bedrohen. Nach den üblichen Schachregeln bedroht ein Dame horizontal, vertikal und diagonal (siehe Abb. 2.4) Abbildung 2.4: n-damen-problem: das Schachbrett Die Bedrohungssituation kann leicht festgestellt werden: Sei (i, j) eine mit einer Dame zu besetzende Position. Bedroht wären dadurch: Zeile i Spalte j alle Felder (k, l) mit (1) k l = i j (Diagonale von links oben nach rechts unten) (2) k + l = i + j (Diagonale von links unten nach rechts oben) Meist wird das Problem im Backtracking-Verfahren gelöst, bei dem rekursiv alle Varianten durchprobiert werden (siehe Programm 2.17, S. 36). Klappt es mit einem Weg nicht, werden die bereits gesetzten Damen wieder sukzessive abgebaut, bis sich andere bislang noch nicht getestete Varianten eröffnen (Abb. 2.5, S. 36). Zunächst soll zum Verständnis die klassische Lösung betrachtet werden: Der gesamte Stand auf dem n n Schachbrett (im folgenden n = 8) wird in einer Reihe globaler Variablen verwaltet. Da es nur um die Feststellung geht, ob z.b. eine Zeile belegt ist oder nicht, kann man Bitmaps verwenden ist das i-te Bit auf 1, so ist die i-te Zeile / Spalte bedroht (siehe Abb. 2.6, S. 36). Die Bedrohungssituation in den beiden Diagonalen kann ebenfalls in einer Integer-Zahl (Bitmap) geführt werden:

42 36 KAPITEL 2. PROZESSE UNTER UNIX Start (0,0) (0,1) (0,2) (0,3) (1,0) (1,1) (1,0) (1,1) (2,0) (2,1) Abbildung 2.5: n-damen-problem: der Lösungsbaum int row, col; Bit: Abbildung 2.6: n-damen-problem: Zeilen-/Spaltenbedrohung Diagonalen von links unten nach rechts oben: Werte von 0 bis 2 (n 1) (Variable diags2) Diagonalen von links oben nach rechts unten: Werte von (n 1) bis +(n 1), ins Positive mit (n 1) transformiert: Werte von 0 bis 2 (n 1) (Variable diags1) Dies ergibt dann das Programm 2.17, S. 36. Anzumerken bleibt, dass von den 92 Lösungen bei n = 8 sehr viele symmetrisch sind. 1 # include < stdio.h> 2 Programm 2.17: 8-Damen klassisch (classicqueens.c) 3 # define INSET(member,set) ((1<<(member))&set) 4 # define INCL(set, member) (( set ) = 1<<(member)) 5 # define EXCL(set,member) (( set ) &= ( ~(1<<(member))) ) 6 # define size struct positions { int row, col ; } pos[ size ]; / queen positions / 9 int rows = 0; / bitmaps of [0.. n 1] / 10 int diags1 = 0; / bitmap of [0..2 ( n 1)] used for row col+n 1 / 11 int diags2 = 0; / bitmap of [0..2 ( n 1)] used for row+col / 12 / rows( i ) == 1 means: line i is threatened 13 diags1 ( i j+size 1) == 1 : diag through ( i, j ) from left up 14 to right down is threatened 15 diags1 ( i+j) == 1 : diag through ( i, j ) from left down to 16 right up is threatened 17 / void printpos () { 20 int j = 0; 21 printf (" (next) positioning :\n" ); 22 printf ("Row : Col\n"); 23 for ( j = 0; j < size ; j++ ) 24 printf ( "%3d : %3d\n", pos[j].row, pos [ j ]. col );

43 2.5. N-DAMEN-PROBLEM MIT PROZESSEN } void try ( int i ){ 28 int j ; 29 for ( j = 0; j < size ; j ++){ 30 if (!( INSET(j,rows )) && 31!( INSET((i+j ), diags2 )) && 32!( INSET((i j+size 1), diags1 )) 33 ) { 34 pos[ j ]. row = j ; pos [ j ]. col = i ; 35 INCL(rows, j ); INCL(diags2, i +j ); INCL(diags1, i j+size 1); 36 if ( i < size 1 ) 37 try ( i +1); 38 else 39 printpos (); 40 EXCL(rows,j ); EXCL(diags2, i +j ); EXCL(diags1, i j+size 1); 41 } 42 } 43 } int main (){ 46 try (0); 47 return 0; 48 } Diese sequentielle Vorgehensweise lässt sich auch parallelisieren. Auf der ersten Reihe auf dem Schachbrett gibt es n mögliche Positionen für die erste Dame. Entsprechend können n Prozesse erzeugt werden, die sich jeweils um einen Teilbaum (Abb. 2.5, S. 36) kümmern. Das Verfahren kann auch weiter mit Parallelisierung fortgesetzt werden. Bei größeren Brettgrößen stößt dies jedoch rasch an die Grenze der Prozesstabelle, so dass die vorgestellte Lösung nicht wirklich skalierbar ist (und auf den Unterrichtsrechnern auf keinen Fall ausprobiert werden sollte). Um das Problem zu vereinfachen, soll das Programm nur die Zahl der gefundenen Lösungen ermitteln. Eine Ausgabe der gefundenen Lösungen ist nicht trivial, da hier eine Synchronisierung stattfinden müsste: wenn viele Prozesse konkurrierend versuchen, Ausgabe zu produzieren, würde dies zu einer wilden Textmischung führen. Über den Exit-Wert kann für nicht zu große Werte von n bequem die Zahl der gefundenen Lösungen eines Teilbaumes zurückgeliefert werden, so dass der übergeordnete Prozess die Gelegenheit hat, die Einzelresultate zusammenzuzählen. Zur Prozesslösung: Der gesamte Stand auf dem n n Schachbrett (im folgenden wieder n = 8) wird in einer Reihe lokaler Variablen von main() verwaltet. Abgesehen von col wird keine dieser Variablen im ersten Prozess modifiziert. Stattdessen erfolgen sämtliche Änderungen nur bei Unterprozessen, die dank der Magie des fork()- Aufrufes auf einer Kopie davon arbeiten. Die entscheidende Schleife beginnt in Zeile 21. Auf den Reihen 0 bis nofqueens 1 sind die Damen bereits gesetzt. Als nächstes ist nun eine Dame auf Reihe nofqueens zu plazieren. Dazu geht die Schleife sämtliche Spaltenpositionen durch und überprüft in den Zeilen 22 bis 25, ob die einzelnen Varianten im Konflikt zu den bereits plazierten Damen stehen.

44 38 KAPITEL 2. PROZESSE UNTER UNIX Falls eine weitere Dame gefahrlos gesetzt werden kann, dann wird diese Variante einem Prozess überlassen, der in Zeile 26 neu erzeugt wird. Dieser setzt die Dame innerhalb der Datenstruktur in den Zeilen 31 bis 35. Wenn damit size Damen gesetzt sind, ist eine Lösung gefunden worden und der Unterprozess signalisiert dies mit einem exit(1) in Zeile 36. Ansonsten gibt es einen Sprung zur Schleife auf Zeile 21, die dann die nächste Zeile zu besetzen versucht. Die Anweisungen ab Zeile 41 werden von allen Prozessen ausgeführt mit Ausnahme derjenigen, die entweder bei fork() scheitern oder selbst eine Lösung gefunden haben. Die Aufgabe besteht hier darin, all die Zahlen der gefundenen Lösungen der einzelnen Unterprozesse zu addieren. Dies erledigt die while-schleife auf Zeile 43, die wait() solange aufruft, bis -1 zurückgeliefert wird, d.h. alle Unterprozesse berücksichtigt worden sind. Die Exit-Werte werden in Zeile 47 einfach aufaddiert. Es findet nur eine zusätzliche Überprüfung statt, ob der Unterprozess normal terminierte und selbst keine Probleme mit der Erzeugung weiterer Unterprozesse hatte. So ein Problem wird mit einem Exit-Wert von 255 in Zeile 28 signalisiert. Dieses Beispiel ist nicht zur Nachahmung geeignet, da es sehr schnell am Mangel an zur Verfügung stehender Einträge in der Prozesstabelle scheitert. Dennoch ist der Ansatz brauchbar, wenn es darum geht, auf einer Mehrprozessor-Maschine die vorhandenen Ressourcen auszunutzen. Allerdings macht es dann nicht Sinn, mehr Prozesse zu erzeugen als tatsächlich Prozessoren vorhanden sind. Ein pragmatischer Ansatz könnte es sein, die erste Stufe der Rekursion (also hier die erste Reihe auf dem Schachbrett) zu parallelisieren und ansonsten sequentiell weiterzuarbeiten. Elegant wird der Programmtext jedoch durch so einen Mischansatz nicht. Ein weiterer Problempunkt ist die Rückgabe gefundener Lösungen. Dies geht entweder nur unter Verwendung von Dateien (leicht zu programmieren, jedoch nicht sehr effizient) oder der direkten Kommunikation zwischen den Prozessen (effizienter, jedoch leider nicht einfach zu programmieren). Für eine Dateiausgabe könnte der folgende Text zwischen Zeile 35 und 36 eingefügt werden: if (col == size) { char file[32]; FILE * fp; sprintf(file,"%d",getpid()); fp = fopen(file,"a"); fprintf(fp,"process %d:\n", getpid()); for (i=0; i< size; i++) fprintf(fp,"row %d / col %d\n", pos[i].row, pos[i].col); } Weitere Minuspunkte ergeben sich aus der Verwendung einer goto-anweisung und gemeinsamer Programmpfade nach dem fork().

45 2.5. N-DAMEN-PROBLEM MIT PROZESSEN 39 Programm 2.18: Rekursion mit Unterprozessen (forkqueens.c) 1 # include < stdio.h> 2 # include < stdlib.h> 3 # include <sys/wait. h> 4 # include <unistd. h> 5 # define INSET(member,set) ((1<<(member))&set) 6 # define INCL(set, member) (( set ) = 1<<(member)) 7 8 int main() { 9 const int size = 4; / square size of the board / 10 struct { int row, col ; } pos[ size ]; / queen positions / 11 / a queen on (row, col ) threatens a row, a column, and 2 diagonals ; 12 rows and columns are characterized by their number (0.. n 1), 13 the diagonals by row col+n 1 and row+col, 14 (n is a shorthand for the square size of the board ) 15 / 16 int rows = 0, cols = 0; / bitmaps of [0.. n 1] / 17 int diags1 = 0; / bitmap of [0..2 ( n 1)] used for row col+n 1 / 18 int diags2 = 0; / bitmap of [0..2 ( n 1)] used for row+col / 19 int row; int col = 0; setnextqueen : for (row = 0; row < size ; ++row) { 22 if (INSET(row, rows )) continue; 23 if (INSET(col, cols )) continue; 24 if (INSET(row col + size 1, diags1 )) continue; 25 if (INSET(row + col, diags2 )) continue; 26 int child = fork (); 27 if ( child == 1) { 28 perror ( "fork" ); exit (255); 29 } 30 if ( child == 0) { 31 INCL(rows, row ); INCL(cols, col ); 32 INCL(diags1, row col + size 1); 33 INCL(diags2, row + col ); 34 pos[row ].row = row; pos[row ]. col = col ; 35 ++col ; / set next queen in next col / 36 if ( col == size ) exit (1); / solution found / 37 goto setnextqueen ; 38 } 39 } / count the results of all children / 42 int stat ; pid_t child ; int nofsolutions = 0; 43 while (( child = wait(& stat )) > 0) { 44 if (! WIFEXITED(stat)) continue; 45 int exitval = WEXITSTATUS(stat); 46 if ( exitval == 255) exit (255); 47 nofsolutions += exitval ; 48 } 49 exit ( nofsolutions ); 50 }

46 40 KAPITEL 2. PROZESSE UNTER UNIX 2.6 Datei-Umlenkung Viele Unix-Kommandos sind als Filter konzipiert: wenn nichts anderes über die Kommandozeile angegeben ist, so lesen sie von der Standardeingabe (0), schreiben auf die Standardausgabe (1) und setzen ihre Fehler- / Diagnoseausgaben auf Standarderror (2) sofern dies jeweils Sinn macht. Programm 2.19 zeigt eine einfache Anwendung: wir setzen den Filedeskriptor 0 auf eine Datei und starten dann ein Programm, das von 0 liest. Übersetzung und Ausführung: spatz$ gcc -Wall umlenkung1.c spatz$ a.out # include <stdio.h> # include <fcntl.h> # include <unistd.h> spatz$ 1 # include < stdio. h> 2 # include < fcntl. h> 3 # include <unistd. h> 4 # include < stdlib. h> 5 # include <sys/wait. h> 6 Programm 2.19: Über 0 aus einer Datei lesen (umlenkung1.c) 7 int main() { 8 pid_t pid ; 9 if ( ( pid = fork () ) < 0 ) 10 perror ("fork" ), exit (1); 11 if ( pid == 0 ) { 12 close (0); 13 if ( open( "umlenkung1.c", O_RDONLY) < 0) 14 perror ( "open"), exit (2); 15 execlp ( "head", "head", " 3", NULL); 16 perror ( "exec" ), exit (3); 17 } else { 18 wait (0); 19 return 0; 20 } 21 }

47 2.6. DATEI-UMLENKUNG 41 Das geht natürlich auch ausgabeseitig, wie Programm 2.20 (S. 41) zeigt: spatz$ gcc -Wall umlenkung2.c spatz$ a.out Ein kleiner Text auf 2 Zeilen spatz$ cat XXX Ein kleiner Text auf 2 Zeilen spatz$ 1 # include < stdio. h> 2 # include < fcntl. h> 3 # include <unistd. h> 4 # include <sys/types. h> 5 # include <sys/wait. h> 6 Programm 2.20: Über 1 in eine Datei schreiben (umlenkung2.c) 7 int main() { 8 pid_t pid ; 9 if ( ( pid = fork () ) < 0 ) { 10 perror ("fork" ); 11 return 1; 12 } 13 if ( pid == 0 ) { 14 close (1); 15 if ( open( "XXX", O_WRONLY O_CREAT O_TRUNC, 0666) < 0) { 16 perror ( "open"); 17 return 2; 18 } 19 execlp ( " cat ", " cat ", NULL); 20 perror ( "exec" ); 21 return 3; 22 } else { 23 wait (0); 24 return 0; 25 } 26 }

48 42 KAPITEL 2. PROZESSE UNTER UNIX 2.7 Eine kleine Shell (tinysh) Allgemeines Wenn man in der Shell ein Kommando startet (oder auf einer grafischen Oberfläche ein Programm durch Anklicken startet), so läuft die traditionelle Erzeugung eines Prozesses ab: Erzeuge einen neuen Prozess mit einem gegebenen Programmtext. (Die Kombination aus fork() und exec(). Einrichtung der Umgebung für den neuen Prozess. Start des neuen Prozesses. Shell fork() Umgebung einrichten exec() wait() Anwendung exit() Abbildung 2.7: Start einer Anwendung von der Shell Die Trennung in fork() und exec() eröffnet die Möglichkeit, dass der Programmtext des übergeordneten Prozesses direkt im neu erzeugten Prozess die Umgebung vorbereitet, die dann bei exec() von dem gleichen Prozess mit dem neuen Programmtext vorgefunden wird. Wie Abbildung 2.7 zeigt, wird genau dies von den UNIX-Shells genutzt, um die Umgebung für Anwendungen einzurichten. Der übergeordnete Prozess der Shell wartet dann normalerweise mit wait() auf das Ende des untergeordneten Prozesses Anforderungen Jede Eingabezeile ist entweder leer oder enthält genau ein Kommando. Die einzelnen Worte (Token) sind durch Leerzeichen getrennt. Ein-/Ausgabe-Umlenkung von / auf Dateien ist möglich. Dazu dienen spezielle Worte: beginnt ein Wort mit einem <, so sind die darauf folgenden Zeichen der Name der Eingabedatei; beginnt ein Wort mit >, so sind die darauf folgenden Zeichen der Name der Ausgabedatei sollte die Datei bereits existieren, wird sie auf Länge 0 verkürzt existiert die Datei noch nicht, wird sie angelegt;

49 2.7. EINE KLEINE SHELL (TINYSH) 43 beginnt ein Wort mit >>, so sind die darauf folgenden Zeichen der Name der Ausgabedatei; das Schreiben erfolgt am Ende der Datei; existiert die Datei noch nicht, wird sie angelegt; Es wird der Exit-Status des Programmes ausgegeben, falls dieser ungleich 0 ist. Wenn das Programm nicht gestartet werden kann, so soll der Exit-Status 255 ausgegeben werden. Es wird kein Signal-Handling durchgeführt ; es gibt keine Unterstützung von Hintergrundkommandos, Pipelines, Builtin-Kommandos oder Shell-Variablen Grundlegender Ablauf Prompt ausgeben Kommandozeile einlesen readline() Zerlegen in Worte tokenizer() I/O Umlenkung vorbereiten fassign() Argumentvektor aufbauen Kommando ausführen Warten Abbildung 2.8: Ablaufprinzip der tinysh

50 44 KAPITEL 2. PROZESSE UNTER UNIX Einschub: stralloc.h, libowfat-bibliothek Es ist zwar sehr einfach möglich mittels calloc() bzw. strdup() einen dynamischen String anzulegen, jedoch bieten die Funktionen strcpy(), strcat(), sprintf(), etc. keine Möglichkeit der Überprüfung, ob die allozierte Länge nicht überschritten wird was nicht zuletzt an der fehlenden Größeninformation bei C-Arrays liegt. Es bietet sich also die Verwendung der Bibliothek libowfat ( an, die von Felix von Leitner nach einem Vorbild von Dan J. Bernstein nachprogrammiert und unter die GPL (GNU General Public License) gestellt wurde. Diese Bibliothek ist bei uns lokal unter /usr/local/diet installiert. Sie kann sehr leicht auch unter Linux installiert werden: herunterladen und entpacken, make aufrufen (GNUmakefile ist enthalten), im GNUmakefile den prefix anpassen (z.b. auch auf /usr/local/diet) und danach make install aufrufen. Bei C-Arrays fehlt im Wesentlichen die Größeninformation. Dies behebt die folgende Datenstruktur für Strings in der stralloc-bibliothek: typedef struct stralloc { char s ; / Zeichen des Strings ( i. A. ohne \0 am Ende) / unsigned int len ; / Laenge des Strings ( len <= a) / unsigned int a ; / Laenge des Arrays s / } stralloc ; Hinter s verbirgt sich das Zeichenarray, das im Allgemeinen nicht nullterminiert ist. Dies hat den Vorteil, dass Strings auch das Null-Byte enthalten können. Die Komponente len enthält die momentane Länge des Strings und a ist die momentante Länge des Arrays s; also gilt folglich immer len a. Da C lokale Variablen nicht automatisch initialisiert, müssen diese jeweils von Hand initialisiert werden: stralloc sa = {0}; Man beachte, dass durch obige Initialisierung nicht nur sa. s, sondern auch sa. len und sa.a auf 0 initialisiert werden. Im Folgenden sind die wesentlichen Funktionen der stralloc-bibliothek kurz beschrieben. Der Rückgabewert dieser Funktionen ist bei Erfolg 1 und sonst 0. int stralloc_ready(stralloc* sa,unsigned int len) Stellt sicher, dass genügend Platz für len Zeichen vorhanden ist. int stralloc_readyplus(stralloc* sa,unsigned int len) Stellt sicher, dass genügend Platz für weitere len Zeichen vorhanden ist. int stralloc_copys(stralloc* sa,const char* buf) Kopiert den nullterminierten String buf nach sa. int stralloc_copy(stralloc* sa,const stralloc* sa2) Kopiert sa2 nach sa.

51 2.7. EINE KLEINE SHELL (TINYSH) 45 int stralloc_cats(stralloc* sa,const char* buf) Hängt den nullterminierten String buf an sa an. int stralloc_cat(stralloc* sa,stralloc* sa2) Hängt sa2 an sa an. int stralloc_0(stralloc* sa) Hängt ein Null-Byte an sa an. void stralloc_free(stralloc* sa) Gibt den von sa belegten Speicher wieder frei also den Speicher von sa >s (und nicht den Speicher für die stralloc-struktur) Einlesen der Kommandozeile readline() Damit können wir die Funktion readline() zum Einlesen der Kommandozeile implementieren. Programm 2.21 (S. 45) definiert die Schnittstelle, Programm 2.22 (S. 45) enthält die Implementierung. 1 # ifndef SA_READLINE_H 2 # define SA_READLINE_H 3 Programm 2.21: tinysh: readline() Schnittstelle (tinysh/sareadline.h) 4 # include < stdio.h> 5 # include < stralloc. h> 6 int readline (FILE fp, stralloc sa ); 7 8 # endif Programm 2.22: tinysh: readline() Implementierung (tinysh/sareadline.c) 1 / 2 Read a string of arbitrary length from a 3 given file pointer. LF is accepted as terminator. 4 1 is returned in case of success, 0 in case of errors. 5 afb 4/ / 7 8 # include < stralloc. h> 9 # include < stdio.h> int readline (FILE fp, stralloc sa ) { 12 if (! stralloc_copys ( sa, "" )) return 0; // FALSE 13 for (;;) { 14 if (! stralloc_readyplus ( sa, 1)) return 0; // FALSE 15 if ( fread ( sa >s + sa >len, sizeof (char ), 1, fp ) <= 0) 16 return 0; // FALSE 17 if ( sa >s[sa >len] == \n ) break; 18 ++sa >len; 19 } 20 return 1; // TRUE 21 }

52 46 KAPITEL 2. PROZESSE UNTER UNIX Mit der C-Funktion size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream); für binären Stream-Input werden nmemb Datenelemente mit je size Bytes Länge vom Stream stream gelesen und in ptr abgelegt. Rückgabewert ist die Anzahl der gelesenen Elemente. Ein kleines Testprogramm zeigt 2.23, S # include < stdio.h> 2 # include < stdlib.h> 3 # include " sareadline. h" 4 5 void print ( stralloc sa ) { 6 int i ; 7 for ( i = 0; i < sa >len; i++) 8 putchar ( sa >s[i ]); 9 } int main() { 12 stralloc line = {0}; 13 printf ( "Eingabe: " ); 14 readline ( stdin, &line ); 15 printf ( "Gelesen: " ); 16 print (& line ); 17 puts ( "" ); 18 exit (0); 19 } Programm 2.23: tinysh: readline() Test (tinysh/test-readline.c) Übersetzung und Ausführung: spatz$ gcc -Wall -I /usr/local/diet/include/ \ -L /usr/local/diet/lib/ sareadline.c test-readline.c -lowfat swg@spatz:~/skripte/soft2.05/2/progs/tinysh> a.out Eingabe: aaa bbb >xxx >>zzz Gelesen: aaa bbb >xxx >>zzz spatz$

53 2.7. EINE KLEINE SHELL (TINYSH) Zerlegen in Tokens tokenizer() Der Wortzerleger tokenizer() arbeitet auf einer als stralloc repräsentierten Zeichenkette und generiert eine Liste vom Typ strlist, die auf die einzelnen Wörter verweist. Dabei wird ein Umkopieren der Wörter vermieden. Stattdessen verweisen die Zeiger in die originale Zeichenkette und die Leerzeichen werden in Nullbytes verwandelt, um als Begrenzer dienen zu können. Abbildung 2.9 zeigt die resultierende Datenstruktur an einem Beispiel. input s c p 0 x 0 y 0 len a 7 30 tokens list len 4 allocated 8 0 Abbildung 2.9: Datenstruktur zur Wortzerlegung Die Datenstruktur für tokens ist ähnlich der in der stralloc-bibliothek aufgebaut, die Struktur wie die notwendigen Funktionen sind in 2.24, S. 48, definiert und in 2.25, S. 49 implementiert.

54 48 KAPITEL 2. PROZESSE UNTER UNIX Programm 2.24: tinysh: Datenstruktur für den tokenizer() Schnittstelle (tinysh/strlist.h) 1 / 2 Data structure for dynamic string lists that works 3 similar to the stralloc library. 4 Return values : 1 if successful, 0 in case of failures. 5 afb 4/ / 7 8 # ifndef STRLIST_H 9 # define STRLIST_H typedef struct strlist { 12 char list ; 13 unsigned int len ; / # of strings in list / 14 unsigned int allocated ; / allocated length for list / 15 } strlist ; / assure that there is at least room for len list entries / 18 int strlist_ready ( strlist list, unsigned int len ); / assure that there is room for len additional list entries / 21 int strlist_readyplus ( strlist list, unsigned int len ); / truncate the list to zero length / 24 int strlist_clear ( strlist list ); / append the string pointer to the list / 27 int strlist_push ( strlist list, char string ); 28 # define strlist_push0 ( list ) strlist_push (( list ), 0) / free the strlist data structure but not the strings / 31 int strlist_free ( strlist list ); # endif

55 2.7. EINE KLEINE SHELL (TINYSH) 49 Programm 2.25: tinysh: Datenstruktur für den tokenizer() Implementierung (tinysh/strlist.c) 1 / 2 Data structure for dynamic string lists that works 3 similar to the stralloc library. 4 Return values : 1 if successful, 0 in case of failures. 5 afb 4/ / 7 # include < stdlib.h> 8 # include " strlist.h" 9 10 / assure that there is at least room for len list entries / 11 int strlist_ready ( strlist list, unsigned int len ) { 12 if ( list > allocated < len ) { 13 unsigned int wanted = len + ( len>>3) + 8; 14 char newlist = (char ) realloc ( list > list, 15 sizeof (char ) wanted ); 16 if ( newlist == 0) return 0; 17 list >list = newlist ; 18 list > allocated = wanted; 19 } 20 return 1; 21 } / assure that there is room for len additional list entries / 24 int strlist_readyplus ( strlist list, unsigned int len ) { 25 return strlist_ready ( list, list >len + len ); 26 } / truncate the list to zero length / 29 int strlist_clear ( strlist list ) { 30 list >len = 0; 31 return 1; 32 } / append the string pointer to the list / 35 int strlist_push ( strlist list, char string ) { 36 if (! strlist_ready ( list, list >len + 1)) return 0; 37 list >list [ list >len++] = string ; 38 return 1; 39 } / free the strlist data structure but not the strings / 42 int strlist_free ( strlist list ) { 43 free ( list >list ); list >list = 0; 44 list > allocated = 0; 45 list >len = 0; 46 return 1; 47 }

56 50 KAPITEL 2. PROZESSE UNTER UNIX Das Programm für den tokenizer() hierzu besteht wieder aus Headerfile (2.26, S. 50) und Implementierung (2.27, S. 50) 1 # ifndef TOKENIZER_H 2 Programm 2.26: tinysh: tokenizer() Schnittstelle (tinysh/tokenizer.h) 3 # define TOKENIZER_H 4 # include < stralloc. h> 5 # include " strlist.h" 6 int tokenizer ( stralloc input, strlist tokens ); 7 8 # endif Programm 2.27: tinysh: tokenizer() Implementierung (tinysh/tokenizer.c) 1 / 2 Simple tokenizer : Take a 0 terminated stralloc object and return a 3 list of pointers in tokens that point to the individual tokens. 4 Whitespace is taken as token separator and all whitespaces within 5 the input are replaced by null bytes. 6 afb 4/ / 8 9 # include <ctype. h> 10 # include < stdlib.h> 11 # include < stralloc. h> 12 # include " strlist. h" 13 # include " tokenizer. h" int tokenizer ( stralloc input, strlist tokens ) { 16 char cp ; 17 int white = 1; strlist_clear ( tokens ); 20 for ( cp = input >s; cp && cp < input >s + input >len; ++cp) { 21 if ( isspace ( cp )) { 22 cp = \0 ; white = 1; continue; 23 } 24 if (! white ) continue; 25 white = 0; 26 if (! strlist_push ( tokens, cp )) return 0; 27 } 28 return 1; 29 }

57 2.7. EINE KLEINE SHELL (TINYSH) 51 Ein kleines Test-Programm zeigt 2.28, S # include < stdio.h> 2 # include < stdlib.h> 3 # include " sareadline. h" 4 # include " strlist.h" 5 # include " tokenizer. h" 6 7 int main() { 8 stralloc line = {0}; 9 printf ( "Eingabe: " ); 10 readline ( stdin, &line ); 11 Programm 2.28: tinysh: tokenizer() Test (tinysh/test-tokenizer.c) 12 strlist tokens = {0}; 13 stralloc_0 (& line ); / required by tokenizer () / 14 if (! tokenizer (& line, &tokens )) printf ( "ERROR\n"); 15 if ( tokens. len == 0) printf ("No Tokens\n"); 16 for ( int i = 0; i < tokens. len ; ++i) 17 printf ( "Token %d: %s \n", i,tokens. list [ i ]); 18 exit (0); 19 } Übersetzung und Ausführung: spatz$ gcc -Wall -std=c99 -I /usr/local/di et/include/ \ -L /usr/local/diet/lib/ \ sareadline.c strlist.c tokenizer.c test-tokenizer.c -lowfat spatz$ a.out Eingabe: aaa >bbb >> >>xxx " " Token 0: aaa Token 1: >bbb Token 2: >> Token 3: >>xxx Token 4: " Token 5: " spatz$

58 52 KAPITEL 2. PROZESSE UNTER UNIX Hauptprogramm unserer Shell Programm 2.29: tinysh: Hauptprogramm (tinysh/tinysh.c) 1 # include < fcntl.h> 2 # include < stdio.h> 3 # include < stdlib.h> 4 # include <unistd. h> 5 # include <sys/wait. h> 6 # include " sareadline. h" 7 # include " strlist.h" 8 # include " tokenizer. h" 9 10 / 11 assign an opened file with the given flags and mode to fd 12 / 13 void fassign ( int fd, char path, int oflags, mode_t mode) { 14 int newfd = open( path, oflags, mode); 15 if (newfd < 0) { 16 perror ( path ); exit (255); 17 } 18 if (dup2(newfd, fd ) < 0) { 19 perror ( "dup2"); exit (255); 20 } 21 close (newfd ); 22 } int main() { 25 stralloc line = {0}; 26 while ( printf ("%% "), readline ( stdin, &line )) { 27 strlist tokens = {0}; 28 stralloc_0 (& line ); / required by tokenizer () / 29 if (! tokenizer (& line, &tokens )) break; 30 if ( tokens. len == 0) continue; 31 pid_t child = fork (); 32 if ( child == 1) { 33 perror ( " fork" ); continue; 34 } 35 if ( child == 0) { 36 strlist argv = {0}; / list of arguments / 37 char cmdname = 0; / first argument / 38 char path ; / of output files / 39 int oflags ; for ( int i = 0; i < tokens. len ; ++i) { 42 switch ( tokens. list [ i ][0]) { 43 case < : 44 fassign (0, &tokens. list [ i ][1], O_RDONLY, 0); 45 break; 46 case > : 47 path = &tokens. list [ i ][1]; 48 oflags = O_WRONLY O_CREAT; 49 if ( path == > ) { 50 ++path; oflags = O_APPEND;

59 2.7. EINE KLEINE SHELL (TINYSH) } else { 52 oflags = O_TRUNC; 53 } 54 fassign (1, path, oflags, 0666); 55 break; 56 default : 57 strlist_push (&argv, tokens. list [ i ]); 58 if (cmdname == 0) cmdname = tokens. list [ i ]; 59 } 60 } 61 if (cmdname == 0) exit (0); 62 strlist_push0 (&argv ); 63 execvp (cmdname, argv. list ); 64 perror (cmdname); 65 exit (255); 66 } / wait for termination of child / 69 int stat ; 70 pid_t pid = wait(& stat ); 71 if ( pid == child ) { 72 if (WIFEXITED(stat)) { 73 int code = WEXITSTATUS(stat); 74 if ( code && code!= 255) { 75 printf ( "terminated with exit code %d\n", code); 76 } 77 } else { 78 printf ( "terminated abnormally\n"); 79 } 80 } else { 81 perror ( "wait" ); 82 } 83 } 84 }

60 54 KAPITEL 2. PROZESSE UNTER UNIX Dazu noch ein kleines Makefile: CC = gcc CFLAGS = -Wall -std=c99 -I /usr/local/diet/include/ \ -L /usr/local/diet/lib/ OBJ = tinysh.o sareadline.o strlist.o tokenizer.o tinysh: $(OBJ) $(CC) $(CFLAGS) -o tinysh $(OBJ) -lowfat tinysh.o: tinysh.c sareadline.h strlist.h tokenizer.h $(CC) $(CFLAGS) -c tinysh.c sareadline.o: sareadline.h sareadline.c $(CC) $(CFLAGS) -c sareadline.c strlist.o: strlist.h strlist.c $(CC) $(CFLAGS) -c strlist.c tokenizer.o: tokenizer.h tokenizer.c strlist.h $(CC) $(CFLAGS) -c tokenizer.c.phony: clean realclean clean: rm -f $(OBJ) core realclean: clean rm -f tinysh Stattdessen hätten wir auch das Makefile-Template (siehe Titelseite) verwenden können! 2.8 Bootstrapping klassisch Henne oder Ei? Alle Aktionen in einem UNIX System erfolgen durch Prozesse. Für alle Prozesse besteht eine Erzeuger-Kindprozess-Beziehung. Bleibt die Frage, woher kommt der erste Prozess, die Wurzel dieses Prozessbaums? Bootstrapping Das Starten eines Betriebssystems heißt Bootstrapping. Für UNIX fallen darunter alle Aktionen vom Stromeinschalten bis zum Erreichen des stabilen Zustands in dem Prozess 1 läuft. Anschließend können alle weiteren Aktionen mit Prozessen und nach den durch die System Calls festgelegten Regeln erfolgen. Phase 0 Hardware Das Einschalten der Stromversorgung bewirkt verschiedene Aktionen, die sehr von der Hardware und der Architektur des Rechners abhängen. Alle haben aber das gleiche Ziel: Selbsttest und Grundinitialisierung der einzelnen Komponenten. Die Hardware befindet sich danach in einem definierten Startzustand.

61 2.8. BOOTSTRAPPING KLASSISCH 55 Phase 1: First Stage Boot Die Hardware verfügt über einige festeingebaute Routinen, die in der Lage sind, einen Boot-Block von der Platte zu lesen und zur Ausführung zu bringen. Der Boot-Block enthält ein loader - Programm. Dieses Programm muss mit der Console kommunizieren können und bereits über das UNIX File System Bescheid wissen. Falls der Platz im Boot-Block für ein Programm mit diesen Fähigkeiten nicht ausreicht, muss das minimale Boot Loader Programm ein weiteres, größeres Loader Programm laden und ausführen können. Phase 2: Second Stage Boot Aufgabe des loader - Programms ist es, den Kernel, meist die Datei /unix, von der Platte in den Hauptspeicher zu laden und zu starten. Anschließend beginnt UNIX zu leben. Loader Programme sind meist trickreiche Assemblerprogramme. Im Boot-Block können nur wenige Anweisungen untergebracht werden, die müssen aber komplexe, Hardwarenahe Aufgaben erledigen. Phase 3: Standalone Sobald der UNIX Kernel gestartet wurde, übernimmt er die gesamte Kontrolle über den Rechner. Der Kernel kann jetzt alles weitere aus eigener Kraft erledigen (Standalone). In seiner Startphase setzt er die Interruptvektoren im low mem, initialisiert die Speicherverwaltungs-Hardware, baut seine Tabellen (Prozess-, Open File-, Inode-, usw.) auf führt eine mount-ähnliche Operation für das root File System aus... Nun fehlt noch etwas Magie, um den ersten Prozess zu erschaffen. Die Prozessverwaltung generiert in ihrer Startphase den Prozess 0 von Hand. Dieser Prozess, meist swapper genannt, besteht nur aus dem System-Daten Bereich: Slot 0 in der Prozesstabelle plus per process data region. Er besitzt keinen Text oder User-Daten Bereich. Dafür existiert er während der gesamten Systemlaufzeit und ist für das Scheduling zuständig. Er benötigt hierfür nur Instruktionen und Daten aus dem Kernel-Adressraum. Da Prozess 0 eigentlich kein echter Prozess ist, erschafft der Kernel auch Prozess 1 manuell. Soweit als möglich benutzt oder imitiert der Kernel hier bereits den fork Mechanismus, um den Prozess 1 vom Prozess 0 abzuspalten. Prozess 1 erhält jedoch einen ganz regulären Context und kann anschließend vom Scheduler als normaler Prozess zur Ausführung gebracht werden. Sein hand crafted Text Bereich enthält einzig die Anweisung execl( /etc/init, init, 0 ) Nach dem exec System Call läuft im Context des Prozesses 1 das Programm /etc/init und Prozess 1 heißt nun init - Prozess.

62 56 KAPITEL 2. PROZESSE UNTER UNIX 2.9 Der init-prozess user Id 0 Operationen: BSD init führt das Shell-Skript /etc/rc aus. Dabei werden u.a. einige Daemon-Prozesse gestartet. init entnimmt der Datei /etc/ttys, welche Terminals für den Multiuser-Betrieb aktiviert werden müssen. 2. System V init liest die Datei /etc/inittab, in der spezifiziert ist, was zu tun ist. Zum normalen Multiuser-Betrieb wird die Datei /etc/rc ausgeführt; dieses Programm startet die meisten Daemon-Prozesse. Nach dessen Beendigung werden wie in /etc/inittab definiert die angeschlossenen Terminals aktiviert. (mehr dazu in einem Administrator Reference Manual). fork() init fork() process ID = 1 fork() init init init exec() exec() exec() getty getty getty wartet auf login Name login exec Arg.: Login Name wartet auf Passwort exec() login exec() /bin/sh Abbildung 2.10: Der Init-Prozess getty setzt die Übertragungsgeschwindigkeit des Terminals, gibt irgendeine Begrüßungsformel aus und wartet auf Eingabe eines login-namens. Login-Name eingegeben exec(/bin/login) login sucht den eingegebenen Login-Namen in der Passwortdatei und fordert die Eingabe eines Passwortes. Alle bis hier ausgeführten Programme (init, getty, login) laufen als Prozesse mit user ID und effective user ID 0 (superuser) - mit dem System Call exec ändert sich die process ID nicht. Danach wird das current working directory auf den entsprechenden Eintrag für das login directory aus der Passwortdatei gesetzt.

63 2.9. DER INIT-PROZESS 57 Group ID und user ID (in dieser Reihenfolge) werden via setgid und setuid wie in der Passwortdatei definiert gesetzt. Über exec wird das in der Passwortdatei spezifizierte Programm gestartet (falls keine Angabe: /bin/sh). Falls der sich anmeldende Benutzer nicht der Superuser ist (login-name meist root): setgid und setuid reduzieren Prozessprivilegien beide System Calls sind dem Superuser vorbehalten (daher diese Reihenfolge)

64 58 KAPITEL 2. PROZESSE UNTER UNIX

65 Kapitel 3 Signale 3.1 Einführung Signale werden für vielfältige Zwecke eingesetzt. Sie können verwendet werden, um den normalen Ablauf eines Prozesses für einen wichtigen Hinweis zu unterbrechen, um die Terminierung eines Prozesses zu erbitten oder zu erzwingen und um schwerwiegende Fehler bei der Ausführung zu behandeln wie z.b. den Verweis durch einen invaliden Zeiger. Signale ersetzen keine Interprozesskommunikation, da sie fast keine Informationen mit sich führen. In Abhängigkeit von der jeweiligen Systemumgebung gibt es mehr oder weniger fest definierte Signale, die über natürliche Zahlen identifiziert werden. Der ISO-Standard für die Programmiersprache C definiert eine einfache und damit recht portable Schnittstelle für die Behandlung von Signalen. Hier gibt es neben der Signalnummer selbst keine weiteren Informationen. Der IEEE Standard (POSIX) bietet eine Obermenge der Schnittstelle des ISO-Standards an, bei der wenige zusätzliche Informationen (wie z.b. die Angabe des invaliden Zeigers) dabei sein können und der insbesondere eine sehr viel feinere Kontrolle der Signalbehandlung erlaubt. Signale können von verschiedenen Parteien bzw. unter unterschiedlichen Bedingungen ausgelöst werden: 1. System Call kill(): damit kann ein Prozess sich oder einem anderen Prozess ein Signal senden (führt nicht notwendig zur Termination) int kill(int pid, int sig); Der sendende Prozess muss entweder ein superuser-prozess sein oder der sendende und empfangende Prozess müssen dieselbe effektive userid haben. 59

66 60 KAPITEL 3. SIGNALE Ist das pid-argument 0, so geht das Signal an alle Prozesse in der Prozess-Gruppe des Senders Ist das pid-argument -1 und der Sender nicht der Superuser, so geht das Signal an alle Prozesse, deren real user ID gleich der effective user ID des Senders ist Ist das pid-argument -1 und der Sender der Superuser, so geht das Signal an alle Prozesse ausgenommen die Superuser-Prozesse (i.a. PID s 0 oder 1) Ist das pid-argument negativ, aber ungleich -1, so geht das Signal an alle Prozesse, deren Prozess-Gruppen-Nummer gleich dem Absolutbetrag von pid ist Ist das sig-argument 0, so wird nur eine Fehler-Prüfung durchgeführt, aber kein Signal geschickt (z.b. zur Prüfung der Gültigkeit des pid-arguments) 2. Kommando kill: nutzt den System Call kill() (s.o. und man) 3. Tastatureingaben, z.b.: ctrl-c (oder delete) beendet einen im Vordergrund laufenden Prozess ( SIGINT), genauer alle Prozesse in der Kontrollgruppe dieses Terminals - vom Kernel verschickt ctrl-\ erzeugt SIGQUIT ctrl-z hält einen im Vordergrund laufenden Prozess an ( SIGSTOP) kann mit SIGCONT fortgesetzt werden 4. Hardware-Bedingungen, z.b.: Gleitpunkt-Arithmetik-Fehler (Floating Point Exception) (SIGFPE) Adressraum-Verletzungen (Segmentation Violation) (SIGSEGV) 5. Software-Bedingungen, z.b.: Schreiben in eine Pipe, an der kein Prozess zum Lesen hängt (SIGPIPE). Typisch für die Auslösung durch das Betriebssystem ist die Terminalschnittstelle unter UNIX. Diese wurde ursprünglich für ASCII-Terminals mit serieller Schnittstelle entwickelt, die nur folgende Eingabemöglichkeiten anboten: Einzelne ASCII-Zeichen, jeweils ein Byte (zusammen mit etwas Extra-Kodierung wie Prüfund Stop-Bits). Ein BREAK, das als spezielles Signal repräsentiert wird, das länger als die Kodierung für ein ASCII-Zeichen währt. Ein HANGUP, bei dem ein Signal wegfällt, das zuvor die Existenz der Leitung bestätigt hat. Dies benötigt einen weiteren Draht in der seriellen Leitung. Diese Eingaben werden auf der Seite des Betriebssystems vom Terminal-Treiber bearbeitet, der in Abhängigkeit von den getroffenen Einstellungen die eingegebenen Zeichen puffert und das Editieren der Eingabe ermöglicht (beispielsweise mittels BACKSPACE, CTRL-u und CTRL-w) und bei besonderen Eingaben Signale an alle Prozesse schickt, die mit diesem Terminal verbunden sind.

67 3.2. SIGNALBEHANDLER 61 Ziel war es, dass im Normalfall ein BREAK zu dem Abbruch oder zumindest der Unterbrechung der gerade laufenden Anwendung führt. Und ein HANGUP sollte zu dem Abbruch der gesamten Sitzung führen, da bei einem Wegfall der Leitung keine Möglichkeit eines regulären Abmeldens besteht. Heute sind serielle Terminals rar geworden, aber das Konzept wurde dennoch beibehalten. Zwischen einem virtuellen Terminal (beispielsweise einem xterm) und den Prozessen, die zur zugehörigen Sitzung gehören, ist ein sogenanntes Pseudo-Terminal im Betriebssystem geschaltet, das der Sitzung die Verwendung eines klassischen Terminals vorspielt. Da es BREAK in diesem Umfeld nicht mehr gibt, wird es durch ein beliebiges Zeichen ersetzt wie beispielsweise CTRL-c. Wenn das virtuelle Terminal wegfällt (z.b. durch eine gewaltsame Beendigung der xterm-anwendung), dann gibt es weiterhin ein HANGUP für die Sitzung. Auf fast alle Signale können Prozesse, die sie erhalten, auf dreierlei Weise reagieren: Voreinstellung: Terminierung des Prozesses. Ignorieren. Bearbeitung durch einen Signalbehandler. Es mag harsch erscheinen, dass die Voreinstellung zur Terminierung eines Prozesses führt. Aber genau dies führt bei normalen Anwendungen genau zu den gewünschten Effekten wie Abbruch des laufenden Programms bei BREAK (die Shell ignoriert das Signal) und Abbau der Sitzung bei HANGUP. Wenn ein Prozess diese Signale ignoriert, sollte es genau wissen, was es tut, da der Nutzer auf diese Weise eine wichtige Kontrollmöglichkeit seiner Sitzung verliert. Sinnvoll ist es natürlich, eine Anwendung mit einem Signalbehandler zu ergänzen, wenn dadurch Datenverluste bei einer Terminierung vermieden werden können. 3.2 Signalbehandler 3.3 Reaktion auf Signale: signal() Ein Prozess kann eine Funktion definieren, die bei Eintreten eines bestimmten Signals ausgeführt werden soll (signal handler). Signale (außer SIGKILL und SIGSTOP) können ignoriert werden. System Call signal: #include <signal.h> int (*signal (int sig, void (*func) (int))) (int); /* ANSI C signal handling */

68 62 KAPITEL 3. SIGNALE Also: signal ist eine Funktion, die einen Zeiger auf eine Funktion zurückliefert, die selbst eine int zurückliefert (die bisherige Reaktion auf das Signal oder bei Fehler SIG_ERR) Das erste Argument ist die Signalnummer (Makro aus signal.h), das zweite (func) ist Zeiger auf eine Funktion, die void liefert (die neue Reaktion auf das Signal) Wegen der detaillierteren Reaktionsmöglichkeit und einiger anderen Fußangeln ist die Funktion sigaction() vorzuziehen! Programm 3.1 demonstriert die Behandlung des Signals SIGINT. Als Signalbehandler operiert die Funktion signal_handler(). Signalbehandler erhalten als Argument eine ganze Zahl, worüber sie die Nummer des Signals erhalten, das sie gerade bearbeiten. Einen Rückgabewert gibt es nicht. 1 # include < signal.h> 2 # include < stdio.h> 3 # include < stdlib.h> 4 Programm 3.1: Behandlung eines SIGINT-Signals (sigint.c) 5 volatile sig_atomic_t signal_caught = 0; 6 7 void signal_handler ( int signal ) { 8 signal_caught = signal ; 9 } int main() { 12 if ( signal (SIGINT, signal_handler ) == SIG_ERR) { 13 perror ( "unable to setup signal handler for SIGINT"); 14 exit (1); 15 } 16 printf ( "Try to send a SIGINT signal!\n"); 17 int counter = 0; 18 while (! signal_caught ) { 19 for ( int i = 0; i < counter ; ++i) 20 ; 21 ++counter ; 22 } 23 printf ( "Got signal %d after %d steps!\n", signal_caught, counter ); 24 } Erläuterungen zu Programm 3.1 (S. 62): Der Signalbehandler signal_handler() setzt nur eine globale Variable auf den Wert des erhaltenen Signals (Zeile 8). Die Verwendung der Speicherklasse volatile und des Datentyps sig_atomic_t wird später diskutiert.

69 3.3. REAKTION AUF SIGNALE: SIGNAL() 63 main() richtet auf Zeile 12 mit der Funktion signal() für das Signal mit der Nummer SIGINT die Funktion signal_handler() als Reaktion auf dieses Signal ein. Alternativen zu diesem Funktionszeiger wären SIG_DFL für die Prozessterminierung oder SIG_IGN für das Ignorieren. Im Erfolgsfalle liefert signal() die frühere Einstellung zurück, ansonsten SIG_ERR. Die Schleife in Zeile 18 bricht ab, wenn sich der Wert von signal_caught ändert. Dies kann in diesem Beispiel nur durch den Signalbehandler passieren. Wenn dies geschieht, wird auf Zeile 22 die Nummer des eingetroffenen Signals ausgegeben und das Programm beendet. So könnte ein Aufruf dieses Programms aussehen, wenn das Versenden des Signals SIGINT durch den Terminaltreiber durch die Eingabe von CTRL-c recht schnell geschieht: spatz$ sigint Try to send a SIGINT signal! ^CGot signal 2 after steps! spatz$ Die 2 ist dabei die Nummer des Signals SIGINT: thales$ grep SIGINT /usr/include/sys/iso/signal_iso.h #define SIGINT 2 /* interrupt (rubout) */ thales$ Zur Speicherklasse volatile: Leider sind mit der Bearbeitung von Signalen große Probleme verbunden. Wenn ein optimierender Übersetzer den Programmtext 3.1 (S. 62) analysiert, könnten folgende Punkte auffallen: Die Schleife in den Zeilen 18 bis 21 ruft keine externen Funktionen auf. Innerhalb der Schleife wird signal_caught nirgends verändert. Daraus könnte vom Übersetzer der Schluss gezogen werden, dass die Schleifenbedingung nur zu Beginn einmal überprüft werden muss. Findet der Eintritt in die Schleife statt, könnte der weitere Test der Bedingung ersatzlos wegfallen. Analysen wie diese sind für heutige optimierende Übersetzer Pflicht, um guten Maschinen-Code erzeugen zu können. Es wäre also fatal, wenn darauf nur wegen der Existenz von asynchron aufgerufenen Signalbehandlern verzichtet werden würde. Um beides zu haben, die fortgeschrittenen Optimierungstechniken und die Möglichkeit, Variablen innerhalb von Signalbehandlern setzen zu können, wurde in C die Speicherklasse volatile eingeführt. Damit lassen sich Variablen kennzeichnen, deren Wert sich jederzeit ändern kann selbst dann, wenn dies aus dem vorliegenden Programmtext nicht ersichtlich ist. Entsprechend gilt dann auch in C, dass alle anderen Variablen, die nicht als volatile klassifiziert sind, sich nicht durch magische Effekte verändern dürfen. Daraus folgt, dass korrekte Signalbehandler in ihren Möglichkeiten stark eingeschränkt sind. So ist es nur zulässig,

70 64 KAPITEL 3. SIGNALE lokale Variablen zu verwenden, mit volatile deklarierte Variablen zu benutzen und Funktionen aufzurufen, die sich an die gleichen Spielregeln halten. Letzteres schließt insbesondere die Verwendung von Ein- und Ausgabe innerhalb eines Signalbehandlers aus. Der ISO-Standard nennt nur abort(), _Exit() 1 und signal() als zulässige Bibliotheksfunktionen. Beim POSIX-Standard werden noch zahlreiche weitere Systemaufrufe genannt. Auf den Manualseiten von Solaris wird dies dokumentiert durch die Angabe Async-Signal-Safe bei MT-Level 2 Zum Datentyp sig_atomic_t: ganzzahliger Typ Lese- und Schreiboperationen laufen garantiert atomar ab Datentyp, den der verwendete Prozessor mit einer einzigen ununterbrechbaren Instruktion laden oder speichern kann Bei allen anderen Datentypen ist es nicht ausgeschlossen, dass mitten in einer Zuweisung ein asynchrones Signal eintreffen kann und der zugehörige Signalbehandler dann eine partiell modifizierte Variable vorfindet. 1 _Exit() unterlässt im Vergleich zu exit() sämtliche Aufräumarbeiten. 2 MT steht hier für Multi-Threading, da dabei ähnliche Probleme auftreten, wenngleich in einem noch größeren Umfang.

71 3.4. WECKSIGNALE MIT ALARM() Wecksignale mit alarm() Zu den Signalen, den der POSIX-Standard definiert, gehört auch SIGALRM, das sich als Wecksignal verwenden lässt. Der Wecker wird mit alarm() unter Angabe einer relativen Weckzeit in Sekunden gestellt. Am Ende dieser Frist kommt es zum Eintreffen des Signals SIGALRM. Programmtext 3.2 (S. 65) zeigt, wie alarm() verwendet werden kann, um eine Operation zeitlich zu befristen. Die Funktion timed_read() arbeitet genauso wie read(), wobei jedoch die Wartezeit auf die gegebene Zahl von Sekunden begrenzt wird. Wenn die Zeit verstreicht, ohne dass eine Eingabe erfolgte, wird 0 zurückgeliefert. Programm 3.2: read-operation mit Zeitlimit (tread/tread.c) 1 / 2 Timed read operation. timed_read () works like read () but returns 0 3 if no input was received within the given number of seconds. 4 / 5 6 # include < signal.h> 7 # include <unistd. h> 8 # include "tread.h" 9 10 static volatile sig_atomic_t time_exceeded = 0; static void alarm_handler ( int signal ) { 13 time_exceeded = 1; 14 } int timed_read ( int fd, void buf, size_t nbytes, unsigned seconds ) { 17 if ( seconds == 0) return 0; 18 / setup signal handler and alarm clock but 19 remember the previous settings : 20 / 21 void ( previous_handler )( int ) = signal (SIGALRM, alarm_handler); 22 if ( previous_handler == SIG_ERR) return 1; time_exceeded = 0; 25 int remaining_seconds = alarm( seconds ); 26 if ( remaining_seconds > 0) { 27 if ( remaining_seconds <= seconds ) { 28 remaining_seconds = 1; 29 } else { 30 remaining_seconds = seconds ; 31 } 32 } 33 int bytes_read = read ( fd, buf, nbytes ); 34 / restore previous settings / 35 if (! time_exceeded ) alarm (0); 36 signal (SIGALRM, previous_handler ); 37 if ( remaining_seconds ) alarm( remaining_seconds ); 38 if ( time_exceeded ) return 0; 39 return bytes_read ; 40 }

72 66 KAPITEL 3. SIGNALE Was geschieht, wenn mehrere Wecksignale nebeneinander eingerichtet werden? Da alarm() nur die Verwaltung einer einzigen Weckzeit unterstützt, ist eine kooperative Vorgehensweise notwendig. Dies wird dadurch erleichtert, dass alarm() bei der Einrichtung einer neuen Weckzeit entweder 0 zurückgibt (vorher war keine Weckzeit aktiv) oder eine positive Zahl von Sekunden als Restzeit zum vorher eingestellten Wecksignal. Entsprechend wird auf Zeile 24 in Programmtext 3.2 (S. 65) in der Variablen remaining_seconds die alternative Weckzeit notiert und später auf Zeile 36 findet eine Wiedereinsetzung statt, nachdem zuvor auf Zeile 29 die verbleibende Restzeit neu berechnet worden ist. Analog wird in Zeile 21 der frühere Signalbehandler für SIGALRM in der Variablen previous_handler gesichert, so dass er in Zeile 35 wieder restauriert werden kann. Bei der Restaurierung ist es wichtig, dass kein Fenster offenbleibt, das zum Verlust einer Signalbehandlung führt oder den alten Signalbehandler auf das noch nicht eingetretene Signal für das von timed_read() eingerichtete Zeitlimit reagieren lässt. Wenn der alte Signalbehandler SIG_DFL war, also die Voreinstellung, dann würde das sogar unerwartet zur Terminierung unseres Prozesses führen. Deswegen wird in Zeile 34 der Wecker zuerst deaktiviert, wenn er bislang sein Signal noch nicht von sich gegeben hat. Danach kann in Zeile 35 der alte Signalbehandler eingesetzt werden, ohne dass wir Gefahr laufen, daß er sofort verwendet wird. In Zeile 36 wird dann der Wecker neu aufgesetzt, falls er vor dem Aufruf von timed_read() eingeschaltet war. Es bleibt hier noch anzumerken, dass es noch bessere Wege gibt, Leseoperationen mit Zeitbeschränkungen zu versehen. Es empfiehlt sich hier entweder die Verwendung der Systemaufrufe poll() oder select() oder der Einsatz asynchroner Lesetechniken. Dennoch ist die Verwendung von alarm() sinnvoll, da es genügend Operationen gibt, bei denen es keine Variante mit Zeitbeschränkung gibt.

73 3.5. DAS VERSENDEN VON SIGNALEN Das Versenden von Signalen Programm 3.3: Versenden eines Signals an den übergeordneten Prozess (killparent.c) 1 # include < stdio.h> 2 # include < stdlib.h> 3 # include <unistd. h> 4 # include < signal.h> 5 # include <sys/wait. h> 6 # include <sys/types. h> 7 8 void sigterm_handler ( int signo ) { 9 const char msg[] = "Goodbye, cruel world!\n"; 10 write (1, msg, sizeof msg 1); 11 _exit (1); 12 } int main() { 15 if ( signal (SIGTERM, sigterm_handler) == SIG_ERR) { 16 perror ( " signal " ); exit (1); 17 } pid_t child = fork (); 20 if ( child == 0) { 21 if ( kill ( getppid (), SIGTERM) < 0 ) { 22 perror ( " kill " ); exit (1); 23 } 24 exit (0); 25 } 26 int wstat ; 27 wait(&wstat ); 28 exit (0); 29 } Der ISO-Standard für C sieht nur eine Funktion raise() vor, die es erlaubt, ein Signal an den eigenen Prozess zu versenden. Im POSIX-Standard kommt die Funktion kill() hinzu, die es erlaubt, ein Signal an einen anderen Prozess zu verschicken, sofern die dafür notwendigen Privilegien vorliegen. Programmtext 3.3 zeigt ein Beispiel, bei dem ein neu erzeugter Prozess ein Signal an den Erzeuger sendet. Programm 3.4 (S. 68) dreht den Spiess um: der Erzeuger schickt ein Signal an das erzeugte Kind und untersucht nach dessen Termination (in Kommentaren ist beim Kindprozess das Setzen des Signalbehandlers ausgeblendet) dessen Exit-Status.

74 68 KAPITEL 3. SIGNALE Programm 3.4: Versenden eines Signals an den erzeugten Prozess (killchild.c) 1 # include < stdio.h> 2 # include < stdlib.h> 3 # include <unistd. h> 4 # include < signal.h> 5 # include <sys/wait. h> 6 # include <sys/types. h> 7 8 void sigterm_handler ( int signo ) { 9 const char msg[] = "Goodbye, cruel world!\n"; 10 write (1, msg, sizeof msg 1); 11 _exit (1); 12 } int main() { 15 pid_t child = fork (); 16 if ( child == 0) { if ( signal (SIGTERM, sigterm_handler) == SIG_ERR) { 19 perror ( " signal " ); exit (1); 20 } sleep (10); 23 exit (0); 24 } 25 if ( kill ( child, SIGTERM) < 0 ) { perror ( " kill " ); exit (1); } 26 int wstat ; 27 wait(&wstat ); 28 int sig = wstat & 0177; 29 int exitnumber = ( wstat >> 8) & 0377; 30 if ( sig ) 31 printf ( "Child terminated with signal %d\n", sig); 32 else 33 printf ( "Child terminated with exit number %d\n", exitnumber); 34 exit (0); 35 } Übersetzung und Ausführung ohne Signalbehandler beim Kind: spatz$ gcc -Wall killchild.c spatz$ a.out Child terminated with signal 15 spatz$ Übersetzung und Ausführung mit Signalbehandler beim Kind: spatz$ a.out Goodbye, cruel world! Child terminated with exit number 1 spatz$

75 3.5. DAS VERSENDEN VON SIGNALEN 69 Zur Abfrage des Exit-Codes wie auch der Signalnummer im Exit-Status, wie er über wait() zurückgeliefert wird, sei nochmals auf Abb. 2.3 (S. 29) verwiesen. Trotz ihres geringen Informationsgehalts dienen Signale gelegentlich zur Kommunikation. So gibt es eine weitverbreitete Konvention, dass bei langfristig laufenden Diensten SIGHUP das erneute Einlesen der Konfiguration veranlasst und SIGTERM eine geordnete Terminierung einleiten soll. Gelegentlich sind für Dienste auch Reaktionen für SIGUSR1 und SIGUSR2 definiert. So wird der Apache-Webserver beispielsweise bei SIGUSR1 veranlasst, bei nächster Gelegenheit auf sanfte Weise neu zu starten. Anders als bei SIGHUP kommt es dann nicht zur Unterbrechung aktueller Netzwerkverbindungen. Der Systemaufruf kill() erfüllt aber auch noch einen weiteren Zweck. Bei einer Signalnummer von 0 wird nur die Zulässigkeit des Signalversendens überprüft. Programmtext 3.5 (S. 69) demonstriert, wie dies dazu verwendet werden kann, um die Existenz eines Prozesses zu überprüfen. Programm 3.5: Verwendung von kill() zur Überprüfung der Existenz eines Prozesses (lookfor.c) 1 # include <errno. h> 2 # include < signal.h> 3 # include < stdio.h> 4 # include < stdlib.h> 5 # include <unistd. h> 6 7 int main( int argc, char argv ) { 8 char cmdname = argv++; argc; 9 if ( argc!= 1) { 10 fprintf ( stderr, "Usage: %s pid\n", cmdname); 11 exit (1); 12 } / convert first argument to pid / 15 char endptr = argv [0]; 16 pid_t pid = strtol ( argv [0], &endptr, 10); 17 if ( endptr == argv [0]) { 18 fprintf ( stderr, "%s: integer expected as argument\n", 19 cmdname); 20 exit (1); 21 } if ( kill ( pid,0) == 0 ) 24 printf ( " Process %d exists!\n", pid ); 25 else 26 printf ( " Process %d doesn t exist!\n",pid ); if ( errno == ESRCH) exit (0); 29 perror (cmdname); exit (1); 30 }

76 70 KAPITEL 3. SIGNALE Übersetzung und Ausführung von Programm 3.5 (S. 69): spatz$ gcc -Wall lookfor.c spatz$ ps -uswg PID TTY TIME CMD 3976? 00:00:02 fvwm 4015? 00:00:00 ssh-agent 4018? 00:00:00 FvwmTheme 4030? 00:00:02 xterm 4031? 00:00:00 FvwmButtons 4034? 00:00:00 xeyes 4036? 00:00:00 xclock 4037? 00:00:00 FvwmPager 4039 pts/0 00:00:00 bash 4057? 00:00:06 xterm 4059 pts/1 00:00:00 bash 4075 pts/1 00:00:00 myxtel 4170 pts/1 00:00:01 gv 4208 pts/1 00:00:02 gs 4690? 00:00:00 xterm 4692 pts/2 00:00:00 bash 5002 pts/ 2 00:00:00 ps spatz$ a.out 4690 Process 4690 exists! a.out: Success spatz$ a.out 5002 Process 5002 doesn t exist! spatz$ Gelegentlich kommt es vor, dass Prozesse nur auf das Eintreffen eines Signals warten möchten und sonst nichts zu tun haben. Theoretisch könnte ein Prozess dann in eine Dauerschleife mit leerem Inhalt treten (auch busy loop bezeichnet), aber dies wäre nicht sehr fair auf einem System mit mehreren Prozessen, da dadurch Rechenzeit vergeudet würde. Abhilfe schafft hier der Systemaufruf pause(), der einen Prozess schlafen legt, bis ein Signal eintrifft. Programm 3.6 demonstriert das Warten auf ein Signal anhand eines virtuellen Ballspiels zweier Prozesse.

77 3.5. DAS VERSENDEN VON SIGNALEN 71 1 # include < stdio.h> 2 # include < stdlib.h> 3 # include <unistd. h> 4 # include <sys/types. h> 5 # include < signal.h> 6 7 # define PINGPONGS 10 8 Programm 3.6: Virtuelles Ballspiel zweier Prozesse (pingpong/pingpong.c) 9 static volatile sig_atomic_t sigcount = 0; void sighandler ( int sig ) { 12 ++sigcount ; 13 if ( signal ( sig, sighandler ) == SIG_ERR) _exit (1); 14 } static void playwith ( pid_t partner, int start ) { 17 int i ; 18 if ( signal (SIGUSR1, sighandler ) == SIG_ERR) { 19 perror ( " signal SIGUSR1"); exit (1); 20 } 21 / give our partner some time for preparation / 22 if ( start ) sleep (1); 23 / start the ping pong game / 24 if ( start ) sigcount = 1; 25 for ( i = 0; i < PINGPONGS; ++i) { 26 if (! sigcount ) pause (); 27 printf ( "[%d] send signal to %d\n", (int) getpid (), ( int ) partner ); 28 if ( kill ( partner, SIGUSR1) < 0) { 29 printf ( "[%d] %d is no longer alive\n", (int) getpid (), ( int ) partner ); 30 return ; 31 } 32 sigcount; 33 } 34 printf ( "[%d] finishes playing\n", ( int ) getpid ()); 35 } int main() { 38 pid_t parent = getpid (); 39 pid_t child = fork (); if ( child < 0) { 42 perror ( "fork" ); exit (1); 43 } 44 if ( child == 0) { 45 playwith ( parent, 1); 46 } else { 47 playwith ( child, 0); 48 } 49 exit (0); 50 }

78 72 KAPITEL 3. SIGNALE Erläuterungen zu Programm 3.6 (S. 71): In Zeile 39 wird ein weiterer Prozess als Spielpartner erzeugt. Beide Prozesse rufen anschließend playwith() auf, um das Spiel durchzuführen. Der neu erzeugte Prozess hat zuerst den virtuellen Ball und darf mit dem Spiel beginnen. Der Besitzer des virtuellen Balles sendet in Zeile 28 den Ball mit Hilfe des Signals SIGUSR1 an den Partner und legt sich anschließend in Zeile 26 mittels pause() schlafen, um auf das Wiedereintreffen des Balls zu warten, das wiederum durch SIGUSR1 signalisiert wird. Die Variable sigcount in Zeile 9 repräsentiert die Zahl der Bälle, die sich im Augenblick im Besitz des Prozesses befinden. Diese Zahl wird von dem Signalbehandler sighandler() in Zeile 12 hochgezählt, wenn ein SIGUSR1-Signal eintrifft und in Zeile 32 wieder heruntergezählt, nachdem das SIGUSR1-Signal an den Partner verschickt wurde. Zu Beginn setzen beide Spieler in Zeile 18 sighandler() als Signalbehandler ein. Derjenige, der mit dem Spiel beginnt, wartet dann in Zeile 22 noch eine Sekunde, um zu vermeiden, dass ein Signal geschickt wird, bevor der Spielpartner die Gelegenheit hatte, seinen Signalbehandler aufzusetzen. Zu beachten ist dabei, dass der Signalbehandler in Zeile 13 sich selbst wieder einsetzt, da der POSIX-Standard nicht festlegt, ob diese Einstellung nach Eintreffen eines Signals erhalten bleibt. signal() gehört mit zu den vom POSIX-Standard genannten Funktionen, die auch innerhalb eines Signalbehandlers aufgerufen werden dürfen.

79 3.6. REAKTION AUF SIGNALE: SIGACTION() Reaktion auf Signale: sigaction() Für eine genauere Behandlung eintreffender Signal bietet POSIX (jedoch nicht ISO-C) den Systemaufruf sigaction() an. #include <signal.h> int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact); Datentyp Feldname Beschreibung void(*) (int) sa_handler Funktionszeiger (wie bisher) void(*) (int, siginfo_t*, void*) sa_sigaction alternativer Zeiger auf einen Signalbehandler, der mehr Informationen zum Signal erhält sigset_t sa_mask Menge von Signalen, die während der Signalbehandlung dieses Signals zu blockieren sind int sa_flags Menge von Boolean-wertigen Optionen Tabelle 3.1: Felder der struct sigaction Während bei signal() zur Spezifikation der Signalbehandlung nur ein Funktionszeiger genügte, kommen bei der struct sigaction, die sigaction() verwendet, die in Tabelle 3.1 genannten Felder zum Einsatz. Programm 3.8 zeigt ein einfaches Beispiel, ein Testprogramm ist 3.9. Zunächst wird für eine bestimmte Zeiteinheit das Signal, das durch ctrl-c erzeugt wird, abgefangen, danach wird der alte Signalbehandler (Termination) wieder eingesetzt. Übersetzung und Ausführung (auf SuSE Linux, gcc 3.3.3): spatz$ make gcc -std=c99 -Wall -g -D_POSIX_C_SOURCE=200112L -D EXTENSIONS =1 -D_Exit=_exit -I/usr/local/diet/include -c -o show.o show.c gcc -std=c99 -Wall -g -D_POSIX_C_SOURCE=200112L -D EXTENSIONS =1 -D_Exit=_exit -I/usr/local/diet/include -c -o sign.o sign.c gcc -L/usr/local/diet/lib show.o sign.o -lowfat -o show spatz$ show Break? go asleep for 5 sec ^C signal handler: SIGNAL = >2< ^C ^C ^\ Quit spatz$ Die (eingefügten) Zeichen ^C und ^\ sollen die erzeugten Signale wiedergeben: nach dem ersten ^C meldet sich der Signalbehandler mit signal handler: SIGNAL = >2<, die weiteren

80 74 KAPITEL 3. SIGNALE ^Cs werden die nächsten 10 Sekunden zurückgehalten, während das ^\ während der Abwicklung des Signalbehandlers (sleep(10) zugestellt wird (Funktion sigemptyset() in Zeile 18 des Programms 3.8 auf S. 74): durch das Leeren der Komponente newact.sa_mask wird erreicht, dass während der Ausführung der installierten Signalbehandlungsfunktion mit Ausnahme des im Argument sig angegebenen Signals keine weiteren Signale von der Zustellung durch den Systemkern zurückgehalten werden (deswegen kommt ja auch unser ^\ sofort durch!) 1 # include < signal.h> 2 3 # ifndef SIGN_H 4 # define SIGN_H 5 6 typedef void ( Sigfunc )( int ); 7 Sigfunc ignoresig ( int ); 8 Sigfunc entrysig ( int ); 9 # endif Programm 3.7: Header-File zu 3.8 (sigaction1/sign.h) 1 # include "sign. h" 2 # include < stdio. h> 3 # include <unistd. h> 4 Programm 3.8: Erstes Beispiel zu sigaction() (sigaction1/sign.c) 5 void myignore( int sig ){ 6 printf ( " signal handler: SIGNAL = >%d<\n",sig); 7 sleep (10); 8 return ; 9 } struct sigaction newact, oldact ; Sigfunc ignoresig ( int sig ) { 14 static int first = 1; // static : bleibt erhalten! 15 newact. sa_handler = myignore; 16 if ( sigemptyset (&newact.sa_mask) < 0) 17 return SIG_ERR; 18 / Durch diese Initialisierung der Komponente sa_mask mit 19 der leeren Menge wird bewirkt, dass waehrend der Aus 20 fuehrung der installierten Signalbehandlungsfunktion 21 mit Ausnahme des im Argument sig angegebenen Signals 22 keine weiteren Signale von der Zustellung durch den 23 Systemkern zurueckgehalten werden 24 / if ( first ) { 27 first = 0; 28 if ( sigaction ( sig, &newact, &oldact ) < 0) 29 return SIG_ERR; 30 else 31 return oldact. sa_handler ; 32 } else { 33 if ( sigaction ( sig, &newact, NULL) < 0) 34 return SIG_ERR;

81 3.6. REAKTION AUF SIGNALE: SIGACTION() else 36 return NULL; 37 } 38 } Sigfunc entrysig ( int sig ) { 41 if ( sigaction ( sig, &oldact, NULL) < 0 ) 42 return SIG_ERR; 43 else 44 return NULL; 45 } 1 # include < stdio. h> 2 # include <unistd. h> 3 # include < stdlib. h> 4 # include "sign. h" 5 Programm 3.9: Testprogramm zu 3.8 (sigaction1/show.c) 6 int main() { 7 int sleep_time ; 8 / install reaction / 9 if ( ( ignoresig (SIGINT) == SIG_ERR) ) { 10 perror ( " ignoresig " ); 11 exit (1); 12 } 13 printf ( "Break?\n"); 14 while(1) { / loop forever / 15 sleep_time = 5; 16 do { 17 printf ( "go asleep for %d sec\n", sleep_time ); 18 sleep_time = sleep ( sleep_time ); 19 } while( sleep_time!= 0); 20 ; 21 printf ( "And now?\n"); 22 / restore reaction / 23 if ( ( entrysig (SIGINT) == SIG_ERR) 24 ( entrysig (SIGQUIT) == SIG_ERR) ) { 25 perror ( " entrysig " ); 26 exit (2); 27 } 28 } 29 exit (0); 30 }

82 76 KAPITEL 3. SIGNALE Mit der Funktion sigaddset() können zu der Menge newact.sa_mask gezielt Signale hinzugenommen werden. Dies zeigt Programm 3.10 auf S. 76, eine kleine Modifikation von Programm Programm 3.8 (S. 74). 1 # include "sign. h" 2 # include < stdio. h> 3 # include <unistd. h> 4 Programm 3.10: Modifikation von 3.8 (sigaction2/sign.c) 5 void myignore( int sig ){ 6 printf ( " signal handler: SIGNAL = >%d<\n",sig); 7 sleep (10); 8 return ; 9 } struct sigaction newact, oldact ; Sigfunc ignoresig ( int sig ) { 14 static int first = 1; // static : bleibt erhalten! 15 newact. sa_handler = myignore; 16 if ( sigemptyset (&newact.sa_mask) < 0) 17 return SIG_ERR; 18 if ( sigaddset (&newact.sa_mask, 3) < 0) 19 return SIG_ERR; 20 if ( first ) { 21 first = 0; if ( sigaction ( sig, &newact, &oldact ) < 0) 24 return SIG_ERR; 25 else 26 return oldact. sa_handler ; 27 } else { 28 if ( sigaction ( sig, &newact, NULL) < 0) 29 return SIG_ERR; 30 else 31 return NULL; 32 } 33 } Sigfunc entrysig ( int sig ) { 36 if ( sigaction ( sig, &oldact, NULL) < 0 ) 37 return SIG_ERR; 38 else 39 return NULL; 40 }

83 3.6. REAKTION AUF SIGNALE: SIGACTION() 77 Ausführung: spatz$ show Break? go asleep for 5 sec ^Csignal handler: SIGNAL = >2< ^\ Quit spatz$ Unmittelbar nach der Eingabe von ^C wurde ein ^\ gegeben es dauert allerdings einige Sekunden (Ablauf von sleep(10) im Signalbehandler) bis ^\ zugestellt wird und logischerweise zur Termination führt (Quit). Mehr zu sigaction() im nächsten Abschnitt!

84 78 KAPITEL 3. SIGNALE 3.7 Die Zustellung von Signalen Die vorangegangenen Beispiele werfen die Frage auf, wie UNIX bei der Zustellung von Signalen vorgeht, wenn der Prozess zur Zeit nicht aktiv ist, gerade ein Systemaufruf für den Prozess abgearbeitet wird oder gerade ein Signalbehandler aktiv ist. Vom ISO-Standard für C wird in dieser Beziehung nichts festgelegt. Der POSIX-Standard geht jedoch genauer darauf ein: 3 Wenn ein Prozess ein Signal erhält, wird dieses Signal zunächst in den zugehörigen Verwaltungsstrukturen des Betriebssystems vermerkt. Signale, die für einen Prozess vermerkt sind, jedoch noch nicht zugestellt worden sind, werden als anhängige Signale bezeichnet. Wenn mehrere Signale mit der gleichen Nummer anhängig sind, ist nicht festgelegt, ob eine Mehrfachzustellung erfolgt. Es können also Signale wegfallen. Nur aktiv laufende Prozesse können Signale empfangen. Prozesse werden normalerweise durch die Existenz eines anhängigen Signals aktiv aber dieses kann auch längere Zeit in Anspruch nehmen, wenn dem zwischenzeitlich mangelnde Ressourcen entgegenstehen. Für jeden Prozess gibt es eine Menge blockierter Signale, die im Augenblick nicht zugestellt werden sollen. Dies hat nichts mit dem Ignorieren von Signalen zu tun, da blockierte Signale anhängig bleiben, bis die Blockierung aufgehoben wird. Der POSIX-Standard legt nicht fest, was mit der Signalbehandlung geschieht, wenn ein Signalbehandler aufgerufen wird. Möglich ist das Zurückfallen auf SIG_DFL (Voreinstellung mit Prozessterminierung) oder die temporäre automatische Blockierung des Signals bis zur Beendigung des Signalbehandlers. Alle modernen UNIX-Systeme wählen die zweite Variante. Dies lässt sich aber gemäß dem POSIX-Standard auch erzwingen, indem die umfangreichere Schnittstelle sigaction() anstelle von signal() verwendet wird. Allerdings ist sigaction() nicht mehr Bestandteil des ISO-Standards für C (aber eben des POSIX- Standards!). UNIX unterscheidet zwischen unterbrechbaren und unterbrechungsfreien Systemaufrufen. Zur ersteren Kategorie gehören weitgehend alle Systemaufrufe, die zu einer längeren Blockierung eines Prozesses führen können. Ist ein nicht blockiertes Signal anhängig, kann ein unterbrechbarer Systemaufruf aufgrund des Signals mit einer Fehlerindikation beendet werden. errno wird dann auf EINTR gesetzt. Dabei ist zu beachten, dass der unterbrochene Systemaufruf nach Beendigung der Signalbehandlung nicht fortgesetzt wird, sondern manuell erneut gestartet werden muss. Dies kann leider zu unerwarteten Überraschungseffekten führen, weil insbesondere auch die stdio-bibliothek keinerlei Vorkehrungen trifft, Systemaufrufe automatisch erneut aufzusetzen, falls es zu einer Unterbrechung kam. Dies ist eine wesentliche Schwäche sowohl des POSIX-Standards als auch der stdio-bibliothek und ein Grund mehr dafür, auf die Verwendung der stdio in kritischen Anwendungen völlig zu verzichten. 3 Siehe Signal Concepts, im Web unter

85 3.7. DIE ZUSTELLUNG VON SIGNALEN 79 Für die genauere Regulierung der Signalbehandlung bietet POSIX (jedoch nicht ISO-C) wie bereits erwähnt den Systemaufruf sigaction() an. Ein wesentlicher Unterschied zwischen sigaction() und signal() besteht bereits darin, dass per Voreinstellung das Signal, das eine Signalbehandlung auslöst, während der Bearbeitung automatisch blockiert wird. Ferner findet (solange nichts gegenteiliges in den Optionen angegeben wurde) keine implizite Veränderung des Signalbehandlers auf SIG_DFL nach der Signalbehandlung statt. Bei signal() ist dies ebenfalls möglich, jedoch nicht garantiert. In neueren UNIX- Version (ab System V.3) wurde der Signalmechanismus erweitert: man kann nun in einem Programm mit sighold(sig) einen kritischen Abschnitt beginnen und mit sigrelse(sig) (sigrelease, s.u.) abschliessen tritt in diesem Abschnitt das Signal sig auf, so wird es bis zur Bendigung des Abschnitts zurückgehalten (siehe z.b. 1 # include < signal.h> 2 # include < stdio.h> 3 # include < stdlib.h> 4 # include <unistd. h> 5 Programm 3.11: Verlust von Signalen (sigfire/sigfire.c) 6 static const int NOF_SIGNALS = 1000; 7 static volatile sig_atomic_t received_signals = 0; 8 static volatile sig_atomic_t terminated = 0; 9 10 static void count_signals ( int sig ) { received_signals ; 12 } void termination_handler ( int sig ) { 15 terminated = 1; 16 } int main() { 19 sighold (SIGUSR1); sighold (SIGTERM); pid_t child = fork (); 22 if ( child < 0) { 23 perror ( "fork" ); exit (1); 24 } 25 if ( child == 0) { 26 struct sigaction action = {0}; 27 action. sa_handler = count_signals ; 28 if ( sigaction (SIGUSR1, &action, 0)!= 0) { 29 perror ( " sigaction " ); exit (1); 30 } 31 action. sa_handler = termination_handler ; 32 if ( sigaction (SIGTERM, &action, 0)!= 0) { 33 perror ( " sigaction " ); exit (1); 34 }

86 80 KAPITEL 3. SIGNALE 35 sigrelse (SIGUSR1); sigrelse (SIGTERM); 36 while (! terminated ) pause (); 37 printf ( "[%d] received %d signals\n", 38 ( int ) getpid (), received_signals ); 39 exit (0); 40 } sigrelse (SIGUSR1); sigrelse (SIGTERM); 43 for ( int i = 0; i < NOF_SIGNALS; ++i) { 44 kill ( child, SIGUSR1); 45 } 46 printf ( "[%d] sent %d signals\n", 47 ( int ) getpid (), NOF_SIGNALS); 48 kill ( child, SIGTERM); wait(0); 49 } Programm 3.11 demonstriert den möglichen Verlust von Signalen trotz umfangreicher Vorsichtsmaßnahmen (hierbei werden spezielle ISO-C Funktionen wie verwendet. Das Experiment besteht hier im Versenden von 1000 SIGUSR1-Signalen, die beim Empfänger nachgezählt werden. In den Zeilen 26 bis 30 setzt der neu erzeugte Prozess die Funktion count_signals() als Signalbehandler für SIGUSR1 auf. Dabei wird hier sigaction() anstelle von signal() verwendet. Dies garantiert, dass während der Bearbeitung des Signals SIGUSR1 weitere eintreffende SIGUSR1-Signale aufgeschoben und nicht sofort in verschachtelter Form bearbeitet werden. Auf diese Weise ist die Atomizität des Hochzählens der Variable received_signals garantiert. Zu bedenken ist dabei, dass der Datentyp sig_atomic_t selbst nur die Atomizität einer einzelnen Lese- oder Schreiboperation gewährleistet. Bei einem Inkrement liegt jedoch eine Leseund eine Schreib-Operation vor. Zwischen diesen Operationen wäre eine Unterbrechung wegen einer Signalbehandlung denkbar. In diesem Beispiel versendet der übergeordnete Prozess 1000 Signale und der neu erzeugte Prozess zählt die eingetroffenen Signale. Wann darf der übergeordnete Prozess davon ausgehen, dass der neu erzeugte Prozess seinen Signalbehandler fertig aufgesetzt hat, so dass er mit dem Zählen beginnen kann? Wenn der übergeordnete Prozess zu früh Signale versendet, während noch die voreingestellte Reaktion für SIGUSR1 eingerichtet ist, würde dies nur zur vorzeitigen Terminierung des erzeugten Prozesses führen. Diese Problematik lässt sich vermeiden, indem die Signale, für die der erzeugte Prozess Behandler aufsetzt, vor der Prozesserzeugung blockiert werden. Das geht am einfachsten mit der Funktion sighold() (auf Zeile 19), die zum POSIX- Standard gehört. Zu jedem Prozess unterhält das Betriebssystem eine Menge blockierter Signale. Mit sighold() wird das angegebene Signal zu der Menge hinzugefügt. Entscheidend ist hier, dass die Menge der blockierten Signale an den neu erzeugten Prozess vererbt wird. So können nach dem fork() in aller Ruhe auf den Zeilen 26 bis 34 die Signalbehandler für SIGUSR1 und SIGTERM aufgesetzt werden, bevor auf Zeile 35 diese Signale wieder mit Hilfe von sigrelse() (eine unglückliche Abkürzung von signal release) wieder aus der Menge der blockierten Signale entfernt werden. Auch der übergeordnete Prozess nimmt die beiden Signale wieder heraus auf der Zeile 42. Wie wird das Experiment beendet? Da, wie das Experiment zeigen soll, Signale verloren gehen können, sollte der erzeugte Prozess nicht darauf warten, bis NOF_SIGNALS eingetroffen sind. Stattdessen wird SIGTERM vom übergeordneten Prozess an den erzeugten Prozess verwendet, um das Ende des Experiments zu signalisieren. Hier sind einige Läufe des Experiments, die demonstrieren, wie sehr die Werte voneinander abweichen können:

87 3.7. DIE ZUSTELLUNG VON SIGNALEN 81 doolin$ sigfire [22073] sent 1000 signals [22074] received 264 signals doolin$ sigfire [22075] sent 1000 signals [22076] received 227 signals doolin$ sigfire [22077] sent 1000 signals [22078] received 481 signals doolin$ sigfire [22079] sent 1000 signals [22080] received 136 signals doolin$ Wenn anstelle von nur SIGUSR1 zwei Signale SIGUSR1 und SIGUSR2 abwechselnd verwendet werden, können höhere Werte erzielt werden, wobei der Erfolg keinesfalls garantiert ist: doolin$ sigfire2 [22142] sent 1000 signals [22143] received 495 signals doolin$ sigfire2 [22144] sent 1000 signals [22145] received 462 signals doolin$ sigfire2 [22146] sent 1000 signals [22147] received 468 signals doolin$ sigfire2 [22151] sent 1000 signals [22152] received 688 signals doolin$ Dieses Experiment untermauert die Regel, dass einzelne Signale zuverlässig zugestellt werden, während es bei dem Mehrfachen Eintreffen des gleichen Signals zu Verlusten kommen kann. In der Praxis zeigen sich jedoch Signalverluste nur bei härteren Rahmenbedingungen, sei es durch ein explizites Dauerfeuer (wie in diesem Experiment) oder durch eine hohe Belastung der Maschine.

88 82 KAPITEL 3. SIGNALE 3.8 Signale als Indikatoren für terminierte Prozesse Wenn ein von einem Prozess P erzeugter Prozess K terminiert, so wird P das Signal SIGCHLD zugestellt. Die voreingestellte Reaktion (SIG_DFL) darauf ist Ignorieren (siehe bei Linux z.b. man -S7 signal). In Programm 3.12 (S. 82) werden nacheinander drei Prozesse erzeugt, die jeweils eine zufällig gewählte Zeit (zwischen 0 und 4 Sekunden) warten und dann mit einem zufälligen Exit-Status (zwischen 0 und 255) terminieren (diese Zufälligkeit ist zunächst ohne Bedeutung). Der Erzeuger legt sich 10 Sekunden schlafen und gibt danach seine und seiner Abkömmlinge Einträge in der Prozesstabelle aus: euklid$ show Create child processes: I m child 1 with PID = 4241! signal handler: SIGNAL = >17< I m child 2 with PID = 4242! signal handler: SIGNAL = >17< I m child 3 with PID = 4243! signal handler: SIGNAL = >17< PID TTY TIME CMD 4233 pts/3 00:00:00 bash 4240 pts/3 00:00:00 show 4241 pts/3 00:00:00 show <defunct> 4242 pts/3 00:00:00 show <defunct> 4243 pts/3 00:00:00 show <defunct> 4244 pts/3 00:00:00 ps signal handler: SIGNAL = >17< Parent: going to exit! euclid$ Auch hier ist wie oben bereits beschrieben zu beachten, dass einzelne Signale zuverlässig zugestellt werden, während es bei dem Mehrfachen Eintreffen des gleichen Signals zu Verlusten kommen kann. 1 # include < stdio. h> 2 # include < stdlib. h> 3 # include <unistd. h> 4 # include < signal. h> 5 Programm 3.12: Prozesse, auf die der Erzeuger nicht wartet (sigchld/show.c) 6 typedef void ( Sigfunc )( int ); 7 8 void myignore( int sig ){ 9 printf ( " signal handler: SIGNAL = >%d<\n",sig); 10 return ; 11 } struct sigaction newact, oldact ; Sigfunc ignoresig ( int sig ) {

89 3.8. SIGNALE ALS INDIKATOREN FÜR TERMINIERTE PROZESSE newact. sa_handler = myignore; 17 if ( sigemptyset (&newact.sa_mask) < 0) 18 return SIG_ERR; 19 newact. sa_flags = 0; 20 if ( sigaction ( sig, &newact, &oldact ) < 0) 21 return SIG_ERR; 22 else 23 return oldact. sa_handler ; 24 } int main() { 27 int pid [3], i = 3; 28 if ( ignoresig (SIGCHLD) == SIG_ERR) { 29 perror ( " ignoresig " ); 30 exit (1); 31 } printf ( "Create child processes:\n" ); 34 while(i>0) { 35 switch( pid[3 i]= fork ()) { 36 case 1: perror ( "fork" ); 37 exit (1); 38 case 0 : printf ( " I m child %d with PID = %d!\n", 4 i, (int) getpid()); 39 srand ( getpid ()); sleep (rand () % 5); 40 exit (( char) rand ()); 41 default : 42 sleep (1); 43 } 44 i ; 45 } 46 sleep (10); 47 switch( fork ()) { 48 case 1: perror ( "fork" ); 49 exit (1); 50 case 0: 51 execlp ( "ps", "ps", " l", NULL); 52 default : 53 sleep (1); 54 } 55 sleep (1); 56 printf ( "Parent: going to exit!\n" ); 57 exit (0); 58 }

90 84 KAPITEL 3. SIGNALE Das lässt sich leicht verändern. der Erzeuger bekommt schließlich Nachricht über die Termination eines erzeugten Prozesses. Wenn er auf das Signal einen Signalbehandler mit einem schlichten wait() einrichtet, wird der Eintrag des eben beendeten Kindprozesses dadurch abgeräumt (siehe Programm 3.13, S. 84): euclid$ show Create child processes: I m child 1 with PID = 4364! I m child 2 with PID = 4365! signal handler: SIGNAL = >17< Process 4365 terminated with Status 225 signal handler: SIGNAL = >17< Process 4364 terminated with Status 248 I m child 3 with PID = 4366! signal handler: SIGNAL = >17< Process 4366 terminated with Status 154 Process table: PID TTY TIME CMD 4356 pts/3 00:00:00 bash 4363 pts/3 00:00:00 show 4367 pts/3 00:00:00 ps signal handler: SIGNAL = >17< Process 4367 terminated with Status 0 Parent: going to exit! euclid$ Programm 3.13: Prozesse, auf die der Erzeuger ohne zu blockieren wartet (sigchld1/show.c) 1 # include < stdio. h> 2 # include < stdlib. h> 3 # include <unistd. h> 4 # include < signal. h> 5 # include <sys/wait. h> 6 7 typedef void ( Sigfunc )( int ); 8 9 void myignore( int sig ){ 10 int status, pid ; 11 printf ( " signal handler: SIGNAL = >%d<\n",sig); 12 pid = wait(& status ); 13 printf ( " Process %d terminated with Status %d\n", pid, (status >> 8) & 0377); 14 return ; 15 } struct sigaction newact, oldact ; Sigfunc ignoresig ( int sig ) { 20 newact. sa_handler = myignore; 21 if ( sigemptyset (&newact.sa_mask) < 0) 22 return SIG_ERR; 23 newact. sa_flags = 0; 24 if ( sigaction ( sig, &newact, &oldact ) < 0) 25 return SIG_ERR;

91 3.8. SIGNALE ALS INDIKATOREN FÜR TERMINIERTE PROZESSE else 27 return oldact. sa_handler ; 28 } int main() { 31 int pid [3], i = 3; 32 if ( ignoresig (SIGCHLD) == SIG_ERR) { 33 perror ( " ignoresig " ); 34 exit (1); 35 } printf ( "Create child processes:\n" ); 38 while(i>0) { 39 switch( pid[3 i]= fork ()) { 40 case 1: perror ( "fork" ); 41 exit (1); 42 case 0 : printf ( " I m child %d with PID = %d!\n", 4 i, (int) getpid()); 43 srand ( getpid ()); sleep (rand () % 5); 44 exit (( char) rand ()); 45 default : 46 sleep (1); 47 } 48 i ; 49 } 50 sleep (10); 51 switch( fork ()){ 52 case 1: perror ( "fork" ); 53 exit (1); 54 case 0: 55 printf ( "Process table : \n"); 56 execlp ( "ps", "ps",null); 57 default : 58 sleep (1); 59 } 60 printf ( "Parent: going to exit!\n" ); 61 exit (0); 62 }

92 86 KAPITEL 3. SIGNALE Diese Lösung ist einfach, aber nicht allzu sicher. Das Problem ist, dass bei einer zeitgleichen Terminierung mehrerer Prozesse es wiederum zu Verlusten des SIGCHLD-Signals kommen kann. Somit kann man sich nicht darauf verlassen, dass für jeden terminierten Prozess der Signalbehandler genau einmal aufgerufen wird. Deswegen empfiehlt es sich, den Status aller bereits terminierter Prozesse abzurufen. Dies wird mit waitpid() unter der Verwendung der Option WNOHANG erreicht. Diese verhindert ein Blockieren des Systemaufrufs waitpid() und somit kann waitpid() gefahrlos solange aufgerufen werden, bis waitpid() 0 oder einen negativen Wert zurückliefert. Programm 3.14: Prozesse, auf die der Erzeuger ohne zu blockieren wartet (sigchld2/show.c) 1 # include < stdio. h> 2 # include < stdlib. h> 3 # include <unistd. h> 4 # include < signal. h> 5 # include <sys/wait. h> 6 # include <sys/types. h> 7 8 typedef void ( Sigfunc )( int ); 9 10 void myignore( int sig ){ 11 int status, pid ; 12 printf ( " signal handler: SIGNAL = >%d<\n",sig); 13 while (( pid = waitpid (( pid_t ) 1, &status, WNOHANG)) > 0) { 14 printf ( "%d terminated with %d\n", pid, (status >> 8) & 0377); 15 }; 16 return ; 17 } struct sigaction newact, oldact ; Sigfunc ignoresig ( int sig ) { 22 newact. sa_handler = myignore; 23 if ( sigemptyset (&newact.sa_mask) < 0) 24 return SIG_ERR; 25 newact. sa_flags = 0; 26 if ( sigaction ( sig, &newact, &oldact ) < 0) 27 return SIG_ERR; 28 else 29 return oldact. sa_handler ; 30 } int main() { 33 int pid [3], i = 3; 34 if ( ignoresig (SIGCHLD) == SIG_ERR) { 35 perror ( " ignoresig " ); 36 exit (1); 37 } printf ( "Create child processes:\n" ); 40 while(i>0) { 41 switch( pid[3 i]= fork ()) { 42 case 1: perror ( "fork" ); 43 exit (1);

93 3.8. SIGNALE ALS INDIKATOREN FÜR TERMINIERTE PROZESSE case 0 : printf ( " I m child %d with PID = %d!\n", 4 i, (int) getpid()); 45 srand ( getpid ()); sleep (rand () % 5); 46 exit (( char) rand ()); 47 default : 48 sleep (1); 49 } 50 i ; 51 } 52 sleep (10); 53 switch( fork ()){ 54 case 1: perror ( "fork" ); 55 exit (1); 56 case 0: 57 printf ( "Process table : \n"); 58 execlp ( "ps", "ps",null); 59 default : 60 sleep (1); 61 } 62 printf ( "Parent: going to exit!\n" ); 63 exit (0); 64 } Um die Prozesse und deren Status zu erfassen, sind umfangreichere Datenstrukturen erforderlich. Wenn auf gemeinsame Datenstrukturen von mehreren Seiten in asynchroner Form zugegriffen werden kann, dann sind die zugreifenden Programmbereiche sogenannte kritische Regionen. Nur durch den gegenseitigen Ausschluss wird verhindert, dass die betroffene Datenstruktur durch einen ungünstigen Unterbrechungszeitpunkt inkonsistent wird. Da sigaction() anstelle von signal() verwendet wird, ist bereits sichergestellt, dass der Signalbehandler nicht mehrfach in verschachtelter Form aufgerufen wird. Somit müssen nur alle verbliebenen Programmbereiche, die auf die gleiche Datenstruktur zugreifen, in sighold() und sigrelse() geklammert werden. Dies soll aber hier nicht weiter behandelt werden!

94 88 KAPITEL 3. SIGNALE 3.9 Signalbehandlung in einer (Mini-)Shell Die mit dem Programmtext 2.29 vorgestellte einfache Shell kümmerte sich nicht um die Signalbehandlung. Dies kann zu überraschenden Effekten führen, wenn der Versuch unternommen wird, aufgerufene Prozesse beispielsweise mit SIGINT zu unterbrechen: euclid$ tinysh % cat >OUT some input... ^Ceuclid$ Hier wurde zunächst cat aufgerufen und nach der ersten eingegebenen Zeile CTRL-c eingegeben, welches bei den aktuellen Einstellungen zu einem SIGINT an alle Prozesse der aktuellen Sitzung führte. Zur aktuellen Sitzung gehört jedoch nicht nur das gerade laufende cat-kommando, sondern natürlich auch tinysh. Da tinysh keinerlei Vorkehrungen traf, wurde es genauso wie cat einfach terminiert, weil dies die voreingestellte Reaktion ist. Wäre tinysh die Login-Shell gewesen, wäre damit die gesamte Sitzung beendet. Hier in diesem Beispiel wurde SIGINT offensichtlich von der normalen Shell ignoriert. Signalbehandlung einer Shell: Wenn ein Kommando im Vordergrund läuft, muss die Shell die Signale SIGINT und SIGQUIT ignorieren (sie soll ja schließlich weiterlaufen). Wenn ein Kommando im Hintergrund läuft, müssen für diesen Prozess SIGINT und SIGQUIT ignoriert werden. Wenn die Shell ein Kommando einliest, sollten SIGINT und SIGQUIT die Neu-Eingabe des Kommandos ermöglichen. Bezüglich SIGHUP muss nichts unternommen werden. Die Worte auf der Kommandozeile werden jetzt differenzierter betrachtet. Zulässige Symbole (token) der Kommandozeilen-Sprache sind: > (T_GT) Ausgabeumlenkung - danach muss ein Dateiname kommen >> (T_GTGT) Ausgabeumlenkung zum Anfügen - danach muss ein Dateiname kommen < (T_LT) Eingabeumlenkung - danach muss ein Dateiname kommen 2> (T_TWO_GT) Umlenkung der Diagnose-Ausgabe - danach muss ein Dateiname kommen & (T_AMP) Ausführung des Kommandos im Hintergrund - die darauf folgenden Zeichen bis zum Zeilenende werden hier ignoriert Die Erkennung der verschiedenen Symbole (token) beschreibt der in Abb. 3.1 dargestellte Automat. Die Syntax-Analyse der Kommandozeile wird wie in Abb. 3.2 (S. 89) dargestellt durchgeführt. Anm.: In Abb. 3.2, S. 89 sind Fehlerzustände nicht dargestellt; von den vier Arten der I/O- Umlenkung >, >>, 2>, < ist jeweils maximal eine zulässig!

95 3.9. SIGNALBEHANDLUNG IN EINER (MINI-)SHELL 89 space > > > out(t_gtgt) else > ungetc; out(t_gt); NEUTRAL 2 TWO_GT \n > out(t_nl) & > out(t_amp) < > out(t_lt) <,>,&,\n,space > ungetc; out(t_word) else > word[0] = c; i++ > > out(t_two_gt) else > ungetc; word[0]= 2 ; else > word[i]=c; i++ WORD i++ GTGT Abbildung 3.1: Token-Erkennung T_WORD T_LT start T_WORD T_WORD infile expected T_GT T_GTGT T_TWO_GT T_NL T_AMP outfile expected command background command Abbildung 3.2: Syntax-Analyse der Kommandozeile Ablauf-Schema:

96 90 KAPITEL 3. SIGNALE Signal Handler einrichten LOOP FOREVER Prompt ausgeben Kommandozeile lesen > Token erkennen und Syntax prüfen Neuen Prozess erzeugen ggf. I/O Umlenkung vornehmen falls im Hintergrund: Signal Handler einrichten ggf. Warten via exec Kommando starten Abbildung 3.3: Ablauf der MidiShell Modularisierung: main.c: termination.h termination.c exit-status ausgeben cmd.h cmd.c Syntax-Analyse I/O-Umlenkung ggf. Signalhandler Kommando starten sign.c sign.h Signal-Handler defs.h grundlegende Vereinbarungen gettoken.h gettoken.c Token holen Abbildung 3.4: Struktur der MidiShell

97 3.9. SIGNALBEHANDLUNG IN EINER (MINI-)SHELL 91 Die Programme im Einzelnen: Programm 3.15: Midi-Shell: Grundlegende Vereinbarungen (midishell/defs.h) 1 / Grundlegende Vereinbarungen : / 2 3 typedef enum {FALSE, TRUE} BOOLEAN; 4 5 / Token Symbole: / 6 typedef enum {T_WORD, T_GT, T_GTGT, T_TWO_GT, T_AMP, 7 T_LT, T_NL, T_EOF, T_ERR} TOKEN; 8 9 # define BADFD 2 10 # define MAXARG # define MAXWORD # define MAXFNAME 256 Programm 3.16: Midi-Shell: Schnittstelle zur Tokenbestimmung (midishell/gettoken.h) 1 # ifdef GET_H 2 # include < stdio.h> 3 # include "defs.h" 4 #else 5 / lexikalische Analyse der Kommandozeile / 6 extern TOKEN gettoken(char word); 7 extern void skip_line (); 8 # endif Programm 3.17: Midi-Shell: Schnittstelle zum Signalbehandler (midishell/sign.h) 1 # ifdef SIGN_H 2 # include < signal.h> 3 # include <sys/wait. h> 4 # include <unistd. h> 5 6 typedef void ( Sigfunc )( int ); 7 8 #else 9 10 # define SIGN_H 11 / avoid multiple includes / 12 # include < signal.h> typedef void ( Sigfunc )( int ); Sigfunc ignoresig ( int ); 17 / ignore interrupt and avoid zombies 18 just for midishell ( parent ) 19 / 20 Sigfunc ignoresig_bg ( int ); 21 / ignore interrupt 22 just for execution of background commands 23 / 24 Sigfunc entrysig ( int ); 25 / restore reaction on interrupt /

98 92 KAPITEL 3. SIGNALE 26 # endif Programm 3.18: Midi-Shell: Schnittstelle zur Kommandoausführung (midishell/cmd.h) 1 # ifdef CMD_H 2 # include < stdio.h> 3 # include < strings.h> 4 # include < fcntl.h> 5 # include <errno. h> 6 # include <unistd. h> 7 # include < stdlib.h> 8 # include "defs.h" 9 # include "gettoken. h" 10 # include "sign.h" 11 #else extern void redirect ( int srcfd, char srcfile, 14 int dstfd, char dstfile, 15 int errfd, char errfile, 16 BOOLEAN append, BOOLEAN bckgrnd); 17 / I / O redirection / extern int invoke ( int argc, char argv [], 20 int srcfd, char srcfile, 21 int dstfd, char dstfile, 22 int errfd, char errfile, 23 BOOLEAN append, BOOLEAN bckgrnd); 24 / invoke () execute simple command / extern TOKEN command(int waitpid); 27 / collect a simple command from stdin 28 by calling gettoken () do redirection 29 if necessary by redirect () and execute 30 command by invoke () 31 / # endif Programm 3.19: Midi-Shell: Schnittstelle zur Ausgabe des Exitstatus (midishell/termination.h) 1 # ifdef TERM_H 2 3 # include < stdio.h> 4 # define lowbyte (w) (( w) & 0377) 5 # define highbyte (w) lowbyte (( w) >> 8) 6 # define MAXSIG #else 9 void statusprt ( int status ); 10 # endif

99 3.9. SIGNALBEHANDLUNG IN EINER (MINI-)SHELL 93 1 # include < stdio.h> 2 # include < stdlib.h> 3 # include < string.h> 4 # include <unistd. h> 5 # include <sys/wait. h> 6 7 # include "defs.h" 8 # include "sign.h" 9 # include "cmd.h" 10 # include " termination. h" int main() { 13 char prompt; 14 int pid, status ; 15 TOKEN term; 16 Programm 3.20: Midi-Shell: main-funktion Start (midishell/main.c) 17 if ( ( ignoresig (SIGINT) == SIG_ERR) 18 ( ignoresig (SIGCHLD) == SIG_ERR)) { 19 perror ( " ignoresig " ); 20 exit (1); 21 } prompt = "midish> "; while (1) { 26 printf ( "%s", prompt ); 27 term = command(&pid); 28 if (term == T_ERR) { 29 continue; 30 } 31 if ( (term!= T_AMP) && (pid!= 0) ) { 32 / wait for foreground process 33 if fg process terminates 34 the signal handler will handle the exit status 35 ( will do his wait!) and 36 the following waitpid will return with 1! 37 / 38 waitpid ( pid,& status,0); 39 } 40 } 41 printf ( "\n\n"); 42 exit (0); 43 }

100 94 KAPITEL 3. SIGNALE 1 # define GET_H 2 # include "gettoken. h" 3 Programm 3.21: Midi-Shell: Tokenbestimmung (midishell/gettoken.c) 4 void skip_line () { 5 int c ; 6 while( (c = getchar ())!= \n ); 7 } 8 9 / lexikalische Analyse der Kommandozeile / TOKEN gettoken(char word) { 12 int c ; 13 char w; 14 enum {NEUTRAL, TWO_GT, GTGT, INWORD } state = NEUTRAL; w = word; 17 while ( (c= getchar () )!= EOF ) { 18 switch ( state ) { case NEUTRAL: 21 switch (c) { 22 case & : 23 / read rest from line : / 24 skip_line (); 25 return (T_AMP); 26 case < : 27 return (T_LT); 28 case \n : 29 return (T_NL); 30 case : 31 case \t : 32 continue; 33 case > : 34 state = GTGT; 35 continue; 36 case 2 : 37 state = TWO_GT; 38 continue; 39 default : 40 state = INWORD; 41 w++ = c; 42 continue; 43 } 44 case GTGT: 45 if (c == > ) 46 return (T_GTGT); 47 ungetc (c, stdin ); 48 return (T_GT); 49 case TWO_GT: 50 if (c == > ) 51 return (T_TWO_GT); 52 w++ = 2 ;

101 3.9. SIGNALBEHANDLUNG IN EINER (MINI-)SHELL ungetc (c, stdin ); 54 state = INWORD; 55 continue; 56 case INWORD: 57 switch (c) { 58 case & : 59 case < : 60 case > : 61 case \n : 62 case : 63 case \t : 64 ungetc (c, stdin ); 65 w = \0 ; 66 return (T_WORD); 67 default : 68 w++ =c; 69 continue; 70 } 71 } 72 } 73 return (T_EOF); 74 }

102 96 KAPITEL 3. SIGNALE 1 # include < string.h> 2 # define CMD_H 3 # include "cmd.h" 4 Programm 3.22: Midi-Shell: Kommandoausführung (midishell/cmd.c) 5 / Redirection of I / O: 6 after redirect the caller has file descriptor 0 7 for input, descriptor 1 for output, and 2 for stderr! 8 / 9 static void redirect ( int srcfd, char srcfile, 10 int dstfd, char dstfile, 11 int errfd, char errfile, 12 BOOLEAN append, BOOLEAN bckgrnd) { 13 int flags, fd ; 14 / we expect for srcfd : 15 0: nothing to do, 2: redirect 0 ( stdin ) to file 16 ( 1 indicates error ) we expect for dstfd : 19 1: nothing to do, 2: redirect 1 ( stdout ) to file 20 ( with respect to parameter append we expect for errfd : 23 2: nothing to do 24 2: redirect 2 ( stderr ) to file 25 / if ( ( srcfd == 0) && bckgrnd ) { 28 strcpy ( srcfile, "/dev/null"); 29 / /dev/ null > 30 there is nothing to read, only EOF 31 a background command couldn t get any 32 input from stdin ; 33 / 34 srcfd = BADFD; 35 / so redirect 0 to srcfile 36 set to / dev/ null above 37 / 38 } if ( srcfd!= 0) { 41 / 0 should point to file for input 42 / 43 if ( close (0) == 1) 44 perror ( " close " ); 45 else if (open( srcfile, O_RDONLY, 0) == 1) { 46 fprintf ( stderr, "can t open %s\n", srcfile ); 47 exit (1); 48 } 49 } / now file is referenced by file descriptor 0 52 /

103 3.9. SIGNALBEHANDLUNG IN EINER (MINI-)SHELL / now the same for std_output / 55 if ( dstfd!= 1) { 56 / output to file (>,>> ( dstfd == 2) 57 / 58 if ( close (1) == 1) 59 perror ( " close " ); 60 else { 61 flags = O_WRONLY O_CREAT; 62 if (! append) / > file / 63 flags = O_TRUNC; 64 else 65 flags = O_APPEND; 66 if ( open( dstfile, flags, 0666) == 1) { 67 / open returns the smallest 68 free file descriptor 69 / 70 fprintf ( stderr, "can t create %s\n", dstfile ); 71 exit (1); 72 } 73 } 74 } 75 / now the same for std_error / 76 if ( errfd!= 2) { 77 / output to file 78 / 79 if ( close (2) == 1) 80 perror ( " close " ); 81 else { 82 flags = O_WRONLY O_CREAT O_TRUNC; if ( open( errfile, flags, 0664) == 1) { 85 / open returns the smallest 86 free file descriptor 87 / 88 fprintf ( stderr, "can t create %s\n", errfile ); 89 exit (1); 90 } 91 } 92 } for ( fd =3; fd < 20; fd++) (void) close ( fd ); 95 / the caller now only needs 0,1,2!!! / 96 } / invoke () execute simple command 99 in a new process 100 / 101 static int invoke ( int argc, char argv [], 102 int srcfd, char srcfile, 103 int dstfd, char dstfile, 104 int errfd, char errfile, 105 BOOLEAN append, BOOLEAN bckgrnd) { 106 / uses redirect () /

104 98 KAPITEL 3. SIGNALE 107 int pid ; / empty commandline??? / 110 if ( argc == 0 ) 111 return (0); 112 switch ( pid = fork () ) { 113 case 1: 114 fprintf ( stderr, "Can t create new process\n"); 115 return (0); 116 case 0: 117 / CHILD / redirect ( srcfd, srcfile, dstfd, dstfile, errfd, errfile, 120 append, bckgrnd ); / restore reaction on interrupt and quit??? 123 not necessary, but could be as follows : 124 if (! bckgrnd ) 125 entrysig (SIGINT); 126 / / install signal handler for background : / 129 if (bckgrnd) { 130 if ( ignoresig_bg (SIGINT) == SIG_ERR) { 131 perror ( "ignorsig_bg SIGINT"); 132 exit (1); 133 } 134 } execvp ( argv [0], argv ); 138 / this shouldn t be reached / 139 fprintf ( stderr, "can t execute %s\n", argv [0]); 140 exit (1); 141 default : 142 / PARENT / 143 if ( srcfd > 0 && close ( srcfd ) == 1) 144 perror ( " close src" ); 145 if ( dstfd > 1 && close ( dstfd ) == 1) 146 perror ( " close dst" ); 147 if ( errfd > 2 && close ( errfd ) == 1) 148 perror ( " close error" ); if ( bckgrnd) 151 printf ( "%d\n", pid ); 152 return ( pid ); 153 } 154 } TOKEN command(int waitpid) { 157 / int waitpid : perhaps we have to wait 158 for the command return value : T_NL or T_AMP on success, T_ERR on error

105 3.9. SIGNALBEHANDLUNG IN EINER (MINI-)SHELL uses : gettoken (), invoke () 163 / TOKEN token, term; 166 int argc, srcfd, dstfd, errfd, pid ; 167 char argv[maxarg+1]; 168 char srcfile [MAXFNAME+1]; 169 char dstfile [MAXFNAME+1]; 170 char errfile [MAXFNAME+1]; 171 char word[maxword], malloc(); 172 BOOLEAN append; argc = 0; srcfd = 0; dstfd = 1; errfd = 2; 175 / defaults / while (1) { 178 switch ( token = gettoken (word)) { 179 case T_WORD: 180 if ( argc == MAXARG) { 181 fprintf ( stderr, "Too many args\n"); 182 break; 183 } 184 if (( argv [ argc]= malloc ( strlen (word)+1))==null) { 185 fprintf ( stderr, "Out of arg memory\n"); 186 break; 187 } 188 strcpy (argv[ argc ], word ); 189 argc++; 190 continue; 191 case T_LT: 192 if ( srcfd!= 0) { 193 fprintf ( stderr, "syntax error : EXTRA <\n"); 194 skip_line (); 195 return T_ERR; 196 } 197 if ( gettoken ( srcfile )!= T_WORD) { 198 fprintf ( stderr, "syntax error : Illegal <\n"); 199 skip_line (); 200 return T_ERR; 201 } 202 / we have to redirect 0 to a file / 203 srcfd = BADFD; 204 continue; 205 case T_GT: 206 case T_GTGT: 207 if ( dstfd!= 1) { 208 fprintf ( stderr, "syntax error : EXTRA > or >>\n"); 209 skip_line (); 210 return T_ERR; 211 } 212 if ( gettoken ( dstfile )!= T_WORD) { 213 fprintf ( stderr, "syntax error : Illegal > or >>\n"); 214 skip_line ();

106 100 KAPITEL 3. SIGNALE 215 return T_ERR; 216 } 217 dstfd = BADFD; 218 append = ( token == T_GTGT); 219 continue; 220 case T_TWO_GT: 221 if ( errfd!= 2) { 222 fprintf ( stderr, "syntax error : EXTRA 2>\n"); 223 skip_line (); 224 return T_ERR; 225 } 226 if ( gettoken ( errfile )!= T_WORD) { 227 fprintf ( stderr, "syntax error : Illegal 2>\n"); 228 skip_line (); 229 return T_ERR; 230 } 231 errfd = BADFD; 232 continue; case T_AMP: 235 case T_NL: 236 term = token ; / one simple command is read / 239 argv [ argc ] = NULL; / Eingabe von > file allein : loeschen / anlegen 242 einer Datei : 243 / 244 if ( ( argc == 0 ) && (dstfd == BADFD) && (!append)) { 245 dstfd = open( dstfile, O_WRONLY O_CREAT O_TRUNC, 0664); 246 close ( dstfd ); 247 return T_NL; 248 } pid = invoke ( argc, argv, srcfd, srcfile, dstfd, 251 dstfile, errfd, errfile, 252 append, term == T_AMP); 253 waitpid = pid ; while ( argc >= 0) 256 free (argv[ argc ]); 257 return ( term ); 258 case T_EOF: 259 printf ( "\n\n"); 260 exit (0); 261 default : exit (1); / not reached! / 262 } 263 }; / end of while (1) / 264 / if reached, then error : / 265 return T_ERR; 266 }

107 3.9. SIGNALBEHANDLUNG IN EINER (MINI-)SHELL # define SIGN_H 2 3 # include < stdio. h> 4 # include "sign. h" 5 Programm 3.23: Midi-Shell: Signalbehandler (midishell/sign.c) 6 void shell_handler ( int sig ){ 7 if ( ( sig == SIGCHLD) (sig == SIGCLD)) { 8 int status ; long gone ; 9 gone = waitpid (0, &status, WNOHANG); 10 if ( gone <= 0 ) return ; 11 printf ( "Terminated: %ld with ", gone); 12 if ( status & 0177) 13 printf ( " Signal %d\n", status & 0177); 14 else 15 printf ( " exit status : %d\n", ( status >> 8)&0xff ); 16 } 17 return ; 18 } struct sigaction newact, oldact ; Sigfunc ignoresig ( int sig ) { 23 static int first = 1; 24 newact. sa_handler = shell_handler ; 25 if ( first ) { 26 first = 0; 27 if ( sigemptyset (&newact.sa_mask) < 0) 28 return SIG_ERR; 29 newact. sa_flags = 0; 30 newact. sa_flags = SA_RESTART; 31 if ( sigaction ( sig, &newact, &oldact ) < 0) 32 return SIG_ERR; 33 else 34 return oldact. sa_handler ; 35 } else { 36 if ( sigaction ( sig, &newact, NULL) < 0) 37 return SIG_ERR; 38 else 39 return NULL; 40 } 41 } struct sigaction newact_bg, oldact_bg ; Sigfunc ignoresig_bg ( int sig ) { 46 newact_bg. sa_handler = SIG_IGN; 47 if ( sigemptyset (&newact_bg.sa_mask) < 0) 48 return SIG_ERR; 49 newact_bg. sa_flags = 0; 50 newact_bg. sa_flags = SA_RESTART; 51 if ( sigaction ( sig, &newact_bg, &oldact_bg ) < 0) 52 return SIG_ERR;

108 102 KAPITEL 3. SIGNALE 53 else return oldact_bg. sa_handler ; 54 } Sigfunc entrysig ( int sig ) { 57 if ( sigaction ( sig, &oldact, NULL) < 0 ) 58 return SIG_ERR; 59 else return NULL; 60 } 1 # define TERM_H 2 # include " termination. h" 3 Programm 3.24: Midi-Shell: Termination (midishell/termination.c) 4 static char sigmsg [] = { 5 "", 6 "Hangup", "Interrupt ", "Quit", 7 " Illegal instruction ", "Trace trap", 8 "IOT instruction", "EMT instruction", 9 " Floating point exception", 10 " Kill ", "Bus error", 11 "Segmentation violation", 12 "Bad arg to system call", 13 "Write on pipe", "Alarm clock", 14 "Terminate signal", 15 "User signal 1", "User signal 2", 16 "Death of child", "Power fail" 17 }; void statusprt ( int status ) { 20 int code ; if ( lowbyte ( status ) == 0) { 23 // normal termination 24 code = highbyte ( status ); 25 printf ( " Exit code %d\n", code); 26 } else { 27 if (( code = ( status & 0177)) <= MAXSIG) 28 printf ( "%s", sigmsg[ code ]); 29 else 30 printf ( "Signal# %d", code ); 31 if (( status & 0200) == 0200) 32 printf (" core dumped"); 33 printf ( "\n"); 34 } 35 }

109 3.9. SIGNALBEHANDLUNG IN EINER (MINI-)SHELL # include <unistd. h> 2 # include < stdlib.h> 3 4 int main() { 5 sleep (20); 6 exit (0); 7 } 1 # include < stdio.h> 2 # include < stdlib.h> 3 Programm 3.25: Midi-Shell: Testprogramm 1 (midishell/sleepwell.c) Programm 3.26: Midi-Shell: Testprogramm 2 (midishell/read-something.c) 4 int main() { 5 int n; 6 printf ("Give number:"); 7 if ( scanf ( "%d", &n) == 1) { 8 printf ( "got : %d\n", n); 9 exit (0); 10 } else { 11 exit (1); 12 } 13 } Das Makefile: # eine kleine Shell - die midishell midish: main.o cmd.o sign.o termination.o gettoken.o gcc -Wall -o midish main.o cmd.o sign.o termination.o gettoken.o # ausfuehrbares Programm: midish gcc -Wall -o sleepwell sleepwell.c # sleepwell: ein Dauerlaeufer gcc -Wall -o read-something read-something.c # read-something: lies von stdin main.o: main.c defs.h sign.h cmd.h termination.h gcc -Wall -c main.c cmd.o: cmd.c cmd.h gettoken.h defs.h sign.h gcc -Wall -c cmd.c gettoken.o: gettoken.c gettoken.h defs.h gcc -Wall -c gettoken.c sign.o: sign.c sign.h gcc -Wall -c sign.c termination.o: termination.c termination.h gcc -Wall -c termination.c.phony: clean realclean clean: rm -f *.o core realclean: rm -f *.o core midish sleepwell read-something

110 104 KAPITEL 3. SIGNALE Ausführung: hypatia$ make gcc -Wall -c main.c gcc -Wall -c cmd.c gcc -Wall -c sign.c gcc -Wall -c termination.c gcc -Wall -c gettoken.c gcc -Wall -o midish main.o cmd.o sign.o termination.o gettoken.o # ausfuehrbares Programm: midish gcc -Wall -o sleepwell sleepwell.c # sleepwell: ein Dauerlaeufer gcc -Wall -o read-something read-something.c # read-something: lies von stdin hypatia$ midish midish>ps PID TTY TIME CMD 212 tty1 00:00:00 bash 846 pts/0 00:00:00 bash 895 pts/0 00:00:00 midish 896 pts/0 00:00:00 ps midish>sleepwell # Eingabe von ctrl-c Interrupt midish>sleepwell & 898 midish>ps PID TTY TIME CMD 212 tty1 00:00:00 bash 846 pts/0 00:00:00 bash 895 pts/0 00:00:00 midish 898 pts/0 00:00:00 sleepwell 899 pts/0 00:00:00 ps midish> # ctrl-c midish>ps PID TTY TIME CMD 212 tty1 00:00:00 bash 846 pts/0 00:00:00 bash 895 pts/0 00:00:00 midish 898 pts/0 00:00:00 sleepwell 900 pts/0 00:00:00 ps midish>read-something Give number:5 got: 5

111 3.9. SIGNALBEHANDLUNG IN EINER (MINI-)SHELL 105 midish>read-something & 902 Give number:midish>5 can t execute 5 Exit code 1 midish>cat < text dies ist ein Text midish>cat < text > new > very_new syntax error: EXTRA > or >> midish>cat -? cat: invalid option --? Try cat --help for more information. Exit code 1 midish>cat -? 2> err Exit code 1 midish>cat err cat: invalid option --? Try cat --help for more information. midish> # ctrl-d hypatia$

112 106 KAPITEL 3. SIGNALE 3.10 Überblick der Signale aus dem POSIX-Standard Signal Voreinstellung Beschreibung SIGABRT A Process abort signal. SIGALRM T Alarm clock. SIGBUS A Access to an undefined portion of a memory object. SIGCHLD I Child process terminated, stopped, or continued. SIGCONT C Continue executing, if stopped. SIGFPE A Erroneous arithmetic operation. SIGHUP T Hangup. SIGILL A Illegal instruction. SIGINT T Terminal interrupt signal. SIGKILL T Kill (cannot be caught or ignored). SIGPIPE T Write on a pipe with no one to read it. SIGQUIT A Terminal quit signal. SIGSEGV A Invalid memory reference. SIGSTOP S Stop executing (cannot be caught or ignored). SIGTERM T Termination signal. SIGTSTP S Terminal stop signal. SIGTTIN S Background process attempting read. SIGTTOU S Background process attempting write. SIGUSR1 T User-defined signal 1. SIGUSR2 T User-defined signal 2. SIGPOLL T Pollable event. SIGPROF T Profiling timer expired. SIGSYS A Bad system call. SIGTRAP A Trace/breakpoint trap. SIGURG I High bandwidth data is available at a socket. SIGVTALRM T Virtual timer expired. SIGXCPU A CPU time limit exceeded. SIGXFSZ A File size limit exceeded. Voreinstellung T A I S C Beschreibung Abbruch des Prozesses. Bei dem bei wait() zurückgelieferten Status ist WIFSIGNALED wahr und über WTERMSIG lässt sich das Signal ermitteln. Analog zu T. Hinzu kommt möglicherweise noch die Erzeugung eines Speicherauszugs (in der Datei core). Letzteres lässt sich mit WCOREDUMP untersuchen. Das Signal wird ignoriert. Der Prozess wird gestoppt. Der Prozess wird fortgesetzt. Tabelle 3.2: Im POSIX-Standard genannte Signale (Quelle: Die Tabelle 3.2 liefert einen Überblick aller vom POSIX-Standard genannten Signale. Einzelne Implementierungen können noch weitere Signale unterstützen. Die Signale lassen sich dabei in mehrere Gruppen aufteilen: Programmierfehler: SIGBUS, SIGFPE, SIGILL, SIGSEGV und SIGSYS. Ressourcenverbrauch: SIGVTALRM, SIGXCPU und SIGXFSZ.

113 3.10. ÜBERBLICK DER SIGNALE AUS DEM POSIX-STANDARD 107 Prozeßkontrolle: SIGCONT, SIGKILL, SIGSTOP, SIGTERM und SIGTRAP. Sitzungskontrolle: SIGHUP, SIGINT, SIGQUIT und SIGTSTP. Ereignis-Indikatoren: SIGALRM, SIGCHLD, SIGPIPE, SIGPOLL, SIGPROF, SIGTTIN, SIGTTOU, SIGURG, SIGUSR1, SIGUSR2 und SIGVTALRM.

114 108 KAPITEL 3. SIGNALE

115 Kapitel 4 Inter-Prozess-Kommunikation (IPC) 4.1 Einführung Jeder UNIX-Prozess besitzt seinen eigenen Context. Innerhalb eines Prozesses können die verschiedenen Moduln über Parameter und Rückgabewerte bei Funktionsaufrufen oder über globale Variablen Daten austauschen. Wollen jedoch zwei eigenständige Prozesse Daten miteinander austauschen, so kann dies nur über den Kernel via System Calls erfolgen. Denn der Kernel verhindert unkontrollierte Übergriffe eines Prozesses in den Adressraum eines anderen Prozesses. user process user process KERNEL Abbildung 4.1: IPC nur über den Kernel Bei diesem Konzept müssen beide Prozesse explizit der Kommunikation zustimmen. Der UNIX- Kernel bietet mit seinen Interprocess Communication Facilities nur die Möglichkeit zur Kommunikation an. 109

116 110 KAPITEL 4. INTER-PROZESS-KOMMUNIKATION (IPC) Netzwerk-Kommunikation Das UNIX-IPC-Konzept lässt sich orthogonal erweitern von der lokalen Kommunikation zwischen Prozessen innerhalb eines Systems auf Netzwerk-Kommunikation zwischen Prozessen, die auf verschiedenen System laufen. Vor allem die Entwicklungsarbeiten der University of California at Berkeley brachte hier einige neue Ansätze zur Interprocess Communication in UNIX ein. User Process UserProcess KERNEL KERNEL Abbildung 4.2: IPC auch über Rechnergrenzen Für die Prozesse kann es völlig transparent sein, wo der jeweilige Partnerprozess abläuft. 4.2 IPC - Client-Server Beispiel Der Client liest einen Dateinamen von stdin ein und schreibt ihn in den IPC-Kanal. Anschliessend wartet er auf die Reaktion des Servers. Der Server liest einen Dateinamen von dem IPC-Kanal und versucht die Datei zu öffnen. Gelingt es dem Server, die Datei zu öffnen, kopiert er ihren Inhalt in den IPC-Kanal. Lässt sich die Datei nicht öffnen, schickt der Server eine Fehlermeldung über den IPC-Kanal. Der Client wartet auf Daten am IPC-Kanal, er liest sie von dort und schreibt sie nach stdout. Konnte der Server die Datei öffnen, zeigt der Client so den Dateiinhalt an, sonst kopiert der Client die Fehlermeldung durch. filename file contenst or error message stdin stdout filename client server file file contents or error message Abbildung 4.3: Client-Server-Beispiel Die beiden gestrichelten Pfeile zwischen dem Server und dem Client entsprechen dem jeweiligen Interprocess Communication Kanal.

117 4.3. SYSTEM CALLS DUP(), DUP2() System Calls dup(), dup2() Im Zusammenhang mit unnamed pipes ist der Systemaufruf dup nützlich: #include <unistd.h> int dup(int oldfd) /*duplicate file descriptor*/ int dup2(int oldfd, int newfd) /*newfd is closed before (if open)*/ /* returns new file descriptor or -1 on error */ dup verdoppelt einen bestehenden Filedeskriptor und liefert als Resultat einen neuen Filedeskriptor (mit der kleinsten verfügbaren Nummer), der mit der gleichen Datei oder der gleichen Pipe verbunden ist. Beide File Deskriptoren haben denselben Positionszeiger. Damit kann z.b. ein Filedeskriptor mit der Nummer 0 erhalten werden, falls dieser vorher geschlossen wurde. Falls so mit exec ein Programm ausgeführt wird, das von Filedeskriptor 0 liest, kann es so dazu gebracht werden, aus einer Pipe zu lesen. Ähnlich kann so der Filedeskriptor 1 manipuliert werden. 4.4 Unnamed Pipes System Call pipe() #include <unistd.h> int pipe( int pipefd[2] ) /* create a pipe */ /* pipefd[2]: file descriptors */ /* returns 0 on success or -1 on error */ Pipes sind der älteste IPC-Mechanismus. Seit Mitte der 70er Jahre existieren sie auf allen Versionen und Arten von UNIX. Eine Pipe besteht aus einem unidirektionalen Datenkanal. Zwei File Deskriptoren repräsentieren die Pipe im User Prozess. Der System Call pipe() kreiert die Pipe, er liefert die beiden Enden als File Deskriptoren über sein Vektorargument an den Prozess. Dabei ist pipefd[1] das Ende zum Schreiben, pipefd[0] das Ende zum Lesen (siehe Abb. 4.4, S. 112) In dieser Konstellation lässt sich die Pipe nur als Zwischenspeicher für Daten außerhalb des User Adressraums benutzen.

118 112 KAPITEL 4. INTER-PROZESS-KOMMUNIKATION (IPC) user process read fd write fd Kernel pipe flow of data Abbildung 4.4: Unnamed Pipe - erster Schritt VORSICHT! Der Kernel synchronisiert Prozesse, die in Pipes schreiben oder aus Pipes lesen Producer / Consumer Modell. Sollte hier in dem Ein-Prozess-Beispiel ein read() oder write System Call blockieren, entsteht ein Deadlock, denn der blockierte Prozess kann den befreienden, komplementären write() oder read() System Call nicht absetzen. Kommunikation zwischen verwandten Prozessen Durch einen fork() System Call entstehen zwei eigenständige, aber verwandte Prozesse, die insbesondere die gleichen I/O-Verbindungen besitzen (siehe Abb. 4.5, S. 112). write fd parent process read fd fork write fd child process read fd Kernel pipe flow of data Abbildung 4.5: Unnamed Pipe zweiter Schritt Schließt nun ein Prozess sein Lese-Ende und der andere Prozess sein Schreib-Ende, so entsteht ein unidirektionaler Kommunikationspfad zwischen den beiden Prozessen (Abb. 4.6, S. 113).

119 4.4. UNNAMED PIPES 113 parent process child process read fd write fd Kernel pipe flow of data Abbildung 4.6: Unnamed Pipe dritter Schritt Wiederholen die Prozesse die fork, pipe() und close System Calls, entstehen längere Pipelines. Dieses Datenverarbeitungs-Prinzip ist untrennbar mit UNIX verbunden. Beispiel: Pipe zwischen zwei Prozessen Das Programm kreiert eine pipe und einen zweiten Prozess, dem diese beiden pipe-deskriptoren verebt werden. Der Erzeugerprozess schreibt Text in die Pipe und wartet auf das Ableben des Kindprozesses. Der Kindprozess liest Text aus der Pipe, schreibt ihn nach stdout und beendet seine Ausführung. Folgende Schritte sind der Reihe nach auszuführen: 1. Parent führt pipe() aus 2. Parent führt fork() aus 3. Child schließt sein Schreib-Ende der Pipe und wartet an seinem Lese-Ende der Pipe. 4. Parent schließt sein Lese-Ende der Pipe, schreibt Text in sein Schreib-Ende der Pipe, und führt wait() für sein Kind aus. 5. Child liest von seinem Lese-Ende, gibt gelesenen Text aus und terminiert. 6. Parent hat auf Child gewartet und kann jetzt auch terminieren.

120 114 KAPITEL 4. INTER-PROZESS-KOMMUNIKATION (IPC) Realisierung: 1 # include < stdio. h> 2 # include <unistd. h> 3 # include < stdlib. h> 4 # include <sys/wait. h> 5 6 # define RD_FD 0 7 # define WR_FD int main() { 10 int childpid, gone, pipefd [2]; if ( pipe ( pipefd ) < 0 ) { 13 perror ( "pipe" ); exit (1); 14 } switch( childpid = fork () ){ 17 case 1: 18 perror ( "fork" ); exit (1); 19 case 0: / child : / { 20 char buf [ 128 ]; int nread ; close ( pipefd [ WR_FD ] ); 23 Programm 4.1: Einfache Kommunikation via Pipe (pipe.c) 24 / read from pipe and copy to stdout / 25 nread = read ( pipefd [ RD_FD ], buf, sizeof ( buf )); 26 write (1, buf, nread ); 27 break; 28 } default : / parent : / close ( pipefd [ RD_FD ] ); 33 write ( pipefd [ WR_FD ], "hello world\n", 12 ); do / wait for child / { 36 if ( (gone = wait ( ( int ) 0 )) < 0 ) { 37 / no interest for exit status / 38 perror ( "wait" ); exit (2); 39 } 40 } while ( gone!= childpid ); 41 } 42 exit (0); 43 }

121 4.5. CLIENT-SERVER MIT UNNAMED PIPES Client-Server mit Unnamed Pipes Bidirektional Durch eine Pipe fließen Daten nur in genau eine Richtung. Zur Realisierung unseres Client-Server Beispiels benötigen wir aber einen bidirektionalen Kommunikationskanal. Wir müssen dazu zwei Pipes kreieren und eine Pipe für jede Richtung konfigurieren. stdout stdin file content filename parent process child process file content (client) filename pipe_1 filename (server) file content pipe_2 Abbildung 4.7: Client-Server mit bidirektionaler Kommunikation Vorgehen: 1. Pipe1 und Pipe2 kreieren 2. fork() ausführen 3. Linker Prozess (Erzeuger) schließt Lese-Ende von Pipe1 und Schreib-Ende von Pipe2 4. Rechter Prozess (Kind) schließt Schreib-Ende von Pipe1 und Lese-Ende von Pipe2 Die Realisierung zeigen die Programme 4.2 (S. 116), 4.4 (S. 117), 4.6 (S. 118),

122 116 KAPITEL 4. INTER-PROZESS-KOMMUNIKATION (IPC) Programm 4.2: Hauptprogramm zum Client/Server-Beispiel (cli-srv/main.c) 1 # include < stdio. h> 2 # include < stdlib. h> 3 # include <unistd. h> 4 # include <sys/wait. h> 5 6 # include " client. h" 7 # include " server. h" 8 9 # define RD_FD 0 10 # define WR_FD int main() { 13 int childpid, gone, pipe_1 [2], pipe_2 [2]; if ( pipe ( pipe_1 ) < 0 pipe ( pipe_2 ) < 0 ) { 16 perror ( "pipe (): can t creat pipes" ); exit (1); 17 } 18 if ( ( childpid = fork ()) < 0 ) { 19 perror ("fork" ); exit (1); 20 } if ( childpid > 0 ) { // client / parent 23 printf ( " Client/Parent: my pid is %d\n", 24 ( int ) getpid () ); close ( pipe_1 [ RD_FD ] ); close ( pipe_2 [ WR_FD ] ); client ( pipe_2 [ RD_FD ], pipe_1 [ WR_FD ] ); / wait for child / 31 do { 32 if ( (gone = wait ( ( int ) 0 )) < 0 ) { 33 perror ( "wait" ); exit (2); 34 } 35 } while ( gone!= childpid ); printf ( " Cli/Par: server/child %d terminated\n", gone ); 38 printf ( " Cli/Par: going to exit\n" ); } else { // server / child 41 printf ( "Server/Child: after fork, my pid is %d\n", 42 ( int ) getpid () ); close ( pipe_1 [ WR_FD ] ); close ( pipe_2 [ RD_FD ] ); server ( pipe_1 [ RD_FD ], pipe_2 [ WR_FD ] ); printf ( "Server/Child: going to exit\n" ); 49 } 50 exit ( 0 ); 51 }

123 4.5. CLIENT-SERVER MIT UNNAMED PIPES 117 Programm 4.3: Client (cli-srv/client.h) 1 / 2 read line ( filename ( from stdin, write it to IPC channel, 3 copy text ( file content ) from IPC channel to stdout 4 / 5 6 # ifndef CLIENT_H 7 # define CLIENT_H 8 extern void client ( int readfd, int writefd ); 9 # endif 1 # define CLIENT_H 2 # include " client. h" 3 4 # include < stdio. h> 5 # include <unistd. h> 6 # include < stdlib. h> 7 # include < string. h> 8 9 # define BUFSIZE void client ( int readfd, int writefd ) { 12 char buf [ BUFSIZ ]; 13 int n; 14 Programm 4.4: Client (cli-srv/client.c) 15 / read filename from stdin, write it to IPC channel / 16 printf ("give filename: " ); / not safe!!! / if ( fgets ( buf, BUFSIZ, stdin ) == (char ) 0 ) { 19 fprintf ( stderr, " Client : filename read error" ); 20 exit (1); 21 } n = strlen ( buf ); 24 if ( buf [ n 1 ] == \n ) n ; / zap NL / if ( write ( writefd, buf, n )!= n ) { 27 perror ( "write" ); exit (2); 28 } / read data from IPC channel, write it to stdout / 31 while ( (n = read ( readfd, buf, BUFSIZ )) > 0 ) 32 if ( write ( 1, buf, n )!= n ) { 33 perror ( "write" ); exit (3); 34 } if ( n < 0 ) { 37 fprintf ( stderr, "read (): Client : can t read from IPC channel" ); 38 exit (4); 39 } 40 }

124 118 KAPITEL 4. INTER-PROZESS-KOMMUNIKATION (IPC) Programm 4.5: Server (cli-srv/server.h) 1 / 2 read filename from IPC channel, open this file, 3 copy data from file to IPC channel. 4 / 5 # ifndef SERVER_H 6 # define SERVER_H 7 extern void server ( int readfd, int writefd ); 8 # endif 1 # include < stdio.h> 2 # include <unistd. h> 3 # include < stdlib.h> 4 # include < string.h> 5 # include < fcntl.h> 6 7 # define BUFSIZE void server ( int readfd, int writefd ) { 10 char buf [ BUFSIZ ]; 11 int n, fd ; 12 Programm 4.6: Server (cli-srv/server.c) 13 / read filename from IPC channel / 14 if ( (n = read ( readfd, buf, BUFSIZ )) < 0 ) { 15 perror ( "read" ); exit (1); 16 } 17 buf [ n ] = \0 ; / try to open file / if ( ( fd = open( buf, O_RDONLY )) < 0 ) { 22 / Format and send error mesg to client / 23 char errmesg [ BUFSIZ ]; (void) sprintf ( errmesg, "Server: can t open infile (%. s)\n", 26 BUFSIZ/2, buf ); 27 n = strlen ( errmesg ); 28 if ( write ( writefd, errmesg, n )!= n ) { 29 perror ("write" ); exit (2); 30 } 31 return ; 32 } while ( (n = read ( fd, buf, BUFSIZ )) > 0 ) 35 if ( write ( writefd, buf, n )!= n ) { 36 perror ( "write" ); exit (3); 37 } 38 if ( n < 0 ) { 39 fprintf ( stderr, "Server: can t read file " ); 40 exit (3); 41 } 42 }

125 4.5. CLIENT-SERVER MIT UNNAMED PIPES 119

126 120 KAPITEL 4. INTER-PROZESS-KOMMUNIKATION (IPC) 4.6 Standard I/O Bibliotheksfunktion popen() und pclose() #include <stdio.h> FILE *popen( char * cmd, char * mode ) /* create a pipe to a cmd: cmd: cmd to be executed mode: read from or write to pipe/cmd returns file pointer on success or NULL on error */ int pclose( FILE * fp ) /* close pipe with cmd */ /* returns exit status of cmd or -1 on error */ Beschreibung Die Funktion popen() aus der Standard I/O Bibliothek kreiert eine unidirektionale (!) Pipe und einen neuen Prozess, der von der Pipe liest oder in die Pipe schreibt. In dem neuen Prozess startet eine Shell und führt die mitgegebene Kommandozeile cmd (unter Berücksichtigung von PATH) aus. Die Pipe wird abhängig vom Argument mode (entweder r oder w!) so konfiguriert, dass sie stdout oder stdin des erzeugten Kommandos mit dem aufrufenden Prozess verbindet. Der aufrufende Prozess erhält sein Pipe-Ende als File Pointer von popen. pclose() schließt eine mit popen() geöffnete I/O-Verbindung ab (NICHT fclose()). Die Funktion blockiert bis das Kommando terminiert und liefert den Exit-Status des Kommandos zurück.

127 4.6. STANDARD I/O BIBLIOTHEKSFUNKTION 121 Beispiel: 1 # include < stdio.h> 2 # include < stdlib.h> 3 4 # define BUFFER_SIZE int main() { 7 8 char buf [ BUFFER_SIZE ]; 9 FILE fp ; 10 Programm 4.7: Beispiel mit popen(), pclose() (popen/popen.c) 11 if ( ( fp = popen( "/bin/pwd", "r" )) == (FILE ) 0 ) { 12 perror ( "popen()" ); 13 exit (1); 14 } if ( fgets ( buf, BUFFER_SIZE, fp ) == (char ) 0 ) { 17 fprintf ( stderr, " fgets error" ); 18 exit (2); 19 } printf ( "The current working directory is:\n" ); 22 printf ( "\t%s", buf ); / pwd inserts newline / pclose ( fp ); 25 exit (0); 26 }

128 122 KAPITEL 4. INTER-PROZESS-KOMMUNIKATION (IPC) 4.7 Über Standarddeskriptoren in Pipe schreiben / lesen 1 # include < stdio.h> 2 # include <unistd. h> 3 # include < stdlib.h> 4 int main() { int pid ; int pfd [2]; 5 pipe ( pfd ); 6 pid = fork (); 7 8 if ( pid < 0) { perror ( "fork" ); exit (1);} 9 10 if ( pid == 0) { / CHILD / 11 char buf [128]; int n; close (0); dup( pfd [0]); 14 Programm 4.8: 0/1 auf Pipe (pipe-2.c) 15 if ( (n = read (0, buf, 15)) < 0 ) { 16 perror ( "read" ); exit (1); 17 } else { 18 buf [n] = \0 ; 19 printf ( " child : read > %s <\n", buf); 20 exit (0); 21 } 22 } else { / PARENT / close (1); dup( pfd [1]); if ( write (1, "I m your parent", 15) < 0) { 27 perror ( "write" ); exit (2); 28 } 29 exit (0); 30 } 31 }

129 4.8. TERMINATION VON PIPELINE-VERBINDUNGEN Termination von Pipeline-Verbindungen 1 # include < stdio.h> 2 # include <unistd. h> 3 # include < stdlib.h> 4 5 int main() { 6 int pid ; int pfd [2]; 7 8 pipe ( pfd ); 9 pid = fork (); if ( pid < 0) { perror ( "fork" ); exit (1); } if ( pid == 0) { / CHILD / 14 char buf [128]; int n; close (0); dup( pfd [0]); 17 Programm 4.9: Termination? (pipe-3.c) 18 while( (n = read (0, buf,7)) > 0) { 19 buf [n] = \0 ; 20 printf ( " child : read > %s <\n", buf); 21 } 22 exit (0); 23 } else { / PARENT / 24 close (1); 25 dup(pfd [1]); 26 if ( write (1, "I m your parent", 15) < 0) { 27 perror ( "write" ); exit (2); 28 } 29 exit (0); 30 } 31 }

130 124 KAPITEL 4. INTER-PROZESS-KOMMUNIKATION (IPC) Übersetzung und Ausführung: spatz$ gcc -Wall pipe-3.c spatz$ a.out spatz$ child: read > I m you < child: read > r paren < child: read > t < spatz$ ps PID TTY TIME CMD 5692 pts/3 00:00:00 bash 5705 pts/3 00:00:00 a.out 5706 pts/3 00:00:00 ps spatz$ kill 5705 spatz$ ps PID TTY TIME CMD 5692 pts/3 00:00:00 bash 5707 pts/3 00:00:00 ps spatz$ So terminiert alles: 1 // Termination!!! 2 # include < stdio.h> 3 # include <unistd. h> 4 # include < stdlib.h> 5 Programm 4.10: Termination! (pipe-4.c) 6 int main() { 7 int pid ; int pfd [2]; 8 pipe ( pfd ); pid = fork (); 9 if ( pid < 0) { perror ( "fork" ); exit (1); } if ( pid == 0) { / CHILD / 12 char buf [128]; int n; 13 close (0); dup( pfd [0]); close ( pfd [1]); / < / while( (n = read (0, buf,7)) > 0) { 18 buf [n] = \0 ; 19 printf ( " child : read > %s <\n", buf); 20 } 21 exit (0); 22 } else { / PARENT / 23 close (1); dup( pfd [1]); 24 if ( write (1, "I m your parent", 15) < 0) { 25 perror ( "write" ); exit (2); 26 } 27 exit (0); 28 } 29 }

131 4.9. WIE DIE SHELL EINE PIPELINE MACHT Wie die Shell eine Pipeline macht 1 / ls l wc l / 2 # include < stdio.h> 3 # include <unistd. h> 4 # include < stdlib.h> 5 6 int main() { 7 int pid1, pid2 ; int pfd [2]; 8 Programm 4.11: Pipelining der Shell (pipe-5.c) 9 pipe ( pfd ); pid1 = fork (); 12 if ( pid1 < 0) { perror ( "fork" ); exit (1); } 13 if ( pid1 == 0) { / CHILD 1: wc l / close (0); dup( pfd [0]); 16 close ( pfd [1]); / without this line??? / 17 execlp ( "wc", "wc", " l", NULL); 18 perror ( "exec" ); exit (2); } else { / PARENT / pid2 = fork (); 23 if ( pid2 < 0) { perror ("fork" ); exit (3); } 24 if ( pid2 == 0) { / CHILD 2: ls l / close (1); dup( pfd [1]); 27 execlp ( " ls ", " ls ", " l", NULL); 28 perror ( "exec" ); exit (4); 29 } else { / PARENT / 30 close ( pfd [1]); / Why? / 31 close ( pfd [0]); / nice! / 32 / terminate, don t wait / 33 exit (0); 34 } 35 } 36 }

132 126 KAPITEL 4. INTER-PROZESS-KOMMUNIKATION (IPC) 4.10 SIGPIPE Das Kommando xlsfonts produziert sehr große Ausgaben, mehr als auf einmal in eine Pipeline passt diese werden in eine Pipeline gelenkt über das Kommando head lesen wir ein bisschen heraus und terminieren dann was passiert mit xlsfonts??? Programm 4.12: Schreiben in eine Pipe ohne Leseende (pipe-6.c) 1 # include < stdio.h> 2 # include <unistd. h> 3 # include < stdlib.h> 4 # include <sys/wait. h> 5 int main() { 6 int pid1, pid2 ; int pfd [2]; 7 pipe ( pfd ); 8 pid1 = fork (); 9 if ( pid1 < 0) { perror ( "fork" ); exit (1); } 10 if ( pid1 == 0) { / CHILD 1 fuer head 2 / 11 close (0); dup( pfd [0]); 12 close ( pfd [1]); / without this line? / 13 execlp ( "head", "head", " 2", NULL); 14 perror ( "exec" ); exit (2); 15 } else { / PARENT / 16 pid2 = fork (); 17 if ( pid2 < 0) { perror ("fork" ); exit (3); } 18 if ( pid2 == 0) { / CHILD 2 fuer xlsfonts / 19 close (1); dup( pfd [1]); 20 close ( pfd [0]); / without this line??? / 21 execlp ( " xlsfonts ", " xlsfonts ", NULL); 22 perror ( "exec" ); exit (4); 23 } else { / PARENT / 24 int status ; 25 close ( pfd [0]); close ( pfd [1]); / without? / 26 / wait for second child / 27 waitpid ( pid2, &status, 0); 28 / terminated because of Signal? / 29 if ( status & 0x7F) { 30 printf ( "terminated by signal %d\n", status & 0x7F); 31 } 32 exit (0); 33 } 34 } 35 } Übersetzung und Ausführung: spatz$ gcc -Wall pipe-6.c spatz$ a.out -adobe-courier-bold-o-normal m-60-iso adobe-courier-bold-o-normal m-60-iso terminated by signal 13 spatz$

133 Kapitel 5 Netzwerk-Kommunikation 5.1 Übersicht Netzwerk räumlich verteiltes System von Rechnern, Steuereinheiten und Peripheriegeräten, verbunden mit Datenübertragungseinrichtungen aktive / passive Komponenten Unterscheidung nach Ausbreitung globales Netz (GAN global area network) Internet, EUNet, VNET (IBM), u.a. Weitverkehrsnetz (WAN wide area network) DATEV-Netz, DFN (Deutsches Forschungsnetz) lokale Netze (LAN local area network, WLAN wireless LAN) innerhalb eines Unternehmens; i.a. mehrere, die selbst wieder vernetzt sind Front-end-Lan (Netz innerhalb einer Abteilung / Instituts) Backbone-LAN (Verbindung von Front-end-LAN s Netzwerktopologie physikalische / logische Verbindung der Rechner im Netz (Stern, Ring, Bus) Protokolle Regeln (Vereinbarungen), nach denen Kommunikationspartner (Rechner) eine Verbindung aufbauen, die Kommunikation durchführen und die Verbindung wieder abbauen 127

134 128 KAPITEL 5. NETZWERK-KOMMUNIKATION Grundlegendes herstellerunabhängiges Konzept: DIN/ISO-OSI-Referenzmodell OSI - Open Systems Interconnection Sender D a t e n s t r o m Empfänger 7 Application H Paket i Anwendungs schicht 6 Presentation H Darstellungs schicht 5 Session H Sitzungs schicht 4 Transport H Transport schicht 3 Network H Vermittlungs schicht 2 Data Link H T Sicherungs schicht 1 Physical Bitfolge Bitübertragungs schicht Medium Medium Abbildung 5.1: ISO-OSI-Referenzmodell 1. Bitübertragungsschicht Regelung aller physikalisch-technischer Eigenschaften der Übertragungsmedien zwischen den verschiedenen End-/Transitsystemen Darstellung von Bits via Spannungen, Stecker, Sicherungsschicht Sicherung der Schicht 1 gegen auf den Übertragungsstrecken auftretenden Übertragungsfehler (elektromagnetische Einflüsse) z.b. Prüfziffern, parity bits 3. Vermittlungsschicht Adressierung der Zielssysteme über das (die) Transitsystem(e) hinweg sowie Wegsteuerung der Nachrichten durch das Netz Flusskontrolle zwischen End- und Transitsystemen (Überlastung von Übertragungswegen und Rechnern / Transitsystemen, faire Verteilung der Bandbreite)

135 5.1. ÜBERSICHT Transportschicht Stellt die mithilfe der Schichten hergestellten Endsystemverbindungen für die Anwender zur Verfügung z.b. Abbildung logischer Rechnernamen auf Netzadressen 5. Kommunikationssteuerungsschicht Bereitsstellung von Sprachmitteln zur Steuerung der Kommunikationsbeziehung (session) Aufbau, Wiederaufnahme nach Unterbrechung, Abbau 6. Datendarstellungsschicht Vereinbarungen bzgl. Datenstrukturen für Datentransfer 7. Anwendungsschicht Berücksichtigung inhaltsbezogener Aspekte (Semantik) Quasistandard TCP/IP Transmission Control Protocol / Internet Protocol Process / Application File Transfer: FTP simple mail transf.prot. terminal emulation network manag. simple network manag. telnet (SMTP) protocol prot Host to host TCP User Datagram Prot.UDP address internet prot. internet control Internet layer resolution message prot Ethernet, IEEE 802, Arcnet, X network access or local network lay twisted pair, Koaxial,Glasfaser OSI TCP/IP Protokoll Implementierungen Abbildung 5.2: TCP/IP

136 130 KAPITEL 5. NETZWERK-KOMMUNIKATION Kopplung von Netzen Kopplungseinheiten zur Verbindung von Netzen (internetworking units) Adressumwandlung, Wegewahl (routing), Flusskontrolle, Fragmentierung und Wiederzusammenfügung von Datenpaketen, Zugangskontrolle, Netzwerkmanagement Repeater Verstärker; Empfangen, Verstärken, Weitersenden der Signale auf Bitübertragungsschicht; die zu verbindenden Netze müssen identisch sein; Verbindung von Netzsegmenten Bridge Verbindung von Netzen mit unterschiedlichen ÜBertragungsmedien, aber mit gleichem Schichtaufbau; operiert auf Sicherungsschicht Router operiert auf Vermittlungsschicht Gateway Verknüpfung von Netzen, die in Schicht 3 (und aufwärts) unterschiedliche Struktur aufweisen Hub Eine Art Multiplexer, der das Eingangssignal sternförmig an die angeschlossenen Geräte weiterleitet (keine eigene Adresse); reduziert den Verkabelungsaufwand Switch spezieller Hub der Unterschied ist die Arbeitsweise: ein Hub empfängt ein Paket und sendet es einfach an alle Ports weiter, ein Switch hingegen schickt ein Paket nur an den Port weiter der das Paket auch benötigt Vorteil eines Switches: wesentlich höhere Geschwindigkeit, die durch das intelligente Routing erreicht wird Dabei übernehmen weiter unten stehende Geräte teilweise die Funktionalität darüberstehender Geräte!

137 5.1. ÜBERSICHT 131 Übertragungsmedien (siehe z.b. de.wikipedia.org/wiki/) Twisted-Pair-Kabel: Kabeltypen, bei denen die beiden Adern eines Adernpaares miteinander verdrillt (auch verseilt oder verdreht) sind; durch die Verdrillung jeweils einer Datenleitung mit einer Masseleitung ist die Datenübertragung weniger störanfällig; Sie bestehen grundsätzlich aus Ader: Kunststoffisolierter Kupferleiter Paar: Je zwei Adern sind zu einem Paar (englisch pair) verdrillt Seele: Bezeichnet die vier miteinander verseilten Paare Kabelmantel: Umfasst die Seele. Besteht aus PVC oder halogenfreiem Material Zusätzlich zu den Aderpaaren können weitere Elemente im Kabel vorhanden sein: Beidrähte als elektrische Masseleitung, Fülladern aus Kunststoff zum Ausfüllen von Hohlräumen zwischen den Paaren oder Trennelemente aus Kunststoff um die Paare auseinander zu halten. Twisted-Pair-Kabel gibt es in zwei- und vierpaariger Ausführung. Bei aktuellen Netzwerkinstallation werden fast nur vierpaarige Kabel verwendet. Man unterscheidet UTP (Unshielded Twisted Pair): Kabel mit ungeschirmten Paaren und ohne Gesamtschirm (siehe Abb. 5.3, S. 131) STP (Shielded Twisted Pair): Die Adernpaare sind mit einem metallischem Schirm (meist eine Alu-kaschierte Kunststofffolie) umgeben (siehe Abb. 5.4, S. 132) S/UTP (Screened Unshielded Twisted Pair): Aufbau wie bei UTP, jedoch mit zusätzlicher Gesamtschirmung um die Seele S/STP (Screened Shielded Twisted Pair): Aufbau wie bei STP, jedoch mit zusättzlicher Gesamtschirmung um die Seele (Abb. 5.5, S. 132) UTP Kupferleiter Aderisolierung Paar Kabelmantel Abbildung 5.3: Unshielded-Twisted-Pair-Kabel Twisted-Pair-Kabel sind billig, einfach zu verlegen; geringe Bandbreite, geringe Abhörsicherheit, hohe Störanfälligkeit

138 132 KAPITEL 5. NETZWERK-KOMMUNIKATION STP Kupferleiter Adernisolierung Paar Paarschirm Kabelmantel Abbildung 5.4: Shielded-Twisted-Pair-Kabel S/STP Kupferkabel Adernisolierung Paar Paarschirm Gesamtschirm Kabelmantel Abbildung 5.5: Screened Shielded-Twisted-Pair-Kabel Koaxialkabel, kurz: Koax-Kabel bestehen aus einem isolierten Innenleiter (auch Seele genannt), der von einem in konstantem Abstand um den Innenleiter angebrachten Außenleiter umgeben ist. Üblicherweise ist diese Ummantelung ebenfalls nach außen isoliert (Abb. 5.6, S. 132 Ausnutzung eines breiten Frequenzspektrums für parallele Übertragung Abbildung 5.6: Koaxial-Kabel

PROGRAMMIEREN MIT UNIX/LINUX-SYSTEMAUFRUFEN

PROGRAMMIEREN MIT UNIX/LINUX-SYSTEMAUFRUFEN PROGRAMMIEREN MIT UNIX/LINUX-SYSTEMAUFRUFEN 2. UNIX/Linux-Prozessverwaltung und zugehörige Systemaufrufe Wintersemester 2015/16 2. Die UNIX/LINUX-Prozessverwaltung Aufgaben: 1. Erzeugen neuer Prozesse

Mehr

Dämon-Prozesse ( deamon )

Dämon-Prozesse ( deamon ) Prozesse unter UNIX - Prozessarten Interaktive Prozesse Shell-Prozesse arbeiten mit stdin ( Tastatur ) und stdout ( Bildschirm ) Dämon-Prozesse ( deamon ) arbeiten im Hintergrund ohne stdin und stdout

Mehr

Betriebssysteme. Dipl.-Ing.(FH) Volker Schepper

Betriebssysteme. Dipl.-Ing.(FH) Volker Schepper 1. Der Prozess beginnt im Zustand Erzeugt, nachdem sein Vaterprozess den Systemaufruf fork() (s.u.) abgesetzt hat. In diesem Zustand wird der Prozess-Kontext initialisiert. 2. Ist diese Aufbauphase abgeschlossen,

Mehr

Prozesse und Logs Linux-Kurs der Unix-AG

Prozesse und Logs Linux-Kurs der Unix-AG Prozesse und Logs Linux-Kurs der Unix-AG Benjamin Eberle 22. Januar 2015 Prozesse unter Linux gestartete Programme laufen unter Linux als Prozesse jeder Prozess hat eine eindeutige Prozess-ID (PID) jeder

Mehr

Systemprogrammierung I - Aufgaben zur Erlangung der Klausurzulassung für Informatiker und Wirtschaftsinformatiker

Systemprogrammierung I - Aufgaben zur Erlangung der Klausurzulassung für Informatiker und Wirtschaftsinformatiker Systemprogrammierung I - Aufgaben zur Erlangung der Klausurzulassung für Informatiker und Nachfolgend finden Sie die drei Aufgaben, die Sie als Zulassungsvoraussetzung für die Scheinklausur am 18.7.2001

Mehr

2A Basistechniken: Weitere Aufgaben

2A Basistechniken: Weitere Aufgaben 2A Basistechniken: Weitere Aufgaben 2A.3 Programmierung unter UNIX/Linux 1. Gegeben sind einige Ausschnitte von C-Programmen, die unter UNIX/Linux ausgeführt werden sollen. Beantworten Sie die zugehörigen

Mehr

Architektur Verteilter Systeme Teil 2: Prozesse und Threads

Architektur Verteilter Systeme Teil 2: Prozesse und Threads Architektur Verteilter Systeme Teil 2: Prozesse und Threads 21.10.15 1 Übersicht Prozess Thread Scheduler Time Sharing 2 Begriff Prozess und Thread I Prozess = Sequentiell ablaufendes Programm Thread =

Mehr

Prozesse. Stefan Janssen. sjanssen@cebitec.uni-bielefeld.de. Alexander Sczyrba asczyrba@cebitec.uni-bielefeld.de

Prozesse. Stefan Janssen. sjanssen@cebitec.uni-bielefeld.de. Alexander Sczyrba asczyrba@cebitec.uni-bielefeld.de Netzwerk - Programmierung Prozesse Stefan Janssen sjanssen@cebitec.uni-bielefeld.de Alexander Sczyrba asczyrba@cebitec.uni-bielefeld.de Madis Rumming mrumming@cebitec.uni-bielefeld.de Übersicht Prozesse

Mehr

Systeme 1. Kapitel 6. Nebenläufigkeit und wechselseitiger Ausschluss

Systeme 1. Kapitel 6. Nebenläufigkeit und wechselseitiger Ausschluss Systeme 1 Kapitel 6 Nebenläufigkeit und wechselseitiger Ausschluss Threads Die Adressräume verschiedener Prozesse sind getrennt und geschützt gegen den Zugriff anderer Prozesse. Threads sind leichtgewichtige

Mehr

Linux Prinzipien und Programmierung

Linux Prinzipien und Programmierung Linux Prinzipien und Programmierung Dr. Klaus Höppner Hochschule Darmstadt Sommersemester 2014 1 / 25 2 / 25 Pipes Die Bash kennt drei Standard-Dateideskriptoren: Standard In (stdin) Standard-Eingabe,

Mehr

Prozesse und Logs Linux-Kurs der Unix-AG

Prozesse und Logs Linux-Kurs der Unix-AG Prozesse und Logs Linux-Kurs der Unix-AG Andreas Teuchert 27./28. Juni 2012 Prozesse unter Linux gestartete Programme laufen unter Linux als Prozesse jeder Prozess hat eine eindeutige Prozess-ID (PID)

Mehr

Monitore. Klicken bearbeiten

Monitore. Klicken bearbeiten Sascha Kretzschmann Institut für Informatik Monitore Formatvorlage und deren Umsetzung des Untertitelmasters durch Klicken bearbeiten Inhalt 1. Monitore und Concurrent Pascal 1.1 Warum Monitore? 1.2 Monitordefinition

Mehr

Systemnahe Programmierung in C Übungen Jürgen Kleinöder, Michael Stilkerich Universität Erlangen-Nürnberg Informatik 4, 2011 U7.fm

Systemnahe Programmierung in C Übungen Jürgen Kleinöder, Michael Stilkerich Universität Erlangen-Nürnberg Informatik 4, 2011 U7.fm U7 POSIX-Prozesse U7 POSIX-Prozesse Prozesse POSIX-Prozess-Systemfunktionen Aufgabe 7 U7.1 U7-1 Prozesse: Überblick U7-1 Prozesse: Überblick Prozesse sind eine Ausführumgebung für Programme haben eine

Mehr

Zählen von Objekten einer bestimmten Klasse

Zählen von Objekten einer bestimmten Klasse Zählen von Objekten einer bestimmten Klasse Ziel, Inhalt Zur Übung versuchen wir eine Klasse zu schreiben, mit der es möglich ist Objekte einer bestimmten Klasse zu zählen. Wir werden den ++ und den --

Mehr

U7 POSIX-Prozesse U7 POSIX-Prozesse

U7 POSIX-Prozesse U7 POSIX-Prozesse U7 POSIX-Prozesse U7 POSIX-Prozesse Prozesse POSIX-Prozess-Systemfunktionen Aufgabe 6 U7.1 U7-1 Prozesse: Überblick U7-1 Prozesse: Überblick Prozesse sind eine Ausführumgebung für Programme haben eine

Mehr

Handbuch Fischertechnik-Einzelteiltabelle V3.7.3

Handbuch Fischertechnik-Einzelteiltabelle V3.7.3 Handbuch Fischertechnik-Einzelteiltabelle V3.7.3 von Markus Mack Stand: Samstag, 17. April 2004 Inhaltsverzeichnis 1. Systemvorraussetzungen...3 2. Installation und Start...3 3. Anpassen der Tabelle...3

Mehr

CA Übung 30.01.2006. Christian kann heute nicht kommen => ich bin heute da, Christian das nächste Mal wieder

CA Übung 30.01.2006. Christian kann heute nicht kommen => ich bin heute da, Christian das nächste Mal wieder CA Übung 30.01.2006 Hallo zusammen! Christian kann heute nicht kommen => ich bin heute da, Christian das nächste Mal wieder Adrian Schüpbach: scadrian@student.ethz.ch Christian Fischlin: cfischli@student.ethz.ch

Mehr

Stundenerfassung Version 1.8 Anleitung Arbeiten mit Replikaten

Stundenerfassung Version 1.8 Anleitung Arbeiten mit Replikaten Stundenerfassung Version 1.8 Anleitung Arbeiten mit Replikaten 2008 netcadservice GmbH netcadservice GmbH Augustinerstraße 3 D-83395 Freilassing Dieses Programm ist urheberrechtlich geschützt. Eine Weitergabe

Mehr

CMS.R. Bedienungsanleitung. Modul Cron. Copyright 10.09.2009. www.sruttloff.de CMS.R. - 1 - Revision 1

CMS.R. Bedienungsanleitung. Modul Cron. Copyright 10.09.2009. www.sruttloff.de CMS.R. - 1 - Revision 1 CMS.R. Bedienungsanleitung Modul Cron Revision 1 Copyright 10.09.2009 www.sruttloff.de CMS.R. - 1 - WOZU CRON...3 VERWENDUNG...3 EINSTELLUNGEN...5 TASK ERSTELLEN / BEARBEITEN...6 RECHTE...7 EREIGNISSE...7

Mehr

Softwarelösungen: Versuch 4

Softwarelösungen: Versuch 4 Softwarelösungen: Versuch 4 Nichtstun in Schleife wird ersetzt durch zeitweilige Zurücknahme der Anforderung, um es anderen Prozessen zu erlauben, die Ressource zu belegen: /* Prozess 0 */ wiederhole flag[0]

Mehr

1 Vom Problem zum Programm

1 Vom Problem zum Programm Hintergrundinformationen zur Vorlesung GRUNDLAGEN DER INFORMATIK I Studiengang Elektrotechnik WS 02/03 AG Betriebssysteme FB3 Kirsten Berkenkötter 1 Vom Problem zum Programm Aufgabenstellung analysieren

Mehr

Primzahlen und RSA-Verschlüsselung

Primzahlen und RSA-Verschlüsselung Primzahlen und RSA-Verschlüsselung Michael Fütterer und Jonathan Zachhuber 1 Einiges zu Primzahlen Ein paar Definitionen: Wir bezeichnen mit Z die Menge der positiven und negativen ganzen Zahlen, also

Mehr

10.6 Programmier-Exits für Workitems

10.6 Programmier-Exits für Workitems 10.6 Programmier-Exits für Workitems 279 10.6 Programmier-Exits für Workitems 10.6.1 Das Interface IF_SWF_IFS_WORKITEM_EXIT Am Schritt einer Workflow-Definition im Reiter»Programmier-Exits«können verschiedene

Mehr

Client-Server mit Socket und API von Berkeley

Client-Server mit Socket und API von Berkeley Client-Server mit Socket und API von Berkeley L A TEX Projektbereich Deutsche Sprache Klasse 3F Schuljahr 2015/2016 Copyleft 3F Inhaltsverzeichnis 1 NETZWERKPROTOKOLLE 3 1.1 TCP/IP..................................................

Mehr

Dokumentation IBIS Monitor

Dokumentation IBIS Monitor Dokumentation IBIS Monitor Seite 1 von 16 11.01.06 Inhaltsverzeichnis 1. Allgemein 2. Installation und Programm starten 3. Programmkonfiguration 4. Aufzeichnung 4.1 Aufzeichnung mitschneiden 4.1.1 Inhalt

Mehr

I Serverkalender in Thunderbird einrichten

I Serverkalender in Thunderbird einrichten I Serverkalender in Thunderbird einrichten Damit Sie den Kalender auf dem SC-IT-Server nutzen können, schreiben Sie bitte zuerst eine Mail mit Ihrer Absicht an das SC-IT (hilfe@servicecenter-khs.de). Dann

Mehr

Speicher in der Cloud

Speicher in der Cloud Speicher in der Cloud Kostenbremse, Sicherheitsrisiko oder Basis für die unternehmensweite Kollaboration? von Cornelius Höchel-Winter 2013 ComConsult Research GmbH, Aachen 3 SYNCHRONISATION TEUFELSZEUG

Mehr

Tutorial - www.root13.de

Tutorial - www.root13.de Tutorial - www.root13.de Netzwerk unter Linux einrichten (SuSE 7.0 oder höher) Inhaltsverzeichnis: - Netzwerk einrichten - Apache einrichten - einfaches FTP einrichten - GRUB einrichten Seite 1 Netzwerk

Mehr

Datensicherung. Beschreibung der Datensicherung

Datensicherung. Beschreibung der Datensicherung Datensicherung Mit dem Datensicherungsprogramm können Sie Ihre persönlichen Daten problemlos Sichern. Es ist möglich eine komplette Datensicherung durchzuführen, aber auch nur die neuen und geänderten

Mehr

Auf der linken Seite wählen Sie nun den Punkt Personen bearbeiten.

Auf der linken Seite wählen Sie nun den Punkt Personen bearbeiten. Personenverzeichnis Ab dem Wintersemester 2009/2010 wird das Personenverzeichnis für jeden Mitarbeiter / jede Mitarbeiterin mit einer Kennung zur Nutzung zentraler Dienste über das LSF-Portal druckbar

Mehr

Binäre Bäume. 1. Allgemeines. 2. Funktionsweise. 2.1 Eintragen

Binäre Bäume. 1. Allgemeines. 2. Funktionsweise. 2.1 Eintragen Binäre Bäume 1. Allgemeines Binäre Bäume werden grundsätzlich verwendet, um Zahlen der Größe nach, oder Wörter dem Alphabet nach zu sortieren. Dem einfacheren Verständnis zu Liebe werde ich mich hier besonders

Mehr

Produktschulung WinDachJournal

Produktschulung WinDachJournal Produktschulung WinDachJournal Codex GmbH Stand 2009 Inhaltsverzeichnis Einleitung... 3 Starten des Programms... 4 Erfassen von Notizen in WinJournal... 6 Einfügen von vorgefertigten Objekten in WinJournal...

Mehr

4. BEZIEHUNGEN ZWISCHEN TABELLEN

4. BEZIEHUNGEN ZWISCHEN TABELLEN 4. BEZIEHUNGEN ZWISCHEN TABELLEN Zwischen Tabellen können in MS Access Beziehungen bestehen. Durch das Verwenden von Tabellen, die zueinander in Beziehung stehen, können Sie Folgendes erreichen: Die Größe

Mehr

Matrix42. Use Case - Sicherung und Rücksicherung persönlicher Einstellungen über Personal Backup. Version 1.0.0. 23. September 2015 - 1 -

Matrix42. Use Case - Sicherung und Rücksicherung persönlicher Einstellungen über Personal Backup. Version 1.0.0. 23. September 2015 - 1 - Matrix42 Use Case - Sicherung und Rücksicherung persönlicher Version 1.0.0 23. September 2015-1 - Inhaltsverzeichnis 1 Einleitung 3 1.1 Beschreibung 3 1.2 Vorbereitung 3 1.3 Ziel 3 2 Use Case 4-2 - 1 Einleitung

Mehr

Die Statistiken von SiMedia

Die Statistiken von SiMedia Die Statistiken von SiMedia Unsere Statistiken sind unter folgender Adresse erreichbar: http://stats.simedia.info Kategorie Titel Einfach Erweitert Übersicht Datum und Zeit Inhalt Besucher-Demographie

Mehr

Enigmail Konfiguration

Enigmail Konfiguration Enigmail Konfiguration 11.06.2006 Steffen.Teubner@Arcor.de Enigmail ist in der Grundkonfiguration so eingestellt, dass alles funktioniert ohne weitere Einstellungen vornehmen zu müssen. Für alle, die es

Mehr

Nicht kopieren. Der neue Report von: Stefan Ploberger. 1. Ausgabe 2003

Nicht kopieren. Der neue Report von: Stefan Ploberger. 1. Ausgabe 2003 Nicht kopieren Der neue Report von: Stefan Ploberger 1. Ausgabe 2003 Herausgeber: Verlag Ploberger & Partner 2003 by: Stefan Ploberger Verlag Ploberger & Partner, Postfach 11 46, D-82065 Baierbrunn Tel.

Mehr

Thermoguard. Thermoguard CIM Custom Integration Module Version 2.70

Thermoguard. Thermoguard CIM Custom Integration Module Version 2.70 Thermoguard Thermoguard CIM Custom Integration Module Version 2.70 Inhalt - Einleitung... 3 - Voraussetzungen... 3 - Aktivierung und Funktion der Schnittstelle... 3 - Parameter... 4 - NLS-Einfluss... 4

Mehr

Advoware mit VPN Zugriff lokaler Server / PC auf externe Datenbank

Advoware mit VPN Zugriff lokaler Server / PC auf externe Datenbank Advoware mit VPN Zugriff lokaler Server / PC auf externe Datenbank Die Entscheidung Advoware über VPN direkt auf dem lokalen PC / Netzwerk mit Zugriff auf die Datenbank des zentralen Servers am anderen

Mehr

Aufklappelemente anlegen

Aufklappelemente anlegen Aufklappelemente anlegen Dieses Dokument beschreibt die grundsätzliche Erstellung der Aufklappelemente in der mittleren und rechten Spalte. Login Melden Sie sich an der jeweiligen Website an, in dem Sie

Mehr

DELFI. Benutzeranleitung Dateiversand für unsere Kunden. Grontmij GmbH. Postfach 34 70 17 28339 Bremen. Friedrich-Mißler-Straße 42 28211 Bremen

DELFI. Benutzeranleitung Dateiversand für unsere Kunden. Grontmij GmbH. Postfach 34 70 17 28339 Bremen. Friedrich-Mißler-Straße 42 28211 Bremen Grontmij GmbH Postfach 34 70 17 28339 Bremen Friedrich-Mißler-Straße 42 28211 Bremen T +49 421 2032-6 F +49 421 2032-747 E info@grontmij.de W www.grontmij.de DELFI Benutzeranleitung Dateiversand für unsere

Mehr

Zwischenablage (Bilder, Texte,...)

Zwischenablage (Bilder, Texte,...) Zwischenablage was ist das? Informationen über. die Bedeutung der Windows-Zwischenablage Kopieren und Einfügen mit der Zwischenablage Vermeiden von Fehlern beim Arbeiten mit der Zwischenablage Bei diesen

Mehr

Hilfe Bearbeitung von Rahmenleistungsverzeichnissen

Hilfe Bearbeitung von Rahmenleistungsverzeichnissen Hilfe Bearbeitung von Rahmenleistungsverzeichnissen Allgemeine Hinweise Inhaltsverzeichnis 1 Allgemeine Hinweise... 3 1.1 Grundlagen...3 1.2 Erstellen und Bearbeiten eines Rahmen-Leistungsverzeichnisses...

Mehr

Kennen, können, beherrschen lernen was gebraucht wird www.doelle-web.de

Kennen, können, beherrschen lernen was gebraucht wird www.doelle-web.de Inhaltsverzeichnis Inhaltsverzeichnis... 1 Grundlagen... 2 Hyperlinks innerhalb einer Datei... 2 Verweisziel definieren... 2 Einen Querverweis setzen... 3 Verschiedene Arten von Hyperlinks... 3 Einfache

Mehr

Wordpress: Blogbeiträge richtig löschen, archivieren und weiterleiten

Wordpress: Blogbeiträge richtig löschen, archivieren und weiterleiten Wordpress: Blogbeiträge richtig löschen, archivieren und weiterleiten Version 1.0 Wordpress: Blogbeiträge richtig löschen, archivieren und weiterleiten In unserer Anleitung zeigen wir Dir, wie Du Blogbeiträge

Mehr

Systemnahe Software (Systemnahe Software II)

Systemnahe Software (Systemnahe Software II) ii Systemnahe Software (Systemnahe Software II) F. Schweiggert, A. Borchert, M. Grabert und J. Mayer 24. Mai 2005 Fakultät Mathematik u. Wirtschaftswissenschaften Abteilung Angewandte Informationsverarbeitung

Mehr

Anleitung über den Umgang mit Schildern

Anleitung über den Umgang mit Schildern Anleitung über den Umgang mit Schildern -Vorwort -Wo bekommt man Schilder? -Wo und wie speichert man die Schilder? -Wie füge ich die Schilder in meinen Track ein? -Welche Bauteile kann man noch für Schilder

Mehr

Programme im Griff Was bringt Ihnen dieses Kapitel?

Programme im Griff Was bringt Ihnen dieses Kapitel? 3-8272-5838-3 Windows Me 2 Programme im Griff Was bringt Ihnen dieses Kapitel? Wenn Sie unter Windows arbeiten (z.b. einen Brief schreiben, etwas ausdrucken oder ein Fenster öffnen), steckt letztendlich

Mehr

Datenbanken Kapitel 2

Datenbanken Kapitel 2 Datenbanken Kapitel 2 1 Eine existierende Datenbank öffnen Eine Datenbank, die mit Microsoft Access erschaffen wurde, kann mit dem gleichen Programm auch wieder geladen werden: Die einfachste Methode ist,

Mehr

HANDBUCH PHOENIX II - DOKUMENTENVERWALTUNG

HANDBUCH PHOENIX II - DOKUMENTENVERWALTUNG it4sport GmbH HANDBUCH PHOENIX II - DOKUMENTENVERWALTUNG Stand 10.07.2014 Version 2.0 1. INHALTSVERZEICHNIS 2. Abbildungsverzeichnis... 3 3. Dokumentenumfang... 4 4. Dokumente anzeigen... 5 4.1 Dokumente

Mehr

Lineargleichungssysteme: Additions-/ Subtraktionsverfahren

Lineargleichungssysteme: Additions-/ Subtraktionsverfahren Lineargleichungssysteme: Additions-/ Subtraktionsverfahren W. Kippels 22. Februar 2014 Inhaltsverzeichnis 1 Einleitung 2 2 Lineargleichungssysteme zweiten Grades 2 3 Lineargleichungssysteme höheren als

Mehr

Drucken aus der Anwendung

Drucken aus der Anwendung Drucken aus der Anwendung Drucken aus der Anwendung Nicht jeder Großformatdruck benötigt die volle Funktionsvielfalt von PosterJet - häufig sind es Standarddrucke wie Flussdiagramme und Organigramme die

Mehr

Unterprogramme. Funktionen. Bedeutung von Funktionen in C++ Definition einer Funktion. Definition einer Prozedur

Unterprogramme. Funktionen. Bedeutung von Funktionen in C++ Definition einer Funktion. Definition einer Prozedur Unterprogramme Unterprogramme sind abgekapselte Programmfragmente, welche es erlauben, bestimmte Aufgaben in wiederverwendbarer Art umzusetzen. Man unterscheidet zwischen Unterprogrammen mit Rückgabewert

Mehr

Daten-Synchronisation zwischen dem ZDV-Webmailer und Outlook (2002-2007) Zentrum für Datenverarbeitung der Universität Tübingen

Daten-Synchronisation zwischen dem ZDV-Webmailer und Outlook (2002-2007) Zentrum für Datenverarbeitung der Universität Tübingen Daten-Synchronisation zwischen dem ZDV-Webmailer und Outlook (2002-2007) Zentrum für Datenverarbeitung der Universität Tübingen Inhalt 1. Die Funambol Software... 3 2. Download und Installation... 3 3.

Mehr

Externe Abfrage von E-Mail für Benutzer der HSA über Mozilla-Thunderbird

Externe Abfrage von E-Mail für Benutzer der HSA über Mozilla-Thunderbird Externe Abfrage von E-Mail für Benutzer der HSA über Mozilla-Thunderbird Vorweg zunächst einige allgemeine Worte: Sie müssen über einen Account bei uns verfügen und ein E-Mail-Postfach bei uns haben. Dann

Mehr

S7-Hantierungsbausteine für R355, R6000 und R2700

S7-Hantierungsbausteine für R355, R6000 und R2700 S7-Hantierungsbausteine für R355, R6000 und R2700 1. FB90, Zyklus_R/W Dieser Baustein dient zur zentralen Kommunikation zwischen Anwenderprogramm und dem Modul R355 sowie den Geräten R6000 und R2700 über

Mehr

Stellen Sie bitte den Cursor in die Spalte B2 und rufen die Funktion Sverweis auf. Es öffnet sich folgendes Dialogfenster

Stellen Sie bitte den Cursor in die Spalte B2 und rufen die Funktion Sverweis auf. Es öffnet sich folgendes Dialogfenster Es gibt in Excel unter anderem die so genannten Suchfunktionen / Matrixfunktionen Damit können Sie Werte innerhalb eines bestimmten Bereichs suchen. Als Beispiel möchte ich die Funktion Sverweis zeigen.

Mehr

Mediator 9 - Lernprogramm

Mediator 9 - Lernprogramm Mediator 9 - Lernprogramm Ein Lernprogramm mit Mediator erstellen Mediator 9 bietet viele Möglichkeiten, CBT-Module (Computer Based Training = Computerunterstütztes Lernen) zu erstellen, z. B. Drag & Drop

Mehr

AGROPLUS Buchhaltung. Daten-Server und Sicherheitskopie. Version vom 21.10.2013b

AGROPLUS Buchhaltung. Daten-Server und Sicherheitskopie. Version vom 21.10.2013b AGROPLUS Buchhaltung Daten-Server und Sicherheitskopie Version vom 21.10.2013b 3a) Der Daten-Server Modus und der Tresor Der Daten-Server ist eine Betriebsart welche dem Nutzer eine grosse Flexibilität

Mehr

I.1 Die Parrot Assemblersprache

I.1 Die Parrot Assemblersprache I.1 Die Parrot Assemblersprache Die virtuelle CPU Parrot ermöglicht die Verarbeitung der Parrot Assemblersprache (PASM). Zum Einstieg soll ein erstes Beispiel die Ausführung einer PASM-Datei zeigen. Legen

Mehr

GEONET Anleitung für Web-Autoren

GEONET Anleitung für Web-Autoren GEONET Anleitung für Web-Autoren Alfred Wassermann Universität Bayreuth Alfred.Wassermann@uni-bayreuth.de 5. Mai 1999 Inhaltsverzeichnis 1 Technische Voraussetzungen 1 2 JAVA-Programme in HTML-Seiten verwenden

Mehr

Anton Ochsenkühn. amac BUCH VERLAG. Ecxel 2016. für Mac. amac-buch Verlag

Anton Ochsenkühn. amac BUCH VERLAG. Ecxel 2016. für Mac. amac-buch Verlag Anton Ochsenkühn amac BUCH VERLAG Ecxel 2016 für Mac amac-buch Verlag 2 Word-Dokumentenkatalog! Zudem können unterhalb von Neu noch Zuletzt verwendet eingeblendet werden. Damit hat der Anwender einen sehr

Mehr

Benutzung der LS-Miniscanner

Benutzung der LS-Miniscanner Benutzung der LS-Miniscanner Seit Januar 2010 ist es möglich für bestimmte Vorgänge (Umlagerungen, Retouren, Inventur) die von LS lieferbaren Miniscanner im Format Autoschlüsselgröße zu benutzen. Diese

Mehr

Erweiterung der Aufgabe. Die Notenberechnung soll nicht nur für einen Schüler, sondern für bis zu 35 Schüler gehen:

Erweiterung der Aufgabe. Die Notenberechnung soll nicht nur für einen Schüler, sondern für bis zu 35 Schüler gehen: VBA Programmierung mit Excel Schleifen 1/6 Erweiterung der Aufgabe Die Notenberechnung soll nicht nur für einen Schüler, sondern für bis zu 35 Schüler gehen: Es müssen also 11 (B L) x 35 = 385 Zellen berücksichtigt

Mehr

Einführung in die Programmierung

Einführung in die Programmierung : Inhalt Einführung in die Programmierung Wintersemester 2008/09 Prof. Dr. Günter Rudolph Lehrstuhl für Algorithm Engineering Fakultät für Informatik TU Dortmund - mit / ohne Parameter - mit / ohne Rückgabewerte

Mehr

SelfLinux-0.10.0. cron

SelfLinux-0.10.0. cron cron Autor: JC PollmanChristian Richter (jpollman@bigfoot.comcrichter@users.sourceforge.net) Formatierung: Matthias Hagedorn (matthias.hagedorn@selflinux.org) Lizenz: GPL Linux bietet zwei verwandte Programme

Mehr

Logging, Threaded Server

Logging, Threaded Server Netzwerk-Programmierung Logging, Threaded Server Alexander Sczyrba Michael Beckstette {asczyrba,mbeckste@techfak.uni-bielefeld.de 1 Übersicht Logging Varianten für concurrent server 2 current working directory

Mehr

Luis Kornblueh. May 22, 2014

Luis Kornblueh. May 22, 2014 Einführung in die Bash Luis Kornblueh KlosterCluster Team 2013/2014, Klosterschule May 22, 2014 1 / 17 Inhaltsverzeichnis Einführung in das Scripting Einfache Beispiele Kommandos ersetzen Bedingungen Tests

Mehr

Automatisierung ( Fernsteuerung ) von Excel unter Microsoft Windows Tilman Küpper (tilman.kuepper@hm.edu)

Automatisierung ( Fernsteuerung ) von Excel unter Microsoft Windows Tilman Küpper (tilman.kuepper@hm.edu) HMExcel Automatisierung ( Fernsteuerung ) von Excel unter Microsoft Windows Tilman Küpper (tilman.kuepper@hm.edu) Inhalt 1. Einleitung...1 2. Beispiele...2 2.1. Daten in ein Tabellenblatt schreiben...2

Mehr

Abschluss Version 1.0

Abschluss Version 1.0 Beschreibung Der Abschluss wird normalerweise nur einmal jährlich durchgeführt. Dieses Tech-Note soll helfen, diesen doch seltenen aber periodisch notwendigen Vorgang problemlos durchzuführen. Abschlussvarianten

Mehr

Arbeiten mit UMLed und Delphi

Arbeiten mit UMLed und Delphi Arbeiten mit UMLed und Delphi Diese Anleitung soll zeigen, wie man Klassen mit dem UML ( Unified Modeling Language ) Editor UMLed erstellt, in Delphi exportiert und dort so einbindet, dass diese (bis auf

Mehr

Was ist ein Prozess?

Was ist ein Prozess? Prozesse unter UNIX Definition Was ist ein Prozess? Zeitliche Abfolge von Aktionen Ein Programm, das ausgeführt wird Prozesshierachie Baumstruktur INIT-Prozess ist die Wurzel (pid=1) und wird beim Booten

Mehr

Einführung in die technische Informatik

Einführung in die technische Informatik Einführung in die technische Informatik Christopher Kruegel chris@auto.tuwien.ac.at http://www.auto.tuwien.ac.at/~chris Betriebssysteme Aufgaben Management von Ressourcen Präsentation einer einheitlichen

Mehr

Outlook. sysplus.ch outlook - mail-grundlagen Seite 1/8. Mail-Grundlagen. Posteingang

Outlook. sysplus.ch outlook - mail-grundlagen Seite 1/8. Mail-Grundlagen. Posteingang sysplus.ch outlook - mail-grundlagen Seite 1/8 Outlook Mail-Grundlagen Posteingang Es gibt verschiedene Möglichkeiten, um zum Posteingang zu gelangen. Man kann links im Outlook-Fenster auf die Schaltfläche

Mehr

K.U.Müller www.muellerbahn.de November 2009

K.U.Müller www.muellerbahn.de November 2009 K.U.Müller www.muellerbahn.de November 2009 Anschluss der Acryl-Röhren an die MpC Ziel ist es, den jeweiligen Röhren eine Block-Nummer zuzuordnen, um in diesem Block außerhalb der Anlage einen Zug parken

Mehr

DIRECTINFO 5.7 SICHERHEITSKONZEPTE FÜR BENUTZER, INFORMATIONEN UND FUNKTIONEN

DIRECTINFO 5.7 SICHERHEITSKONZEPTE FÜR BENUTZER, INFORMATIONEN UND FUNKTIONEN DIRECTINFO 5.7 SICHERHEITSKONZEPTE FÜR BENUTZER, INFORMATIONEN UND FUNKTIONEN - Whitepaper 1 Autor: Peter Kopecki Version: 1.2 Stand: Mai 2006 DIRECTINFO 5.7... 1 SICHERHEITSKONZEPTE FÜR BENUTZER, INFORMATIONEN

Mehr

Leitfaden zur ersten Nutzung der R FOM Portable-Version für Windows (Version 1.0)

Leitfaden zur ersten Nutzung der R FOM Portable-Version für Windows (Version 1.0) Leitfaden zur ersten Nutzung der R FOM Portable-Version für Windows (Version 1.0) Peter Koos 03. Dezember 2015 0 Inhaltsverzeichnis 1 Voraussetzung... 3 2 Hintergrundinformationen... 3 2.1 Installationsarten...

Mehr

Dieses UPGRADE konvertiert Ihr HOBA-Finanzmanagement 6.2 in die neue Version 6.3. Ein UPGRADE einer DEMO-Version ist nicht möglich.

Dieses UPGRADE konvertiert Ihr HOBA-Finanzmanagement 6.2 in die neue Version 6.3. Ein UPGRADE einer DEMO-Version ist nicht möglich. UPGRADE Version 6.2 -> Version 6.3 Dieses UPGRADE konvertiert Ihr HOBA-Finanzmanagement 6.2 in die neue Version 6.3. Ein UPGRADE einer DEMO-Version ist nicht möglich. Bitte beachten Sie, dass das UPGRADE

Mehr

Guideline. Facebook Posting. mit advertzoom Version 2.3

Guideline. Facebook Posting. mit advertzoom Version 2.3 Guideline Facebook Posting mit advertzoom Version 2.3 advertzoom GmbH advertzoom GmbH Stand November 2012 Seite [1] Inhalt 1 Facebook Posting Schnittstelle... 3 1.1 Funktionsüberblick... 3 2 Externe Ressource

Mehr

Informatik Grundlagen, WS04, Seminar 13

Informatik Grundlagen, WS04, Seminar 13 Informatik Grundlagen, WS04, Seminar 13 Informatik Informatik Grundlagen, Seminar 13 WS04 1 Was wir heute besprechen Nachbesprechen von Übungsblatt 11 Rekursion Grundprinzipien Übung Besprechung Übungsblatt

Mehr

C.M.I. Control and Monitoring Interface. Zusatzanleitung: Datentransfer mit CAN over Ethernet (COE) Version 1.08

C.M.I. Control and Monitoring Interface. Zusatzanleitung: Datentransfer mit CAN over Ethernet (COE) Version 1.08 C.M.I. Version 1.08 Control and Monitoring Interface Zusatzanleitung: Datentransfer mit CAN over Ethernet (COE) de LAN LAN Beschreibung der Datentransfermethode Mit dieser Methode ist es möglich, analoge

Mehr

Hilfedatei der Oden$-Börse Stand Juni 2014

Hilfedatei der Oden$-Börse Stand Juni 2014 Hilfedatei der Oden$-Börse Stand Juni 2014 Inhalt 1. Einleitung... 2 2. Die Anmeldung... 2 2.1 Die Erstregistrierung... 3 2.2 Die Mitgliedsnummer anfordern... 4 3. Die Funktionen für Nutzer... 5 3.1 Arbeiten

Mehr

Leitfaden zum Sichern einer Festplatte als Image mit der System Rescue CD

Leitfaden zum Sichern einer Festplatte als Image mit der System Rescue CD Leitfaden zum Sichern einer Festplatte als Image mit der System Rescue CD Benötigte Dinge: Eine System Rescue CD (kann vom Internet heruntergeladen werden http://www.sysresccd.org) Eine USB Festplatte

Mehr

BEDIENUNG ABADISCOVER

BEDIENUNG ABADISCOVER BEDIENUNG ABADISCOVER Juni 2005 / EMO v.2005.1 Diese Unterlagen sind urheberrechtlich geschützt. Alle Rechte, auch die der Übersetzung, des Nachdrucks und der Vervielfältigung der Unterlagen, oder Teilen

Mehr

Mind Mapping am PC. für Präsentationen, Vorträge, Selbstmanagement. von Isolde Kommer, Helmut Reinke. 1. Auflage. Hanser München 1999

Mind Mapping am PC. für Präsentationen, Vorträge, Selbstmanagement. von Isolde Kommer, Helmut Reinke. 1. Auflage. Hanser München 1999 Mind Mapping am PC für Präsentationen, Vorträge, Selbstmanagement von Isolde Kommer, Helmut Reinke 1. Auflage Hanser München 1999 Verlag C.H. Beck im Internet: www.beck.de ISBN 978 3 446 21222 0 schnell

Mehr

Feiertage in Marvin hinterlegen

Feiertage in Marvin hinterlegen von 6 Goecom GmbH & Co KG Marvin How to's Feiertage in Marvin hinterlegen Feiertage spielen in Marvin an einer Reihe von Stellen eine nicht unerhebliche Rolle. Daher ist es wichtig, zum Einen zu hinterlegen,

Mehr

Inhaltverzeichnis 1 Einführung... 1 2 Zugang zu den Unifr Servern... 1. 3 Zugang zu den Druckern... 4 4 Nützliche Links... 6

Inhaltverzeichnis 1 Einführung... 1 2 Zugang zu den Unifr Servern... 1. 3 Zugang zu den Druckern... 4 4 Nützliche Links... 6 Inhaltverzeichnis 1 Einführung... 1 2 Zugang zu den Unifr Servern... 1 2.1 Version Mac OSX 10.1-10.4, 10.6-10.7... 1 2.2 Version Mac OSX 10.5 (Leopard)... 2 3 Zugang zu den Druckern... 4 4 Nützliche Links...

Mehr

Antolin-Titel jetzt automatisch in WinBIAP kennzeichnen

Antolin-Titel jetzt automatisch in WinBIAP kennzeichnen & Antolin-Titel jetzt automatisch in WinBIAP kennzeichnen Immer mehr Schulen setzen das erfolgreiche Leseförderungsprojekt "Antolin" ein - und die Bibliotheken verzeichnen große Nachfrage nach den Kinderbüchern,

Mehr

Delegatesund Ereignisse

Delegatesund Ereignisse Delegatesund Ereignisse «Delegierter» Methoden Schablone Funktionszeiger Dr. Beatrice Amrhein Überblick Definition eines Delegat Einfache Delegate Beispiele von Delegat-Anwendungen Definition eines Ereignisses

Mehr

TR75E002-A FA / 07.2014. Bedienungsanleitung Industrie-Handsender-Verwaltung IHV DEUTSCH

TR75E002-A FA / 07.2014. Bedienungsanleitung Industrie-Handsender-Verwaltung IHV DEUTSCH TR75E002-A FA / 07.2014 Bedienungsanleitung Industrie-Handsender-Verwaltung IHV DEUTSCH Inhaltsverzeichnis 1 Das Programm Industrie-Handsender-Verwaltung IHV... 3 1.1 Was ist das Programm Industrie-Handsender-Verwaltung

Mehr

R-ADSL2+ Einrichthinweise unter Windows 98/ME

R-ADSL2+ Einrichthinweise unter Windows 98/ME R-ADSL2+ Einrichthinweise unter Windows 98/ME Verwenden Sie einen externen Router? Dann folgen Sie bitte der Anleitung des Routers und NICHT unseren zur Einrichtung einer Internetverbindung unter Windows

Mehr

Er musste so eingerichtet werden, dass das D-Laufwerk auf das E-Laufwerk gespiegelt

Er musste so eingerichtet werden, dass das D-Laufwerk auf das E-Laufwerk gespiegelt Inhaltsverzeichnis Aufgabe... 1 Allgemein... 1 Active Directory... 1 Konfiguration... 2 Benutzer erstellen... 3 Eigenes Verzeichnis erstellen... 3 Benutzerkonto erstellen... 3 Profil einrichten... 5 Berechtigungen

Mehr

Text Formatierung in Excel

Text Formatierung in Excel Text Formatierung in Excel Das Aussehen des Textes einer oder mehrerer Zellen kann in Excel über verschiedene Knöpfe beeinflusst werden. Dazu zuerst die betroffenen Zelle(n) anwählen und danach den entsprechenden

Mehr

Wo möchten Sie die MIZ-Dokumente (aufbereitete Medikamentenlisten) einsehen?

Wo möchten Sie die MIZ-Dokumente (aufbereitete Medikamentenlisten) einsehen? Anleitung für Evident Seite 1 Anleitung für Evident-Anwender: Einbinden der MIZ-Dokumente in Evident. Wo möchten Sie die MIZ-Dokumente (aufbereitete Medikamentenlisten) einsehen? Zunächst müssen Sie entscheiden,

Mehr

affilinet_ Flash-Spezifikationen

affilinet_ Flash-Spezifikationen affilinet_ Flash-Spezifikationen Inhaltsverzeichnis Allgemeines...2 Klickzählung...2 Lead/Sale Programme... 2 PPC und Kombi Programme...3 Übergabe von Formulardaten...4 clicktag Variante Sale/Lead Programm...4

Mehr

Artikel Schnittstelle über CSV

Artikel Schnittstelle über CSV Artikel Schnittstelle über CSV Sie können Artikeldaten aus Ihrem EDV System in das NCFOX importieren, dies geschieht durch eine CSV Schnittstelle. Dies hat mehrere Vorteile: Zeitersparnis, die Karteikarte

Mehr

Stepperfocuser 2.0 mit Bootloader

Stepperfocuser 2.0 mit Bootloader Stepperfocuser 2.0 mit Bootloader Info Für den Stepperfocuser 2.0 gibt es einen Bootloader. Dieser ermöglicht es, die Firmware zu aktualisieren ohne dass man ein spezielles Programmiergerät benötigt. Die

Mehr

Übung zu Grundlagen der Betriebssysteme. 13. Übung 22.01.2012

Übung zu Grundlagen der Betriebssysteme. 13. Übung 22.01.2012 Übung zu Grundlagen der Betriebssysteme 13. Übung 22.01.2012 Aufgabe 1 Fragmentierung Erläutern Sie den Unterschied zwischen interner und externer Fragmentierung! Als interne Fragmentierung oder Verschnitt

Mehr

Massenversand Dorfstrasse 143 CH - 8802 Kilchberg Telefon 01 / 716 10 00 Telefax 01 / 716 10 05 info@hp-engineering.com www.hp-engineering.

Massenversand Dorfstrasse 143 CH - 8802 Kilchberg Telefon 01 / 716 10 00 Telefax 01 / 716 10 05 info@hp-engineering.com www.hp-engineering. Massenversand Massenversand Seite 1 Massenversand Seite 2 Inhaltsverzeichnis 1. WICHTIGE INFORMATIONEN ZUR BEDIENUNG VON CUMULUS 4 2. STAMMDATEN FÜR DEN MASSENVERSAND 4 2.1 ALLGEMEINE STAMMDATEN 4 2.2

Mehr