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. |