Rheinwerk Computing < openbook > Rheinwerk Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger

 << zurück
Linux-UNIX-Programmierung von Jürgen Wolf
Das umfassende Handbuch – 2., aktualisierte und erweiterte Auflage 2006
Buch: Linux-UNIX-Programmierung

Linux-UNIX-Programmierung
1216 S., mit CD, 49,90 Euro
Rheinwerk Computing
ISBN 3-89842-749-8
gp Kapitel 7 Dämonen, Zombies und Prozesse
  gp 7.1 Was ist ein Prozess?
  gp 7.2 Prozesskomponente
    gp 7.2.1 Prozessnummer (PID)
    gp 7.2.2 Prozessnummer des Vaterprozesses (PPID)
    gp 7.2.3 Benutzer- und Gruppennummer eines Prozesses (UID, EUID, GID, EGID)
    gp 7.2.4 Prozessstatus
    gp 7.2.5 Prozesspriorität
    gp 7.2.6 Timesharing-Prozesse
    gp 7.2.7 Prozessauslagerung
    gp 7.2.8 Steuerterminal
  gp 7.3 Prozesse überwachen – ps, top, kpm
  gp 7.4 Lebenszyklus eines Prozesses
  gp 7.5 Umgebungsvariablen eines Prozesses
    gp 7.5.1 Einzelne Umgebungsvariablen abfragen
    gp 7.5.2 Umgebungsvariable verändern oder hinzufügen – putenv() und setenv()
    gp 7.5.3 Löschen von Umgebungsvariablen – unsetenv() und clearenv()
  gp 7.6 Ressourcenlimits eines Prozesses
    gp 7.6.1 Mehr Sicherheit mit Ressourcenlimits
  gp 7.7 Prozesserkennung
  gp 7.8 Erzeugung von Prozessen – fork()
    gp 7.8.1 Pufferung
    gp 7.8.2 Was wird vererbt und was nicht?
    gp 7.8.3 Einen Prozess mit veränderter Priorität erzeugen
  gp 7.9 Warten auf einen Prozess
  gp 7.10 Die exec-Familie
    gp 7.10.1 execl()
    gp 7.10.2 execve()
    gp 7.10.3 execv()
    gp 7.10.4 execle()
    gp 7.10.5 execlp()
    gp 7.10.6 execvp()
    gp 7.10.7 Kindprozesse mit exec-Aufruf überlagern
  gp 7.11 Kommandoaufrufe aus dem Programm – system()
  gp 7.12 Dämonprozesse
    gp 7.12.1 Wie ein Prozess zum Dämon wird ...
    gp 7.12.2 Dämon, sprich mit uns ...
    gp 7.12.3 Protokollieren von Dämonen – syslog()
    gp 7.12.4 syslog() in der Praxis
    gp 7.12.5 Den Dämon, den ich rief ...
  gp 7.13 Rund um die Ausführung von Prozessen
    gp 7.13.1 Einen Dämon beim Booten mit einem init-Skript starten
    gp 7.13.2 Hintergrundprozesse und Jobkontrolle
    gp 7.13.3 Prozesse zeitgesteuert ausführen (cron-Jobs)
  gp 7.14 Zusammenfassung und Ausblick


Rheinwerk Computing

7.13 Rund um die Ausführung von Prozessedowntop

Jetzt haben Sie zwar eine Menge zu den Prozessen erfahren, aber häufig fehlt es noch ein wenig am Detail. Hiermit ist nicht mehr der programmtechnische Aspekt gemeint, sondern alles rund um die Ausführung von Prozessen. Hier war die Rede von einem Hintergrundprozess, dann wieder von einem init-Skript oder einem cron-Job. Eine Menge Know-how, das man hier einfach nicht als gotgegeben stehen lassen kann, weshalb darauf in den nächsten Seiten bis zum Schluss dieses Kapitels eingegangen werden soll.


Rheinwerk Computing

7.13.1 Einen Dämon beim Booten mit einem init-Skript starten  downtop

Sobald der Kernel gestartet wurde, kann dieser erst nur lesend auf die Root-Partition zugreifen. Der erste Prozess, den der Kernel dann startet, ist »init« (/sbin/init), der Prozess mit der Prozessidentifikation (PID) 1. »init« wurde bereits in diesem Kapitel erwähnt und ist somit der Elternteil aller noch folgenden Prozesse. »init« kümmert sich beim Start zunächst um die Konfigurationen des Systems und das Ausführen vieler Dämonprozesse.


Hinweis   Hier muss gleich darauf hingewiesen werden, dass ich über »init« keine 100 %ige Aussagen machen lassen. Hier kochen die jeweiligen Distributionen häufig ihr eigenes Süppchen. Die Unterschiede beziehen sich wieder insbesondere auf die Verzeichnisse, wo sich die Init-Dateien befinden, und darauf, welche Konfigurationsdateien hierbei berücksichtigt werden. Natürlich soll das auch heißen, dass die Init-Pakete verschiedener Distributionen gewöhnlich gänzlich inkompatibel sind und nicht untereinander ausgetauscht werden können. Daher folgt hier zunächst ein allgemeiner Überblick über den Init-Prozess (genauer System-V-init).


Nachdem der Kernel »init« gestartet hat, wertet »init« zuallererst die Konfigurationsdatei /etc/inittab aus. Anschließend führt »init« ein Shellscript zur Systeminitialisierung aus. Der Pfad und der Name des Shellscripts ist wieder abhängig von der Distribution. Als Nächstes wird mit rc ein weiteres Shellscript gestartet, das sich wiederum abhängig von der Distribution auf unterschiedlichen Verzeichnissen befinden kann.

rc startet jetzt die einzelnen Shellscripts, die sich in den Verzeichnissen rc[n].d befinden, wobei n hier für den Runlevel steht. Die Skriptdateien (auch Startup-Skripte genannt) in diesem Verzeichnis wiederum starten jetzt die Dämonen (Systemdienste). Der Ort der rc[n].d-Verzeichnisse ist wieder stark abhängig von der Distribution, aber meistens sollten Sie diese unter /etc oder /etc/init.d finden.

Runlevel

»init« verwendet gewöhnlich sieben Runlevels, wobei jeder Runlevel jeweils eine Gruppe von Diensten beinhaltet, die das System beim Starten oder Beenden ausführen soll. Es gibt in /etc/inittab auch einen »default runlevel«, der wiederum von den Distributionen abhängt. Welcher Runlevel jetzt welche Dienste startet, ist von symbolischen Links in den Verzeichnissen rc[n].d abhängig. Diese symbolischen Links verweisen meistens auf die Startup-Skripte der Dienste, die in /etc/init.d abgelegt werden. Hierzu nun ein kurzer Überblick der unterschiedlichen Runlevels und auf deren Bedeutung (bei den gängigsten Distributionen zumindest):


Tabelle 7.13    Runlevels und deren Bedeutung

Runlevel Bedeutung
0 Dieser Runlevel hält das System komplett an (Shutdown mit Halt).
1 oder S Dies stellt den Single-User-Modus (Einzelbenutzermodus) dar.
2 oder M Multi-User-Modus (Mehrbenutzermodus) ohne Netzwerk.
3 Multi-User-Modus (Mehrbenutzermodus) mit einem Netzwerk, aber ohne einen X-Start.
4 Dieser Runlevel wird gewöhnlich nicht verwendet und steht somit zur freien Verfügung dar.
5 Multi-User-Modus (Mehrbenutzermodus) mit einem Netzwerk und einem grafischen Login (X-Start). Nach dem Login wird gewöhnlich die grafische Oberfläche gestartet.
6 Dieser Runlevel startet das System neu (Shutdown mit Reboot).

Bei den Runlevels 0 und 6 handelt es sich um spezielle Fälle, in denen sich ein System nicht lange aufhalten wird. In diesem Runlevel wird das System heruntergefahren bzw. neu gestartet. Generell werden am häufigsten die Runlevels 2 und 3, die Mehrbenutzer-Levels und 5, womit eine X-Anmeldeprozedur (mit z. B. xdm, kdm, gdm etc) gestartet wird, verwendet. Runlevel 1 bzw. S sind häufig unterschiedlich bei den Systemen definiert. Runlevel 4 wird nicht benutzt.

Runlevel 1

Im Runlevel 1 bzw. S, dem Einzelbenutzermodus, werden gewöhnlich alle Mehrbenutzer- und Anmeldeprozesse beendet. Somit geht man sicher, dass das System mit geringem Aufwand ausgeführt wird. Natürlich bietet Runlevel 1 einen vollen root-Zugriff auf das System. Damit ein System in Runlevel 1 auch nach einem root-Passwort abfragt, wurde der Runlevel S entwickelt. Unter System-V-init gibt es keinen echten Runlevel S, es dient daher hierbei nur dazu, eben das root-Passwort abzufragen. Dieser Runlevel ist gewöhnlich dazu gedacht, dass Administratoren diverse Patches einspielen können.

Die Datei /etc/inittab

In /etc/inittab steht, was »init« in den entsprechenden Runlevels zu tun hat, da »init« beim Start des Systems immer von Runlevel 0 bis zum »default runlevel« durchläuft. Damit der Übergang von einem Level zum nächsten reibungslos abläuft, führt »init« die in /etc/inittab angegebenen Aktionen aus. Dasselbe geschieht natürlich auch im umgekehrten Fall, beim Herunterfahren bzw. Neustarten des Systems.

Die Verwendung von inittab alleine macht noch keinen Sommer, weshalb man hierbei noch zusätzliche Schichten in Form eines Skripts für das Wechseln der Runlevels eingebaut hat. Dieses Skript (rc zu finden meistens unter /etc/init.d/rc oder /etc/rc.d/rc) wird gewöhnlich aus inittab aufgerufen. Dieses Skript (rc) führt wiederum weitere Skripte in einem vom Runlevel abhängigen Verzeichnis aus, um das System in seinen neuen (Runlevel-)Zustand zu bringen.

Startup-Skripte erstellen und ausführen

Zuerst müssen Sie suchen, wo sich die Kopien der Startup-Skripte befinden. Meistens sollte dies im Verzeichnis /etc/init.d sein:

$ ls -l /etc/init.d/
insgesamt 308
-rwxr-xr-x  1 root root  2570 2005–03–02 18:45 acpid
-rwxr-xr-x  1 root root  1098 2005–02–24 10:29 acpi-support
-rwxr-xr-x  1 root root 11038 2005–03–25 23:08 alsa
-rwxr-xr-x  1 root root  1015 2004–11–26 12:36 anacron
-rwxr-xr-x  1 root root  1388 2005–03–01 04:11 apmd
-rwxr-xr-x  1 root root  1080 2005–02–18 11:37 atd
-rw-r--r--  1 root root  2805 2005–01–07 19:35 bootclean.sh
-rwxr-xr-x  1 root root  1168 2004–10–29 18:05 cron
...

Jede dieser Dateien in /etc/init.d ist meistens ein Shellscript, das für einen Dämon oder sonstige Konfiguration im System verantwortlich ist. Um zu erfahren, wie diese Skripte arbeiten, rufen Sie doch am besten eines auf, z. B. den cron-Dämon:

# /etc/init.d/cron
 * Usage: /etc/init.d/cron start|stop|restart|reload|force

Hier haben Sie versucht, das Skript »cron«, das für die Ausführung und Beendung des cron-Dämons verantwortlich ist, aufzurufen. Sie bekommen hierbei die möglichen Argumente mit, wie Sie das Skript aufrufen können. Neben »start« und »stop« finden Sie hierbei häufig auch noch »restart«, was im Prinzip dasselbe bewirkt wie ein »stop« mit anschließendem »start«. Des Weiteren findet man gewöhnlich auch eine Option »reload«, die den Dienst nicht beendet, sondern ihn auffordert, seine Konfiguration neu einzulesen (meist per Signal SIGHUP).

Damit das Beispiel nicht allzu schwer wird, werden wir einen einfachen Dämon erstellen, der nichts anderes macht, als sich in einer Endlosschleife alle 15 Sekunden schlafen zu legen. Wie man Dämonen schreibt, wurde ja schon ein Kapitel zuvor demonstriert. Hier das Programm (Dämon), das wir automatisch mit einem Startup-Skript beim Booten des Systems starten wollen und beim Herunterfahren wieder beenden:

/* mydaemon.c */
#include <stdio.h>
#include <unistd. h.>
#include <syslog.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <signal.h>
#include <stdlib.h>
typedef void (*sighandler_t)(int);
static sighandler_t 
handle_signal (int sig_nr, sighandler_t signalhandler) {
   struct sigaction neu_sig, alt_sig;
   neu_sig.sa_handler = signalhandler;
   sigemptyset (&neu_sig.sa_mask);
   neu_sig.sa_flags = SA_RESTART;
   if (sigaction (sig_nr, &neu_sig, &alt_sig) < 0)
      return SIG_ERR;
   return alt_sig.sa_handler;
}
static void start_daemon (const char *log_name,
                          int facility) {
   int i;
   pid_t pid;
   /* Elternprozess beenden, somit haben wir einen Waisen */
   /* dessen sich jetzt vorerst init annimmt              */
   if ((pid = fork ()) != 0)
      exit (EXIT_FAILURE);
  /* Unser Kindprozess wird zum Sessionführer. Misslingt */
  /* dies, kann der Fehler daran liegen, dass der Prozess   */
  /* bereits Sessionführer ist                             */
   if (setsid() < 0) {
      printf("%s kann nicht Sessionführer werden!\n", 
                log_name);
      exit (EXIT_FAILURE);
   }
   /* Signal SIGHUP ignorieren */
   handle_signal (SIGHUP, SIG_IGN);
   /* Oder einfach: signal(SIGHUP, SIG_IGN) ... */
   /* Das Kind terminieren */
   if ((pid = fork ()) != 0)
      exit (EXIT_FAILURE);
   /* Gründe für das Arbeitsverzeichnis:
    * + core-Datei wird im aktuellen Arbeitsverzeichnis 
    *   hinterlegt.
    * + Damit bei Beendigung mit umount das Dateisystem 
    *   sicher abgehängt werden kann 
    */
   chdir ("/");
   /* Damit wir nicht die Bitmaske vom Elternprozess     */
   /* erben bzw. diese bleibt, stellen wir diese auf 0  */
   umask (0);
   /*Wir schließen alle geöffneten Filedeskriptoren.... */
   for (i = sysconf (_SC_OPEN_MAX); i > 0; i--)
      close (i);
   /* Da unser Dämonprozess selbst kein Terminal für */
   /* die Ausgabe hat....                            */
   openlog (log_name, LOG_PID | LOG_CONS| LOG_NDELAY,
            facility);
}
int main (int argc, char **argv) {
   int time   = 15;
   start_daemon ("mydaemon", LOG_LOCAL0);
   while (1) {
      /* Enlosschleifen: Hier sollte nun der Code für den
       * Daemon stehen, was immer er auch tun soll. */
      sleep(time);
   }
   syslog( LOG_NOTICE, "Daemon hat sich beendet\n");
   closelog();
   return EXIT_SUCCESS;
}

Das Programm übersetzen:

$ gcc -o mydaemon mydaemon.c

Natürlich muss es sich hierbei nicht zwangsläufig um ein binäres Programm handeln, Sie können natürlich auch Shell- oder Perl-Skripte mit den Startup-Skripten starten. Als Nächstes sollten Sie das Programm »mydaemon« nach /usr/sbin (oder auch /usr/local/sbin) kopieren. Für diese Aktion benötigen Sie wohl die Superuser-root-Rechte.

# cp mydaemon /usr/sbin
# ls -l /usr/sbin/my*
-rwxr-xr-x  1 root root 12992 /usr/sbin/mydaemon

Jetzt folgt das Startup-Skript, womit Sie diesen Dämon »mydaemon« beim Booten starten und beim Shutdown wieder beenden können. Zwar handelt es sich um ein Shellscript, aber im Prinzip müssen Sie nur die Variable DAEMON anpassen (mit entsprechendem Pfad und Namen). Sofern Sie weitere Optionen wie hier mit »start«, »stop« und »restart« benötigen, müssen Sie sich einfach nur den Quellcode von anderen Startup-Skripten ansehen (die im Prinzip immer ähnlich aufgebaut sind).


Hinweis   Ist Ihr Interesse an der Shellscript-Programmierung geweckt? Auch hierzu finden Sie beim selben Verlag ein umfangreiches Werk aus meiner Feder ;-)


Folgendermaßen sieht nun das Startup-Skript (»start_i_daemon«) aus, womit Sie »mydaemon« starten, beenden und neu starten können:

#!/bin/sh
# Name : start_i_daemon
DAEMON="/usr/sbin/mydaemon"
test -f $DAEMON || exit 0
case "$1" in
   start)
        echo -n "Starte mydaemon"
        $DAEMON
        echo "....[DONE]"
        ;;
    stop)
        echo -n "Stoppe mydaemon"
        killall mydaemon
        echo "....[DONE]"
        ;;
    restart)
        $0 stop
        $0 start
        echo "....[DONE]"
        ;;
    *)
        echo "Usage: $0 {start|stop|restart}"
        exit 1
        ;;
esac

Dieses Startup-Skript »start_i_daemon« sollten Sie jetzt in das Verzeichnis /etc/init.d verschieben und ausführbar machen.

# mv start_i_daemon /etc/init.d
# chmod u+x /etc/init.d/start_i_daemon

Theoretisch können Sie diesen Dienst jetzt auch schon von Hand starten und wieder beenden.

# /etc/init.d/start_i_daemon
Usage: /etc/init.d/start_i_daemon {start|stop|restart}
# /etc/init.d/start_i_daemon start
Starte mydaemon....[DONE]
# /etc/init.d/start_i_daemon restart
Stoppe mydaemon....[DONE]
Starte mydaemon....[DONE]
# /etc/init.d/start_i_daemon stop
Stoppe mydaemon....[DONE]

Damit das Ganze aber jetzt auch automatisch beim Booten bzw. Beenden des Systems in einem bestimmten Runlevel passiert, benötigt das von »init« gestartete Master-Kontrollskript (rc) weitere Informationen, welche Prozesse ausgeführt werden sollen. Das Master-Kontrollskript sieht hierzu in einem der Verzeichnisse rc[n].d (z. B. rc1.d; rc2.d ... rc6.d) nach, wann bei welchem Runlevel ein Prozess gestartet wird. n steht hier wieder für den entsprechenden Runlevel. Ein Blick auf mein System (Ubuntu Linux) im Verzeichnis rc2.d (bei mir der »default runlevel«) sieht wie folgt aus:

# ls -l /etc/rc2.d/
lrwxrwxrwx  1 root root 17 K11anacron -> ../init.d/anacron
lrwxrwxrwx  1 root root 17 S05vbesave -> ../init.d/vbesave
lrwxrwxrwx  1 root root 18 S10sysklogd -> ../init.d/sysklogd
lrwxrwxrwx  1 root root 15 S11klogd -> ../init.d/klogd
lrwxrwxrwx  1 root root 14 S12alsa -> ../init.d/alsa
lrwxrwxrwx  1 root root 13 S14ppp -> ../init.d/ppp
lrwxrwxrwx  1 root root 16 S19cupsys -> ../init.d/cupsys
lrwxrwxrwx  1 root root 15 S20acpid -> ../init.d/acpid
lrwxrwxrwx  1 root root 14 S20apmd -> ../init.d/apmd
...

Hierbei fällt gleich auf, dass alle Einträge hier symbolische Links auf die Startup-Skripte, die im init.d-Verzeichnis liegen, sind. Dies sieht in den anderen Runlevels genauso aus. Bei allen symbolischen Links fällt außerdem der Anfangsbuchstabe »S« oder »K« gefolgt von einer Nummer und dem eigentlichen Dienst auf (z. B. K11anacron). Die Bedeutung ist hierbei folgende: Steigt »init« einen Runlevel höher, werden alle Startup-Skripte, die mit »S« beginnen, ausgeführt, also mit dem Argument »start«. Genauso sieht dies in der umgekehrten Reihenfolge aus, wenn »init« von einem höheren Runlevel in den nächst niedrigeren wechselt, werden alle Startup-Skripte ausgeführt, die mit »K« beginnen. Gewöhnlich bedeutet dies, dass die Skripte mit dem Argument »stop« ausgeführt werden. Die Nummern haben eine Art Prioritätsbedeutung. Die Skripte im Runlevel-Verzeichnis werden bei der Option »start« in alphabetischer Reihenfolge ausgeführt und bei »stop« in umgekehrter Reihenfolge. Somit bedient man sich einer Nummerierung, um die Reihenfolge der Ausführung zu beeinflussen.

In diesem Verzeichnis (hier z. B. /etc/rc2.d = Runlevel 2) wollen wir nun auch unser Startup-Skript einfügen, das unseren Dämon »mydaemon« (beim Booten) starten soll. Hierzu müssen Sie ebenfalls einen symbolischen Link in das entsprechende Verzeichnis setzen (hier nach /etc/init.d/). Weitere symbolische Links werden Sie auch im Runlevel 0 (/etc/rc0.d) und 6 (/etc/rc6.d) eintragen müssen. Da viele Systeme zwischen einem Shutdown und einem Reboot unterscheiden, sollte man immer auch einen Eintrag in Runlevel 6 machen, um sicherzugehen, dass sich der Dämon beim Neustart auch beendet hat. Hierzu nun die Einträge (symbolischen Links) für die /etc/rc[n].d-Verzeichnisse:

# ln -s /etc/init.d/start_i_daemon \
> /etc/rc2.d/S23start_i_daemon
# ln -s /etc/init.d/start_i_daemon \
> /etc/rc0.d/k23start_i_daemon
# ln -s /etc/init.d/start_i_daemon \
> /etc/rc6.d/k23start_i_daemon

Mit der ersten Zeile weisen Sie das System nun an, dass das Startup-Skript /etc/init.d/start_i_daemon den Dämon »mydaemon« mit dem Argument »start« ausführen wird, wenn es im Runlevel 2 angelangt ist. Mit der zweiten Zeile legen Sie fest, dass mit /etc/init.d/start_i_daemon der Dämon »mydaemon« beim Herunterfahren des Systems mit dem Argument »stop« beendet wird, wenn es in das Runlevel 0 gekommen ist. Selbiges machen Sie auch mit der dritten Zeile, nur eben für den Neustart des Systems im Runlevel 6.

Wenn Sie jetzt das System beenden und wieder hochfahren, können Sie in der Startup-Sitzung (sofern diese sichtbar ist) Ihren Dienst beim Starten beobachten. Hier werden Sie irgendwann eine Zeile wie

Starte mydaemon....[DONE]

wiederfinden. Beim Beenden bzw. Neustarten des Systems ergibt sich selbige Meldung, nur dass eben »mydaemon« beendet wurde. Ein Blick mit dem Kommando pstree zeigt auch, dass »mydaemon« von »init« gestartet wurde, also »init« der Elternprozess davon ist.


Rheinwerk Computing

7.13.2 Hintergrundprozesse und Jobkontrolle  downtop

Wollen Sie einen Prozess im Hintergrund setzen, müssen Sie dem Kommando oder Programm am Ende das Amperesand-Zeichen (&) hinzufügen, z. B.:

$ sleep 10 &
[1] 7538

In diesem Beispiel haben Sie das Kommando sleep mit dem Argument 10 aufgerufen und im Hintergrund ausgeführt. Würden Sie dieses Beispiel ohne das Amperesand-Zeichen ausführen, also normal im »Vordergrund«, müssten Sie hier zehn Sekunden oder überhaupt immer auf das Ende des Prozesses warten, bis Ihnen der Eingabeprompt der Shell wieder zur Verfügung steht. Dadurch dass Sie einen Prozess im Hintergrund ausführen, können Sie ohne weiteres weitere Prozesse »parallel« starten.

Jobverwaltung

Damit Sie Prozesse, die Sie in den Hintergrund gestellt haben, auch noch kontrollieren, gibt es eine Jobverwaltung (Job-Control-System). Damit können Sie z. B. Prozesse, die Sie in den Hintergrund gestellt haben, wieder nach »vorne« holen und ggf. wieder in den Hintergrund schicken. Dies kann z. B. sinnvoll sein, wenn ein Prozess die kompletten Systemressourcen (besonders die CPU) aufbraucht oder etwa eine Eingabe vom Benutzer erwartet. Wenn Sie einen Hintergrundprozess starten, wird eine Nummer zwischen eckigen Klammern und die Prozessidentifikationsnummer angezeigt. Bei der Nummer zwischen den eckigen Klammern handelt es sich um die Jobnummer.

$ sleep 10 &
[1] 8204
$ sleep 10 &
[2] 8205

Hier haben Sie zweimal das Kommando sleep im Hintergrund gestartet. Der Prozess mit der Nummer 8204 hat dabei die Jobnummer 1 und 8205 die Jobnummer 2. Damit Sie sich einen Überblick zu allen sich im Hintergrund befindlichen Prozessen verschaffen können, wird das Kommando jobs verwendet:

$ jobs
[1]-  Running                 sleep 10 &
[2]+  Running                 sleep 10 &

Hier können Sie an »Running« erkennen, dass beide Prozesse im Hintergrund noch aktiv sind. Eine halbe Minute später sieht dies schon ganz anders aus:

$ jobs
[1]-  Done                    sleep 10
[2]+  Done                    sleep 10

jobs sagt uns hier mit »Done«, dass die beiden ehemaligen Hintergrundprozesse ihre Arbeit verrichtet haben. Jetzt wollen wir nochmals sleep aufrufen und dann aber mit der Tastenkombination (STRG)+(Z) für Stopp (Signal SIGTSTP) den Prozess anhalten.

$ sleep 10
STRG+Z
[1]+  Stopped                 sleep 10

Ein Blick in die Jobverwaltung zeigt uns mit »Stopped«, dass der Prozess angehalten wurde:

$ jobs
[1]+  Stopped                 sleep 10

Häufig wird ein Prozess gestoppt, wenn die Ausführung verdächtig lange dauert oder der Prozess den Rechner erheblich ausbremst. Auch ein Blick in der Prozessliste zeigt uns den gestoppten Prozess anhand des »T« (für Traced):

$ ps -l | grep sleep
0 T  1000 8461 7042 0 76  0–831 finish pts/2 00:00:00 sleep

Um dem gestoppten Prozess doch noch sein »Happy End« zu gönnen, stehen Ihnen mehrere Möglichkeiten zur Verfügung. Sie senden dem gestoppten Prozess das Signal SIGCONT, womit dieser seine Ausführung, wenn möglich, fortsetzt. Hierbei benötigen Sie nicht zwangsläufig die PID des Prozesses, sondern können auch das Prozentzeichen, gefolgt von der Jobnummer, verwenden. In unserem Beispiel wäre dies:

$ kill -SIGCONT %1
$ jobs
[1]+  Done                    sleep 10

Hiermit haben Sie den angehaltenen Job mit der Nummer 1 wieder fortgesetzt. Wollen Sie den angehaltenen Prozess hingegen nicht mehr fortsetzen, sondern komplett beenden, dann müssen Sie nur stattdessen die Signale SIGCONT, SIGTERM oder, wenn es damit nicht mehr klappt, SIGKILL senden.

Wieder zurück in die Vergangenheit, der Prozess sleep befindet sich wieder im gestoppten Zustand. Die nächste Möglichkeit, den Prozess weiter auszuführen, wäre es, diesen mit dem Kommando bg (für background) und der entsprechenden Jobnummer (mit vorangestelltem Prozentzeichen) wieder in den Hintergrund zu schicken, wo dieser weiter ausgeführt wird.

$ bg %1
[1]+ sleep 10 &

Geben Sie bg ohne das Prozentzeichen an, wird der einzige Job, der möglich ist, in den Hintergrund geschickt. Verwenden Sie das Prozentzeichen ohne Jobnummer, bezieht sich die Angabe immer auf den aktuellen Job (current job).

Jetzt nochmals zurück in die Vergangenheit, und sleep ist wieder angehalten. Eine weitere Möglichkeit, den Prozess weiter auszuführen, wäre es, das Kommando fg (für foreground) zu verwenden. Die Verwendung von fg entspricht exakt der von bg. Auch hierbei gibt man nach dem Prozentzeichen die entsprechende Jobnummer an von dem Prozess, von dem man will, dass dieser im Vordergrund weiter ausgeführt wird. fg entspricht demselben Anwendungsfall wie schon die Verwendung des Signals SIGCONT.

$ fg %1
sleep 10

Hier nochmals ein Überblick der Funktionen der Jobverwaltung:


Tabelle 7.14    Kommandos zur Jobverwaltung

Jobbefehl Bedeutung
fg %jobnr Holt sich einen Job in den Vordergrund, wo dieser weiterläuft.
bg %jobnr Schiebt einen angehaltenen Job in den Hintergrund, wo dieser weiterläuft.
(CTRL)+(Z) Hält einen sich im Vordergrund aufhaltenden Prozess auf (suspendiert diesen).
kill %jobnr Beendet einen Prozess.
kill -SIGCONT %jobnr Setzt die Ausführung eines angehaltenen Prozesses fort (egal ob im Hinter- bzw. Vordergrund).

Mit dem folgenden Beispiel soll die Anwendung von fg etwas deutlicher demonstriert werden:

/* atest.c */
#include <stdio.h>
#include <stdlib.h>
int main(void) {
   int wert;
   printf("Bitte Wert eingeben: ");
   scanf("%d", &wert);
   printf("Der Wert lautet: %d\n", wert);
   return EXIT_SUCCESS;
}

Das Programm bei der Ausführung (im Hintergrund):

$ gcc -o atest atest.c
$ ./atest &
Bitte Wert eingeben:
[1] 10240
[1]+  Stopped                 ./atest
$ jobs
[1]+  Stopped                 ./atest
$ fg %1
./atest
10 ENTER
Der Wert lautet: 10
$

Hier hat sich das im Hintergrund gestartete Programm »atest« von selbst angehalten, weil sich darin eine Eingabeaufforderung für den Benutzer befand. Anschließend wurde das Programm mit fg in den Vordergrund geholt, um dieser Eingabeaufforderung nachzukommen, und das Programm wurde bis zum Ende fortgesetzt.


Hinweis   Zwar werden wohl die meisten mit der Korn-Shell (ksh) oder der Bourne-again-Shell (bash) arbeiten, aber sofern Sie mit der Ur-Shell, der Bourne-Shell (sh), arbeiten, sollte gesagt sein, dass die »echte« Bourne-Shell keine Jobkontrolle bietet. Unter Linux ist ein Aufruf der Bourne-Shell nichts anderes als ein symbolischer Link zur Bash.



Rheinwerk Computing

7.13.3 Prozesse zeitgesteuert ausführen (cron-Jobs)  toptop

Häufiger werden Sie die Programme, die Sie schreiben, auch zeitgesteuert ausführen wollen und nicht immer von Hand. Für solche Aufgaben wurde der cron-Dämon geschaffen. Hierbei definieren Sie einmal eine Aufgabe und delegieren diese dann an den cron-Dämon. Dies wird mit einem Eintrag in der crontab-Datei gemacht. Da es besonders bei Heimrechnern nicht immer möglich ist, dass ein entsprechender cron-Job ausgeführt wird (hierzu muss der PC immer an sein), gibt es auch noch einen Dämon namens »anacron«, der versäumte cron-Jobs nachholt.

Einmal gestartet (gewöhnlich von »init« mit einem Startup-Skript) liest der cron-Dämon standardmäßig jede Minute die crontab-Datei ein und schaut, ob ein Job für ihn vorhanden ist. Bevor Sie erfahren, wie Sie Ihr Programm zu einer bestimmten Zeit starten können, hierzu noch einige Anmerkungen. Bedenken Sie bitte, wenn Sie einen Prozess über cron starten, dass keine Verbindung mehr zum Bildschirm und zu der Tastatur besteht. Sie müssen sich vor Augen halten, dass eine Benutzereingabeaufforderung sowie eine Ausgabe auf dem Bildschirm recht sinnlos in Ihrem Programm sind. In beiden Fällen sollten Sie die Ein- bzw. Ausgabe(n) umleiten. In der Praxis lässt man sich häufig die Ausgabe per E-Mail an den Eigentümer der crontab senden.

Die Einträge der crontab-Datei vom Superuser root befinden sich meistens im /etc-Verzeichnis. Die crontab-Dateien für den Benutzer sollten sich im Verzeichnis /var/spool/cron befinden. Zum Bearbeiten der crontab-Datei genügt gewöhnlich ein einfaches crontab mit der Option -e (für edit). Hiermit wird crontab mit dem Standardeditor (meistens vi) gestartet. Beim ersten Aufruf (als normaler Nutzer) wird die crontab-Datei leer sein.

Einen Eintrag in die crontab-Datei macht man mit der folgenden Syntax:


Tabelle 7.15    Ein Eintrag in die crontab-Datei

Minuten Stunden Tage Monate Wochentage [User] Befehl
0-59 0-23 1-31 1-12 0-7 Name programm

Praktisch besteht jede einzelne Zeile aus sechs (bzw. sieben) Spalten, worin ein Auftrag festgelegt wird. Jede Spalte wird durch ein Leerzeichen bzw. ein Tabulator-Zeichen voneinander getrennt. In der ersten Spalte werden hierbei die Minuten (0-59), in der zweiten die Stunden (0-23), in der dritten die Tage (1-31), in der vierten die Monate (1-12), in der fünften die Wochentage (0-7; 0 und 7 stehen hierbei für Sonntag) und in der sechsten Spalte das Kommando bzw. Skript angegeben, das zum gegebenen Zeitpunkt ausgeführt werden soll. In der Syntax finden Sie hierbei noch eine Spalte User (die sechste), die allerdings beim normalen User entfällt. Diese Spalte ist dem root vorbehalten, worin dieser einen cron-Job für bestimmte User einrichten kann. Somit besteht eine Zeile (Auftrag) einer crontab-Datei für den normalen Benutzer aus sechs und für den root aus sieben Spalten.

Natürlich können Sie auch Kommentare bei der crontab-Datei hinzufügen. Diese werden mit dem für das Shellscript üblichen # eingeleitet. Sie können bei den Zeitangaben anstatt Zahlen auch einen Stern (*) verwenden, was für »Erster und Letzter« steht – also »immer«. Somit würde folgender Eintrag das Programm »meinProgramm« jede Minute ausführen:

# Führt das Programm jede Minute aus
* * * * * $HOME/meinProgramm

Es sind auch Zahlenbereiche erlaubt, z. B. ein Bereich wird mit einem Bindestrich von einem anderen getrennt. Die Zahlen zwischen den Bereichen sind immer inklusive. Somit bedeutet folgender Eintrag, dass das Programm »meinProgramm« jeden Tag um 10, 11, 12, 13 und 14 Uhr ausgeführt wird:

0 10–14 * * * $HOME/meinProgramm

Es können aber auch Listen verwendet werden, womit Nummern oder auch ein Zahlenbereich von Nummern durch Kommata getrennt werden:

0 10–12,16,20–23 * * * $HOME/meinProgramm

Hier würde jeden Tag um 10, 11, 12, 16, 20, 21, 22 und 23 Uhr das Programm »meinProgramm« ausgeführt. Hierbei muss darauf geachtet werden, dass keine Leerzeichen in der Liste vorkommen dürfen, da das Leerzeichen ja das Trennzeichen für den nächsten Eintrag ist.

Es sind außerdem Aufträge in bestimmten Schritten möglich. Somit könnten Sie anstatt der Angabe, alle vier Stunden ein bestimmtes Programm auszuführen, mit

0 0,4,8,12,16,20 * * * $HOME/meinProgramm

dies mit 0–23/4 oder */4 verkürzen:

0 */4 * * * $HOME/meinProgramm

Hierzu einige Beispiele zum besseren Verständnis:

# Jeden Tag um 11 Uhr das Skript meinProgramm ausführen
0 11 * * * $HOME/meinProgramm
# Jeden Dienstag und Freitag um 23 Uhr das 
# Programm meinProgramm ausführen
0 23 * * 2,5 $HOME/meinProgramm
# Jeden zweiten Tag das Programm meinProgramm 
# um 23 Uhr ausführen
0 23 * * 0–6/2 $HOME/meinProgramm
# Achtung: Das Programm wird jeden 15. eines 
# Monats UND jeden Samstag um 23 Uhr ausgeführt
0 23 15 * 6 $HOME/meinProgramm

Gewöhnlich wird Ihre crontab-Datei auf Dauer immer umfangreicher. Sofern Sie crontab zur Fernwartung einsetzen, können Sie sich die Ausgabe Ihres Skripts per E-Mail zuschicken lassen. Hierzu müssen Sie die Variable MAILTO (per Standard meist auf user@host gesetzt) in crontab mit Ihrer E-Mail-Adresse versehen. Mit einem leeren MAILTO (»«) können Sie dies wieder abschalten. Hierzu soll eine einfache crontab-Datei erstellt werden:

$ crontab -e
[ ---vi startet--- ]
# Alle zwei Minuten die Ausgabe von meinProgramm 
# an testdatei hängen
*/2 * * * * $HOME/meinProgramm >> $HOME/testdatei
[ --- Abspeichern und Quit (:wq)--- ]
crontab: installing new crontab
$

Weitere Optionen zu crontab finden Sie in der folgenden Tabelle:


Tabelle 7.16    Optionen für crontab und deren Bedeutung

Aufruf Bedeutung
crontab -e Die crontab-Datei editieren
crontab -l Alle cron-Jobs auflisten
crontab -r Alle cron-Jobs löschen


Hinweis   Bitte beachten Sie, dass es drei Versionen von cron-Dämonen gibt (BSD, SYSV und Linux), so dass es hier und da zu einigen Differenzen kommen kann. Die Manual Page veschafft Ihnen die absolute Sicherheit.


 << zurück
  
  Zum Rheinwerk-Shop
Neuauflage: Linux-UNIX-Programmierung
Neuauflage:
Linux-UNIX-
Programmierung

bestellen
 Ihre Meinung?
Wie hat Ihnen das Openbook gefallen?
Ihre Meinung

 Buchtipps
Zum Rheinwerk-Shop: Linux-Server






 Linux-Server


Zum Rheinwerk-Shop: Das Komplettpaket LPIC-1 & LPIC-2






 Das Komplettpaket
 LPIC-1 & LPIC-2


Zum Rheinwerk-Shop: Linux-Hochverfügbarkeit






 Linux-
 Hochverfügbarkeit


Zum Rheinwerk-Shop: Shell-Programmierung






 Shell-
 Programmierung


Zum Rheinwerk-Shop: Linux Handbuch






 Linux Handbuch


 Lieferung
Versandkostenfrei bestellen in Deutschland, Österreich und der Schweiz
Info





Copyright © Rheinwerk Verlag GmbH 2006
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das Openbook denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt.
Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


Nutzungsbestimmungen | Datenschutz | Impressum

Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de

Cookie-Einstellungen ändern