man 7 daemon
beschreibt ausführlich, wie man einen Daemon erstellt. Meine Antwort ist nur ein Auszug aus diesem Handbuch.
Es gibt mindestens zwei Arten von Dämonen:
- herkömmliche SysV-Daemons (alter Stil),
- systemd-Daemons (neuer Stil).
SysV-Daemons
Wenn Sie am traditionellen SysV-Daemon interessiert sind, sollten Sie die folgenden Schritte ausführen:
- Schließen Sie alle geöffneten Dateideskriptoren außer der Standard-Eingabe , Ausgabe , und Fehler (d. h. die ersten drei Dateideskriptoren 0, 1, 2). Dadurch wird sichergestellt, dass kein versehentlich übergebener Dateideskriptor im Daemon-Prozess verbleibt. Unter Linux wird dies am besten durch Iteration durch
/proc/self/fd
implementiert , mit einem Fallback der Iteration von Dateideskriptor 3 zu dem Wert, der vongetrlimit()
zurückgegeben wird fürRLIMIT_NOFILE
. - Setzen Sie alle Signal-Handler auf ihre Standardeinstellungen zurück. Dies geschieht am besten, indem die verfügbaren Signale bis zur Grenze von
_NSIG
durchlaufen werden und setzen sie aufSIG_DFL
zurück . - Setzen Sie die Signalmaske mit
sigprocmask()
zurück . - Bereinigen Sie den Umgebungsblock, indem Sie Umgebungsvariablen entfernen oder zurücksetzen, die sich negativ auf die Daemon-Laufzeit auswirken könnten.
- Ruf
fork()
an , um einen Hintergrundprozess zu erstellen. - Ruf im Kind
setsid()
von jedem Terminal zu trennen und eine unabhängige Sitzung zu erstellen. - Rufen Sie im untergeordneten Element
fork()
auf erneut, um sicherzustellen, dass der Daemon nie wieder ein Terminal wiedererlangen kann. - Rufen Sie
exit()
an im ersten Kind, so dass nur das zweite Kind (der eigentliche Daemon-Prozess) übrig bleibt. Dies stellt sicher, dass der Daemon-Prozess wieder init/PID 1 untergeordnet wird, wie es bei allen Daemons der Fall sein sollte. - Verbinden Sie im Daemon-Prozess
/dev/null
zur Standard-Eingabe , Ausgabe , und Fehler . - Setzen Sie im Daemon-Prozess den
umask
zurück auf 0, damit die Dateimodi anopen()
übergeben werden ,mkdir()
und dergleichen steuern direkt den Zugriffsmodus der erstellten Dateien und Verzeichnisse. - Ändern Sie im Daemon-Prozess das aktuelle Verzeichnis in das Stammverzeichnis (
/
), um zu vermeiden, dass der Daemon ungewollt das Aushängen von Einhängepunkten blockiert. - Schreiben Sie im Daemon-Prozess die Daemon-PID (wie von
getpid()
zurückgegeben ) in eine PID-Datei, zum Beispiel/run/foobar.pid
(für einen hypothetischen Daemon "foobar"), um sicherzustellen, dass der Daemon nicht mehr als einmal gestartet werden kann. Dies muss race-free implementiert werden, damit die PID-Datei nur dann aktualisiert wird, wenn gleichzeitig verifiziert wird, dass die zuvor in der PID-Datei gespeicherte PID nicht mehr existiert oder zu einem fremden Prozess gehört. - Löschen Sie im Daemon-Prozess die Berechtigungen, falls möglich und zutreffend.
- Benachrichtigen Sie vom Daemon-Prozess aus den ursprünglich gestarteten Prozess, dass die Initialisierung abgeschlossen ist. Dies kann über eine unbenannte Pipe oder einen ähnlichen Kommunikationskanal implementiert werden, der vor dem ersten
fork()
erstellt wird und daher sowohl im Original- als auch im Daemon-Prozess verfügbar. - Rufen Sie
exit()
an im Originalprozess. Der Prozess, der den Daemon aufgerufen hat, muss sich darauf verlassen könnenexit()
geschieht nach Die Initialisierung ist abgeschlossen und alle externen Kommunikationskanäle sind eingerichtet und zugänglich.
Beachten Sie diese Warnung:
Das BSD daemon()
Funktion sollte nicht verwendet werden, da es nur eine Teilmenge implementiert dieser Schritte.
Ein Daemon, der Kompatibilität bereitstellen muss mit SysV-Systemen sollte das oben aufgezeigte Schema implementieren. Es wird jedoch empfohlen, dieses Verhalten optional und über ein Befehlszeilenargument konfigurierbar zu machen, um das Debuggen zu erleichtern und die Integration in Systeme zu vereinfachen, die systemd verwenden.
Beachten Sie, dass daemon()
ist nicht POSIX-kompatibel.
Dämonen neuen Stils
Für Daemons neuen Stils werden die folgenden Schritte empfohlen:
- Falls
SIGTERM
empfangen wird, fahre den Daemon herunter und beende ihn sauber. - Falls
SIGHUP
empfangen wird, laden Sie ggf. die Konfigurationsdateien neu. - Geben Sie einen korrekten Exit-Code aus dem Haupt-Daemon-Prozess an, da dieser vom Init-System verwendet wird, um Dienstfehler und -probleme zu erkennen. Es wird empfohlen, dem Exit-Code-Schema zu folgen, wie es in den LSB-Empfehlungen für SysV-Init-Skripte definiert ist.
- Wenn möglich und anwendbar, stellen Sie die Steuerschnittstelle des Daemons über das D-Bus-IPC-System bereit und holen Sie sich als letzten Schritt der Initialisierung einen Busnamen.
- Stellen Sie für die Integration in systemd eine .service-Unit-Datei bereit, die Informationen zum Starten, Stoppen und anderweitigen Warten des Daemon enthält. Siehe
systemd.service(5)
für Details. - Verlassen Sie sich so weit wie möglich auf die Funktionalität des Init-Systems, um den Zugriff des Daemons auf Dateien, Dienste und andere Ressourcen zu beschränken, d. h. im Fall von systemd, verlassen Sie sich auf die Ressourcenbegrenzungskontrolle von systemd, anstatt Ihre eigene zu implementieren systemd’s Privilege Droping Code, anstatt ihn im Daemon zu implementieren, und ähnliches. Siehe
systemd.exec(5)
für die verfügbaren Steuerelemente. - Wenn D-Bus verwendet wird, machen Sie Ihren Daemon bus-aktivierbar, indem Sie eine Konfigurationsdatei für die Aktivierung des D-Bus-Dienstes bereitstellen. Dies hat mehrere Vorteile:Ihr Daemon kann bei Bedarf träge gestartet werden; es kann parallel zu anderen Daemons gestartet werden, die es benötigen – was die Parallelisierung und Boot-Up-Geschwindigkeit maximiert; Ihr Daemon kann bei einem Fehler neu gestartet werden, ohne dass Busanforderungen verloren gehen, da der Bus Anforderungen für aktivierbare Dienste in eine Warteschlange stellt. Einzelheiten siehe unten.
- Wenn Ihr Daemon über einen Socket Dienste für andere lokale Prozesse oder entfernte Clients bereitstellt, sollte er gemäß dem unten aufgeführten Schema für Sockets aktivierbar gemacht werden. Wie die D-Bus-Aktivierung ermöglicht dies das Starten von Diensten bei Bedarf sowie eine verbesserte Parallelisierung des Dienststarts. Bei zustandslosen Protokollen (z. B. Syslog, DNS) kann ein Daemon, der eine Socket-basierte Aktivierung implementiert, neu gestartet werden, ohne dass eine einzige Anforderung verloren geht. Einzelheiten siehe unten.
- Falls zutreffend, sollte ein Daemon das Init-System über den Abschluss des Starts oder Statusaktualisierungen über den
sd_notify(3)
benachrichtigen Schnittstelle. - Anstatt den
syslog()
zu verwenden aufrufen, um sich direkt beim Syslog-Dienst des Systems anzumelden, kann sich ein Daemon neuen Stils dafür entscheiden, sich einfach überfprintf()
bei der Standardfehlermeldung anzumelden , die dann vom Init-System an Syslog weitergeleitet wird. Wenn Protokollebenen erforderlich sind, können diese codiert werden, indem einzelnen Protokollzeilen Zeichenfolgen wie "<4>" (für Protokollebene 4 "WARNING" im Syslog-Prioritätsschema) vorangestellt werden, wobei ein ähnlicher Stil wie beimprintk()
Ebenensystem. Einzelheiten finden Sie untersd-daemon(3)
undsystemd.exec(5)
.
Um mehr zu erfahren, lesen Sie ganz man 7 daemon
.
In Linux möchte ich einen Daemon hinzufügen, der nicht gestoppt werden kann und der Dateisystemänderungen überwacht. Wenn irgendwelche Änderungen entdeckt werden, sollte es den Pfad zu der Konsole schreiben, wo es gestartet wurde + einen Zeilenumbruch.
Daemons arbeiten im Hintergrund und gehören (normalerweise...) keinem TTY an, weshalb Sie stdout/stderr nicht so verwenden können, wie Sie es wahrscheinlich möchten. Normalerweise ein Syslog-Daemon (syslogd ) wird zum Protokollieren von Meldungen an Dateien (Debug, Fehler, ...) verwendet.
Außerdem gibt es ein paar erforderliche Schritte um einen Prozess zu dämonisieren.
Wenn ich mich richtig erinnere, sind diese Schritte:
- Gabelung schalten Sie den Elternprozess aus und lassen Sie ihn terminieren, wenn das Forking erfolgreich war. -> Da der Elternprozess beendet wurde, läuft der Kindprozess nun im Hintergrund.
- setsid - Erstellen Sie eine neue Sitzung. Der aufrufende Prozess wird zum Leiter der neuen Sitzung und zum Prozessgruppenleiter der neuen Prozessgruppe. Der Prozess ist nun von seinem steuernden Terminal (CTTY) getrennt.
- Signale abfangen - Signale ignorieren und/oder verarbeiten.
- Fork erneut &Lassen Sie den übergeordneten Prozess terminieren, um sicherzustellen, dass Sie den sitzungsführenden Prozess loswerden. (Nur Sitzungsleiter können erneut ein TTY erhalten.)
- chdir - Ändern Sie das Arbeitsverzeichnis des Daemons.
- umask - Ändern Sie die Dateimodusmaske entsprechend den Anforderungen des Daemons.
- schließen - Schließen Sie alle offenen Dateideskriptoren, die möglicherweise vom übergeordneten Prozess geerbt werden.
Um Ihnen einen Ausgangspunkt zu geben:Schauen Sie sich diesen Skelettcode an, der die grundlegenden Schritte zeigt. Dieser Code kann jetzt auch auf GitHub gegabelt werden:Grundgerüst eines Linux-Daemons
/*
* daemonize.c
* This example daemonizes a process, writes a few log messages,
* sleeps 20 seconds and terminates afterwards.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <syslog.h>
static void skeleton_daemon()
{
pid_t pid;
/* Fork off the parent process */
pid = fork();
/* An error occurred */
if (pid < 0)
exit(EXIT_FAILURE);
/* Success: Let the parent terminate */
if (pid > 0)
exit(EXIT_SUCCESS);
/* On success: The child process becomes session leader */
if (setsid() < 0)
exit(EXIT_FAILURE);
/* Catch, ignore and handle signals */
//TODO: Implement a working signal handler */
signal(SIGCHLD, SIG_IGN);
signal(SIGHUP, SIG_IGN);
/* Fork off for the second time*/
pid = fork();
/* An error occurred */
if (pid < 0)
exit(EXIT_FAILURE);
/* Success: Let the parent terminate */
if (pid > 0)
exit(EXIT_SUCCESS);
/* Set new file permissions */
umask(0);
/* Change the working directory to the root directory */
/* or another appropriated directory */
chdir("/");
/* Close all open file descriptors */
int x;
for (x = sysconf(_SC_OPEN_MAX); x>=0; x--)
{
close (x);
}
/* Open the log file */
openlog ("firstdaemon", LOG_PID, LOG_DAEMON);
}
int main()
{
skeleton_daemon();
while (1)
{
//TODO: Insert daemon code here.
syslog (LOG_NOTICE, "First daemon started.");
sleep (20);
break;
}
syslog (LOG_NOTICE, "First daemon terminated.");
closelog();
return EXIT_SUCCESS;
}
- Kompilieren Sie den Code:
gcc -o firstdaemon daemonize.c
- Daemon starten:
./firstdaemon
-
Überprüfen Sie, ob alles richtig funktioniert:
ps -xj | grep firstdaemon
-
Die Ausgabe sollte dieser ähneln:
+------+------+------+------+-----+-------+------+------+------+-----+ | PPID | PID | PGID | SID | TTY | TPGID | STAT | UID | TIME | CMD | +------+------+------+------+-----+-------+------+------+------+-----+ | 1 | 3387 | 3386 | 3386 | ? | -1 | S | 1000 | 0:00 | ./ | +------+------+------+------+-----+-------+------+------+------+-----+
Was Sie hier sehen sollten, ist:
- Der Daemon hat kein steuerndes Terminal (TTY =? )
- Die übergeordnete Prozess-ID (PPID ) ist 1 (Der Init-Prozess)
- Die PID !=SID was bedeutet, dass unser Prozess NICHT der Sitzungsleiter ist
(wegen der zweiten Abspaltung()) - Weil PID !=SID ist, kann unser Prozess keine Kontrolle über ein TTY übernehmen
Systemprotokoll lesen:
- Suchen Sie Ihre Syslog-Datei. Meine ist hier:
/var/log/syslog
-
Führen Sie Folgendes aus:
grep firstdaemon /var/log/syslog
-
Die Ausgabe sollte dieser ähneln:
firstdaemon[3387]: First daemon started. firstdaemon[3387]: First daemon terminated.
Hinweis: In Wirklichkeit möchten Sie auch einen Signalhandler implementieren und die Protokollierung richtig einrichten (Dateien, Protokollebenen ...).
Weiterführende Literatur:
- Linux-UNIX-Programmierung - Deutsch
- Programmierung des Unix-Daemon-Servers