Ob Apache oder Datenbank -- alle Dämonen müssen irgendwann rauf- oder runtergefahren werden. Das heute vorgestellte Skript startet Prozesse und schießt sie auf Anforderung ab.
apachectl
, das Tool um den Apache zu starten, macht's so: Wird
es mit einem Parameter start
aufgerufen, fährt es den Webserver
hoch und legt die PID des Hauptprozesses in einer Datei
http.pid
im log
-Verzeichnis ab. Ruft man anschließend
apachectl stop
auf, wird in http.pid
nachgesehen, welche
Prozessnummer der Apache hat und ein kill
-Befehl
darauf angesetzt.
Das Skript in Listing pc
(für Process Control)
macht genau das selbe mit beliebigen Prozessen:
pc start myproc
startet das Programm myproc
als Hintergrundprozess
und legt die Prozess-ID in myproc.pid
ab. Ein anschließendes
pc stop myproc
liest myproc.pid
aus und sendet dem Prozess ein SIGTERM
-Signal,
das diesen beendet, falls er es nicht bewusst abfängt und
ignoriert. Im Notfall lässt sich mit
pc -s KILL stop myproc
auch das SIGKILL
-Signal einstellen, das kein Prozess abfangen kann
und das ihn unweigerlich vom Himmel holt. Wem die Geschwätzigkeit von
pc
zuviel wird, kann es mit der Option -q
zum Schweigen
bringen:
pc -q start myproc
sagt nicht Starting myproc ... started, sondern gar nichts, falls alles glatt ging. Soll vor dem Starten in ein anderes Verzeichnis gewechselt werden, lässt sich das mit
pc -d /my/directory start myproc
bewerkstelligen. Soll nur die Datei, in der die PID gespeichert wird,
in einem anderen Verzeichnis landen, kommt die l
-Option zum Einsatz:
pc -l /tmp start myproc
legt myproc.pid
in /tmp
ab.
Bei relativen Pfadangaben ist darauf zu achten, dass diese vom
gegenwärtigen Verzeichnis aus gelten, oder von dem mittels der -d
-Option
aus festgelegten.
Das Skript pc
überprüft genau, ob es auch
mit dem start
/stop
-Befehl und mindestens
einem Programmnamen aufgerufen wurde. Andernfalls bricht es
ab und gibt die Aufrufsyntax aus:
pc [-d dir] [-l logdir] [-s signal] [-q] start|stop proc
Der Aufruf des Programms, das pc
starten soll, kann auch zusätzliche
Parameter enthalten. So startet zum Beispiel
pc start sleep 60
einen 60 Sekunden währenden sleep
-Prozess im Hintergrund und legt
die PID in der Datei sleep.pid
ab. Den Reigen beendet
pc stop sleep
(mit oder ohne Parameterangabe 60
) abrupt wieder.
pc
funktioniertDas Getopt::Std
-Modul, das Zeile 7 in Listing pc
hereinzieht,
behandelt die Kommandozeilenoptionen und setzt entsprechende
Einträge im Hash %opts
. Der Befehl
getopts('d:l:s:q', \%opts);
legt fest, dass die Optionen -d
, -l
und -s
, falls sie
gesetzt sind, jeweils ein Argument erwarten. -q
steht für sich
selbst, ist also ein boolean Flag. Wird pc
mit
pc -s KILL -q stop sleep
aufgerufen, ist $opts{s}
auf "KILL"
gesetzt und $opts{q}
auf 1
.
Zeile 10 definiert den Prototyp der usage
-Funktion und legt fest,
dass sie einen Skalar als Argument erwartet. Danach kann usage
auch
ohne Klammern aufgerufen werden, wie z.B. in Zeile 14. Dort wird
überprüft, ob die Anzahl der Parameter, die übrigbleiben, nachdem
getopt
alle definierten Optionen bearbeitet und entfernt hat, zwei
unterschreitet. In diesem Fall fehlt entweder eine Aktion oder das
Skript, das pc
starten oder stoppen soll. Zeile 15 prüft, ob
start
oder stop
vergessen wurde.
Zeigt die -d
-Option an, dass pc
zunächst in ein bestimmtes
Verzeichnis wechseln soll, führt Zeile 18 dies durch und bricht ab,
falls die Aktion aus irgend welchen Gründen fehlschlägt.
Wie alle Fehlermeldungen
wird auch diese über die usage
-Funktion mit einer kleinen
Hilfsanweisung mit den gültigen Aufrufparametern von pc
begleitet,
bevor das Programm abbricht.
Zeile 21 definiert den Namen der Datei, in der die Prozess-ID eines
gestarteten Programms abgelegt wird. Wurde mit -l
ein anderes Verzeichnis
spezifiziert, hängt Zeile 22 den Dateinamen hinter den angegebenen Pfad.
Das Modul File::Spec
tut dies betriebssystemunabhängig, unter Unix
kommt dies auf's selbe heraus wie "$opt{l}/$pidf"
.
Die if
-else
-Logik ab Zeile 24 verzweigt zu start_proc
oder
stop_proc
, je nachdem ob wir einen Prozess starten oder stoppen
wollen. Im Startfall übergibt Zeile 25 der Funktion start_proc
nicht
nur die Pfadangabe zur PID-Datei, sondern fügt auch noch den Namen
des zu startenden Prozesses mit allen angegebenen Parametern bei.
@ARGV[1..$#ARGV]
ist ein sogenanntes Array-Slice, das
alle Argumente aus @ARGV
enthält, außer dem ersten, das auf
start
oder stop
gesetzt ist und beim eigentlichen Starten
des Prozesses nichts verloren hat.
start_proc
ab Zeile 31 entpuffert zunächst die Standard-Ausgabe,
so dass es die mit print
geschriebenen Meldungen auch dann gleich
anzeigt, wenn noch kein Newline-Zeichen angegeben wurde. $| = 1
tut
genau dies, und um die Einstellung nicht global durchzuführen,
wird es mit local
ausgezeichnet --
lexikalischen Scope mit my
kann man leider nur mit ``normalen''
Variaben, $|
ist ein Spezialteil.
Stellt Zeile 38 fest, dass eine PID-Datei schon existiert, bricht
pc
den Vorgang mit der Meldung "Already running"
ab, denn
es geht davon aus, dass der Prozess schon gestartet wurde und
noch läuft und erst mit pc stop
gestoppt werden muss.
Zeile 43 erzeugt mit fork
einen Sohnprozess. Der Vater, der
normal weiterläuft erhält in $pid
die Prozess-ID des Sohnes
zugewiesen, während im Sohn-Universum $pid
auf 0
gesetzt ist.
Der Vater schreibt dann in Zeile 46 schnell die Sohn-PID in die PID-Datei und
kehrt nach dem if
-Block mit einer Meldung zurück, falls pc
nicht
gerade mit der -q
-Option zum Schweigen verdonnert wurde.
Der Sohn überlädt in Zeile 51 mit exec
den gegenwärtigen Prozess
mit einem externen Programm, dessen Name und Kommandozeilenparamter
in @proc
liegen. exec
ist ein schwarzes Loch, aus dem niemand
jemals lebend zurückkehrt -- außer es ging etwas grob schief, wie wenn
etwa der Prozess zum Überladen nicht gefunden wurde. In diesem
Fall springt Zeile 52 ein und bricht mit einer Fehlermeldung ab.
stop_proc
ab Zeile 59 versucht, einen laufenden Prozess durch
das Senden eines Signals zu beenden. Hierzu versucht Zeile 66, die
Prozess-ID aus der PID-Datei zu extrahieren. Existiert diese nicht,
liegt der Schluß nahe, dass der Prozess nie mit pc
gestartet wurde
und Zeile 68 bricht den Vorgang ab.
Andernfalls sendet Zeile 74 dem Prozess mit dem kill
-Signal das
Signal Nummer 0
, was auf den Prozess keine Auswirkungen hat, aber
schiefgeht, falls der Prozess nicht existiert. In diesem Fall
denkt pc
, der Prozess wäre schon gestoppt, löscht die PID-Datei
und bricht rasch mit einer Fehlermeldung ab.
Geht Zeile 74 gut, existiert der Prozess mit der angegebenen PID und
das Konstrukt zwischen 80 und 89 versucht zehn Mal im Sekundentakt,
den Prozess mit einem SIGTERM-Signal zum Beenden zu überreden.
Falls pc
mit der
Option -s
und einem Signalnamen wie KILL
, HUP
, INT
etc.
aufgerufen wurde, nimmt der kill
-Befehl in Zeile 81 diesen stattdessen.
Alle verfügbaren Signal definiert die include
-Datei
/usr/include/asm/signal.h
).
Zeile 82 prüft hinterher immer, ob der Prozess immer noch herumhängt,
und falls ja, geht's in die nächste Runde. Nach zehn ist endgültig
Schluss und pc
gibt auf.
pc -s KILL stop myapp
holt eine vorher gestartete Applikation myapp
aber garantiert
auf den Boden der Tatsachen zurück, denn das SIGKILL
-Signal mit
der Nummer 9 bedeutet das unweigerliche Ende, ohne dass der Prozess
auch noch eine Chance zum Aufräumen hätte.
Stellt pc
fest, dass der
Prozess gekillt wurde, löscht es in Zeile 84 die PID-Datei und beendet sich.
Die usage
-Funktion ab Zeile 95 extrahiert aus $0
, das
den Programmpfad enthält (z. B. ./myapp
) den Programmnamen (myapp
)
und gibt die unterstützten Optionen aus.
Startet fleissig und stoppt selten! Viel Spass damit!
001 #!/usr/bin/perl 002 ################################################## 003 # pc [-d dir] [-l logdir] [-s signal] [-q] 004 # start|stop proc 005 ################################################## 006 007 use Getopt::Std; 008 use File::Spec; 009 010 sub usage($); 011 012 getopts('d:l:s:q', \%opts); 013 014 usage "Wrong argument count" if @ARGV < 2; 015 usage "No action" unless $ARGV[0] eq "start" || 016 $ARGV[0] eq "stop"; 017 018 chdir($opts{d}) or usage "Cannot chdir to $opts{d}" if 019 exists $opts{d}; 020 021 my $pidf = "$ARGV[1].pid"; 022 $pidf = File::Spec->catfile($opts{l}, 023 $pidf) if $opts{l}; 024 if($ARGV[0] eq "start") { 025 start_proc($pidf, @ARGV[1..$#ARGV]); 026 } else { 027 stop_proc($pidf); 028 } 029 030 ################################################## 031 sub start_proc { 032 ################################################## 033 my ($pidf, @proc) = @_; 034 local $| = 1; 035 036 print "Starting @proc ... " unless $opts{q}; 037 038 if(-f $pidf) { 039 print "Already running\n"; 040 exit 0; 041 } 042 043 defined(my $pid = fork()) or die "Fork failed"; 044 045 if($pid != 0) { # Father 046 open LOG, ">$pidf" or 047 usage "Cannot open $pidf"; 048 print LOG "$pid\n"; 049 close LOG; 050 } else { # Son 051 exec @proc; 052 usage "Cannot start \"$proc[0]\""; 053 } 054 055 print "started\n" unless $opts{q}; 056 } 057 058 ################################################## 059 sub stop_proc { 060 ################################################## 061 my $pidf = shift; 062 local $| = 1; 063 064 print "Stopping ... " unless $opts{q}; 065 066 if(! open LOG, "<$pidf") { 067 print "No instance running\n"; 068 exit 0; 069 } 070 071 my $pid = <LOG>; 072 close LOG; 073 074 if(! kill 0, $pid) { 075 print "Already Stopped\n"; 076 unlink $pidf or die "Cannot unlink $pidf"; 077 return; 078 } 079 080 foreach (1..10) { 081 kill $opts{s} || "TERM", $pid; 082 if(! kill 0, $pid) { 083 print "stopped\n" unless $opts{q}; 084 unlink $pidf or 085 die "Cannot unlink $pidf"; 086 return; 087 } 088 sleep(1); 089 } 090 091 print "Can't stop it - giving up.\n"; 092 } 093 094 ################################################## 095 sub usage($) { 096 ################################################## 097 (my $prog = $0) =~ s#.*/##g; 098 print "$prog: $_[0].\n"; 099 print "usage: $prog [-d dir] [-l logdir] " . 100 "start|stop proc\n"; 101 exit 1; 102 }
Michael Schilliarbeitet als Software-Engineer bei Yahoo! in Sunnyvale, Kalifornien. Er hat "Goto Perl 5" (deutsch) und "Perl Power" (englisch) für Addison-Wesley geschrieben und ist unter mschilli@perlmeister.com zu erreichen. Seine Homepage: http://perlmeister.com. |