Upload
others
View
3
Download
0
Embed Size (px)
Citation preview
Beispiel Block 2 Parallele Prozesse: fork, exec und wait Interprozesskommunikation mit Unnamed Pipes
Oliver Höftberger
SS 2013
Slides by Benedikt Huber 1
fork, exec, exit und wait
Prozess erzeugen
Programmabbild ersetzen
Programm beenden
Auf Ende eines Prozesses warten 2
Prozesseigenschaften Linux (1)
Zustand Running, Stopped, …
Scheduling Priorität, CPU-Zeit, …
Identifikation PID, Owner, Gruppe, …
Speicherverwaltung Pointer auf MMU Info
Signale Mask, Pending
Prozessverwandschaften Parents, Siblings
4/9/2013 3
Prozesseigenschaften Linux (2)
Process Control Block Register, PC, Statuswort, Segmentregister, Page Table Info
Kernelstack
Dateideskriptorentabelle
Berechtigungen, Accounting Information
Timerverwaltung
Interprozesskommunikation
Siehe: sched.h / struct task_struct
4/9/2013 4
Prozesshierarchie
Jeder Prozess hat
Vaterprozess
Ausnahme: init
Jeder Prozess hat
eine eindeutige ID (pid_t)
init─┬─acpid
├─ahc_dv_0
├─ahc_dv_1
├─bash
├─clock-applet
├─crond
├─cups-config-dae
├─cupsd
├─2*[dbus-daemon-1]
├─dbus-launch
├─dhcpd
├─gdm-binary─┬─gdm-binary─┬─X
│ │ └─gdmgreeter
│ └─gdm-binary─── ...
├─2*[sendmail]
├─sesam_server─┬─sesam_server
│ └─sesam_server── ...
├─smbd───5*[smbd]
├─sshd─┬─sshd───sshd───bash───pine
│ ├─sshd───sshd───bash───pine
. .
5
Erstellen von Prozessen
Prozesse werden üblicherweise mit fork(2)
erzeugt.
Weitere Möglichkeit: clone(2)
In der Übung ist ausschließlich fork(2) zu verwenden
exec(3) überschreibt den aktuellen Prozess durch
ein anderes Programm.
wait(3) wartet auf die Terminierung eines Kindes
6
7
fork() / exec() / wait()
fork()
• erzeugt neuen Prozess
exec*(„program“)
• ersetzt image eines Prozesses durch neues Programm
exit(status)
• beendet Prozess
wait*(&status)
• wartet auf Beendigung des Kindprozesses
fork parent fork child
exec
wait exit
initialize
child context
8
fork()
Erzeugt einen neuen Prozess
Neuer Prozess ist eine identische Kopie des aufrufenden Prozesses (bis auf PID, Locks, ...)
Erzeugender Prozess ist Vater des erzeugten Prozesses
Beide Prozesse laufen parallel und führen dasselbe Programm aus
fork parent fork child
exec
wait exit
initialize
child context
9
fork()
nPID = fork()
Vaterprozess
PC
nPID = fork()
Vaterprozess
PCnPID = fork()
Kindprozess
PC
Vor dem fork
Nach dem fork
10
fork()
Unterscheidung zwischen Vater- und Kindprozess durch den Rückgabewert von fork(): • -1 im Fehlerfall
• 0 im Kindprozess (child)
• >0 im aufrufenden Prozess (parent)
11
fork()
Kindprozess erbt von Elternprozess offene Dateien (gemeinsamer Zugriff!)
Dateipuffer
Signaldefinitionen
momentane Variablenwerte
Jedoch gilt: Variablen sind lokal (keine Beeinflussung)
Signale können lokal umgesetzt werden
Kommunikation (IPC) via Pipes, Sockets, Shared Memory, ...
12
C-Interface
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
13
Verwendung von fork()
pid_t pid; ... switch (pid= fork()) { case -1: bail_out( “can‘t fork“ ); break; case 0: /* child: ChildProcess() */ ... exit(EXIT_SUCCESS); break; default: /* parent: ParentProcess() */ ... break; }
clone(2), vfork(2)
clone() wird von fork(2), vfork(2) aufgerufen. clone() erlaubt Kontrolle über die zu duplizierenden
Eigenschaften (Linux spezifisch, nicht portabel!)
vfork(): blockiert bis exec(), das Kind darf keine Variablen mehr ändern, und nur noch exec() ausführen. Veraltet, nicht mehr empfohlen
clone(), vfork() ist kein Teil des Stoffes!
14
15
exec()
Erlaubt es einem Prozess, ein anderes Programm auszuführen
Startet ein neues Programm innerhalb eines Prozesses
PID bleibt gleich
fork parent fork child
exec
wait exit
initialize
child context
Die exec() Familie (1)
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ... , char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int fexecve(int fd, char *const argv[], char *const envp[]);
16
17
Die exec() Familie (2)
Ersetzt aktuelles Programm durch das in der Datei path enthaltene
exec*p - Variante sucht (wie Shell) in $PATH nach Programm mit dem spezifierten Namen
Argumentübergabe beachten! – Argumentliste muss mit NULL Zeiger enden
Varianten mit variable Argumentanzahl (execl*) und Argumentarray (execv*)
Variante execle: Environment kann verändert werden Variante fexecve: akzeptiert Dateideskriptor
18
execv(), execvp()
#include <unistd.h>
int execv(char *path, char *argv[] ); int execvp(char *filename, char *argv[] ); char *cmd[] = { "ls", "-l", (char *) 0 }; (void) execv ("/bin/ls", cmd); (void) execvp ("ls", cmd); bail_out ( “can‘t exec“ );
19
execl(), execlp()
#include <unistd.h> int execl(char *path, char *arg0, ..., char *arg_n,(char*)0); int execlp(char *filename, char *arg0, ..., char *arg_n, (char*)0); (void) execl ( “/bin/ls“, “ls“, “-l“, (char*) 0 ); (void) execlp ( “ls“, “ls“, “-l“, (char*) 0 ); bail_out ( “can‘t exec“ );
20
exit()
Beendet den aktuellen Prozess
Rückgabewert kann von Vaterprozess abgefragt werden
Beim Beenden: • Leeren der stdio – Buffer
• Schließen offener Dateien
• Löschen von temporären Dateien (tmpfile(3))
• Aufrufen von Exit-Handlern (atexit(3))
fork parent fork child
exec
wait exit
initialize
child context
21
exit()
void exit(int status);
Status: 8-bit (0-255)
Im C-Standard definierte Rückgabewerte • exit(EXIT_SUCCESS) keine Fehler
• exit(EXIT_FAILURE) Fehler aufgetreten
Weitere Rückgabewerte BSD: sysexits.h
http://tldp.org/LDP/abs/html/exitcodes.html
22
wait()
Wartet bis Kindprozess terminiert
Liefert PID und Status des beendeten Prozesses
Wenn kein Kindprozess existiert, -1 als Rückgabewert (auch bei EINTR)
fork parent fork child
exec
wait exit
initialize
child context
23
wait()
Liefert PID und Status des terminierten Kindprozesses • pid = wait(&status)
Status beinhaltet Rückgabewert und Signalinformation
WIFEXITED(status), WEXITSTATUS(status)
WIFSIGNALED(status), WTERMSIG(status)
See man 2 wait
Nach dem Aufruf von wait wird der Kindprozess aus der Prozesstabelle entfernt
24
exit(): Zombies
UNIX: Auch bereits terminierte Prozesse besetzen einen Eintrag in der Prozesstabelle falls kein Platz mehr frei ist, kann kein neuer Prozess mehr gestartet
werden
Der Kindprozess terminiert und der Vaterprozess hat noch nicht wait ausgeführt Der Kindprozess wird auf Zustand „Zombie“ gesetzt
Eintrag in der Prozesstabelle bleibt erhalten bis der Vaterprozess wait ausführt
25
exit(): Zombies
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv) {
unsigned i = 0;
while(++i) {
pid_t pid = fork();
if(pid < 0) { bail_out(“fork()”); }
if(pid == 0) { exit(EXIT_SUCCESS); }
printf(“We now have %d zombies…\n",i);
}
}
26
exit(): Orphans
UNIX: Auch bereits terminierte Prozesse besetzen einen Eintrag in der Prozesstabelle falls kein Platz mehr frei ist, kann kein neuer Prozess mehr gestartet
werden
Der Vaterprozess terminiert und der Kindprozess wurde noch nicht beendet Kind Prozess „verwaist“ (orphan) und wird dem init Prozess vererbt
Nach der Beendigung eines Orphans entfernt init den Prozesseintrag
27
C-Interface
#include <sys/wait.h> pid_t wait ( int * status );
int status; pid_t child_pid, pid; ...
while ((pid = wait(&status)) != child_pid ) {
bail_out( “can‘t wait“, 1 ); } if (WEXITSTATUS(status) == EXIT_SUCCESS ) ...
Stoppt Prozessausführung
bis entweder Kindprozess
terminiert oder ein Fehler
auftritt (≠ busy waiting).
if (pid != -1) continue; /* other child */
if (errno == EINTR) continue; /* interrupted */
28
waitpid(): verwandter Call to wait()
Warten auf das Terminieren eines Prozesses mit einer bestimmten Prozess ID:
pid_t waitpid (pid_t pid,
int *status,
int options);
waitpid(-1,&status,0) äquivalent zu wait
waitpid(pid, &status, 0) wartet auf Kind mit PID pid
waitpid(-1, &status, WNOHANG) blockiert nicht.
Wurde ein Kind beendet?
Synchron waitpid(-1,&status, WNOHANG)
Blockiert nicht, holt Exit-Status falls ein Kind beendet
wurde (Polling)
Asynchron
Wenn ein Kind beendet wurde wird das Signal SIGCHLD
an den Elternprozess gesendet.
Installieren eines Signalhandlers (sigaction) für
SIGCHLD
Aufruf von wait im Signal Handler
29
30
Rückblende Teil 1
fork() - Kindprozess erzeugen
exec() - neues Programm innerhalb eines Prozesses
starten
exit() – den aktuellen Prozess beenden
wait() - im Vaterprozess auf die Terminierung des
Kindprozesses warten
Fallstricke
int main(…)
{
fprintf(stdout,“Hallo“);
fork();
return 0;
}
Ausgabe: “HalloHallo“
Warum?
31
Fallstricke
int main(…)
{
fprintf(stdout,“Hallo“);
fork();
return 0;
}
Ausgabe: “HalloHallo“
Warum?
32
Stream IO
„Hallo“
IO
OS
Stream IO
„Hallo“
IO
OS
Stream IO
„Hallo“
IO
fork
Fallstricke
int main(…)
{
fprintf(stdout,“Hallo“);
fflush(stdout);
fork();
return 0;
}
Ausgabe: “Hallo“
33
Stream IO
„Hallo“
IO
OS
Stream IO
„Hallo“
IO
OS
Stream IO
„Hallo“
IO
fork
Stream IO
<empty>
IO
OS
Stream IO
<empty>
IO
OS
Stream IO
<empty>
IO
fork
Unnamed Pipes
pipe() und dup()
34
35
Pipes
Kommunikationskanal zwischen verwandten
Prozessen
Eigenschaften:
Unidirektional (2 Pipes für Übertragung in beide
Richtungen)
„Stream“ von Daten
Implizite Synchronisation
36
Verwendung von Pipes (1)
Pipe wird mittels eines Feldes von zwei Integer-
Elementen deklariert
int fildes[2];
Deskriptor fildes[0] ist Leseende
Deskriptor fildes[1] ist Schreibende
Nicht verwendete Enden müssen geschlossen
werden
Schreibender Prozess schließt das Leseende
Lesender Prozess schließt das Schreibende
37
Verwendung von Pipes (1)
write()
Prozess
read()
p[1]→
p[0]←
write()
Kindprozess
read()
p[1]→
p[0]←
write()
Vaterprozess
read()p[0]→
p[1]←
←←
ohne geschlossene Enden
38
Verwendung von Pipes (1)
geschlossene Enden
write()
Kindprozess
read()
p[1]→ write()
Vaterprozess
read()p[0]→
←←
39
Verwendung von Pipes (2)
Öffnen einer Pipe mittels Systemaufruf pipe()
fdopen() erstellt einen mit dem gegebenen Filedeskriptor assozierten Stream (FILE *)
Verwendung von Funktionen, die auf Streams operieren
z.B. fopen(), fclose()
dup2() kann zum Umlenken von Dateien verwendet werden
40
C-Interface
#include <limits.h> /* for PIPE_BUF */
#include <unistd.h> /* prototype */
int fildes[2];
if (pipe(fildes) != 0)
{
bail_out( “can‘t create pipe“, 1 );
}
dup(2), dup2(2)
dup() dupliziert einen File-Deskriptor.
Der neue Deskriptor erhält die niedrigste nicht
verwendete ID
Duplizierter Deskriptor zeigt auf dieselbe „open file
description“ (file offset, file status flags) man 2 open
dup2(old, new) Schließt Filedeskriptor mit ID new
Dupliziert old, der neue Deskriptor erhält ID new
41
Umleiten der Standardein-/ausgabe
Anwendung: Kommunikation mit
Kommandozeilen-Utility, welches über
Standardeingabe / Standardausgabe kommuniziert
Strategie: Umleiten der Standardeingabe (0) oder
Standardausgabe (1) in neuem Prozess
Schließen des Filedeskriptors (z.B. fileno(stdin)) für
Standard I/O
Duplizieren eines offenen FDs (z.B. file_descr[0]) auf
eben geschlossenen (fileno(stdin))
Schließen des duplizierten FDs (file_descr[0])
42
43
C-Interface #include <fcntl.h> #include <sys/types.h> #include <unistd.h> int fd; fd = open(„log.txt“,O_WRONLY|O_CREAT); dup2(fd, /* old descriptor */ fileno(stdout)); /* new descriptor */ close(fd); /* close old desc. */ (void) execlp(“grep“, “grep“, „max“, (char *) 0);
44
Synchronisation
Lesen von leerer Pipe ist blockierend
Schreiben auf volle Pipe ebenso
Achtung: Blockiert wenn vergessen Pipe zu schließen
Lesen von Pipe ohne offene Schreibenden liefert
EOF
Schreiben auf Pipe ohne offene Leseenden liefert
SIGPIPE Signal
45
Pipes: Pitfalls
Pipes eignen sich gut für unidirektionale
Kommunikation
Bidirektional: Zwei Pipes
Fehleranfällige Synchronisation (deadlock)
Synchronisation & Puffer
fflush() verwenden
Puffer konfigurieren (setbuf(3), setvbuf(3))
46
Rückblende
Kommunikation zwischen verwandten Prozessen
mittels Pipes.
Implizite Synchronisation
Nicht verwendete Enden schließen
Filestream für Filedeskriptor der Pipe mittels
fdopen()
Umlenken von Pipes mittels dup2()