man 7 daemon
décrit comment créer un démon en détail. Ma réponse est juste un extrait de ce manuel.
Il existe au moins deux types de démons :
- démons SysV traditionnels (à l'ancienne),
- démons systemd (nouveau style).
Démons SysV
Si vous êtes intéressé par le démon SysV traditionnel, vous devez implémenter les étapes suivantes :
- Fermer tous les descripteurs de fichiers ouverts à l'exception de l'entrée standard , sortie , et erreur (c'est-à-dire les trois premiers descripteurs de fichier 0, 1, 2). Cela garantit qu'aucun descripteur de fichier transmis accidentellement ne reste dans le processus démon. Sous Linux, cela est mieux implémenté en itérant à travers
/proc/self/fd
, avec un retour d'itération du descripteur de fichier 3 à la valeur renvoyée pargetrlimit()
pourRLIMIT_NOFILE
.- Réinitialisez tous les gestionnaires de signaux à leur valeur par défaut. Pour ce faire, il est préférable de parcourir les signaux disponibles jusqu'à la limite de
_NSIG
et en les réinitialisant àSIG_DFL
.- Réinitialisez le masque de signal à l'aide de
sigprocmask()
.- Nettoyez le bloc d'environnement, en supprimant ou en réinitialisant les variables d'environnement susceptibles d'avoir un impact négatif sur l'exécution du démon.
- Appelez le
fork()
, pour créer un processus d'arrière-plan.- Dans l'enfant, appelez
setsid()
pour se détacher de n'importe quel terminal et créer une session indépendante.- Chez l'enfant, appelez le
fork()
encore une fois, pour s'assurer que le démon ne pourra plus jamais réacquérir un terminal.- Appelez le
exit()
dans le premier enfant, de sorte que seul le deuxième enfant (le processus démon réel) reste. Cela garantit que le processus démon est re-parenté à init/PID 1, comme tous les démons devraient l'être.- Dans le processus démon, connectez
/dev/null
à l'entrée standard , sortie , et erreur .- Dans le processus démon, réinitialisez le
umask
à 0, de sorte que les modes de fichier sont passés àopen()
,mkdir()
et autres contrôlent directement le mode d'accès des fichiers et répertoires créés.- Dans le processus démon, remplacez le répertoire courant par le répertoire racine (
/
), afin d'éviter que le démon bloque involontairement le démontage des points de montage.- Dans le processus démon, écrivez le PID du démon (tel que renvoyé par
getpid()
) à un fichier PID, par exemple/run/foobar.pid
(pour un démon hypothétique "foobar") pour s'assurer que le démon ne peut pas être démarré plus d'une fois. Cela doit être implémenté de manière sans course afin que le fichier PID ne soit mis à jour que lorsqu'il est vérifié en même temps que le PID précédemment stocké dans le fichier PID n'existe plus ou appartient à un processus étranger.- Dans le processus du démon, supprimez les privilèges, si possible et applicable.
- Depuis le processus démon, informez le processus d'origine démarré que l'initialisation est terminée. Cela peut être mis en œuvre via un canal sans nom ou un canal de communication similaire créé avant le premier
fork()
et donc disponible à la fois dans le processus d'origine et dans le processus démon.- Appelez le
exit()
dans le processus d'origine. Le processus qui a appelé le démon doit pouvoir s'appuyer sur ceexit()
arrive après l'initialisation est terminée et tous les canaux de communication externes sont établis et accessibles.
Notez cet avertissement :
Le BSD
daemon()
la fonction ne devrait pas être utilisé, car il n'implémente qu'un sous-ensemble de ces étapes.Un démon qui doit fournir la compatibilité avec les systèmes SysV doivent mettre en œuvre le schéma indiqué ci-dessus. Cependant, il est recommandé de rendre ce comportement facultatif et configurable via un argument de ligne de commande pour faciliter le débogage ainsi que pour simplifier l'intégration dans les systèmes utilisant systemd.
Notez que daemon()
n'est pas compatible POSIX.
Démons de nouveau style
Pour les démons de nouveau style, les étapes suivantes sont recommandées :
- Si
SIGTERM
est reçu, arrêtez le démon et quittez proprement.- Si
SIGHUP
est reçu, rechargez les fichiers de configuration, le cas échéant.- Fournissez un code de sortie correct du processus du démon principal, car il est utilisé par le système init pour détecter les erreurs et les problèmes de service. Il est recommandé de suivre le schéma de code de sortie tel que défini dans les recommandations LSB pour les scripts d'initialisation SysV.
- Si possible et applicable, exposez l'interface de contrôle du démon via le système D-Bus IPC et saisissez un nom de bus comme dernière étape de l'initialisation.
- Pour l'intégration dans systemd, fournissez un fichier d'unité .service contenant des informations sur le démarrage, l'arrêt et la maintenance du démon. Voir
systemd.service(5)
pour plus de détails.- Autant que possible, comptez sur la fonctionnalité du système init pour limiter l'accès du démon aux fichiers, services et autres ressources, c'est-à-dire dans le cas de systemd, comptez sur le contrôle de limite de ressources de systemd au lieu d'implémenter le vôtre, comptez sur Le privilège de systemd abandonne le code au lieu de l'implémenter dans le démon, et similaire. Voir
systemd.exec(5)
pour les contrôles disponibles.- Si D-Bus est utilisé, rendez votre démon activable par bus en fournissant un fichier de configuration d'activation de service D-Bus. Cela présente de multiples avantages :votre démon peut être démarré paresseusement à la demande; il peut être démarré en parallèle avec d'autres démons qui en ont besoin — ce qui maximise la parallélisation et la vitesse de démarrage ; votre démon peut être redémarré en cas d'échec sans perdre aucune demande de bus, car le bus met en file d'attente les demandes de services activables. Voir ci-dessous pour plus de détails.
- Si votre démon fournit des services à d'autres processus locaux ou clients distants via un socket, il doit être rendu activable par socket en suivant le schéma indiqué ci-dessous. Comme l'activation D-Bus, cela permet le démarrage à la demande des services ainsi qu'une meilleure parallélisation du démarrage des services. De plus, pour les protocoles sans état (tels que syslog, DNS), un démon implémentant l'activation basée sur les sockets peut être redémarré sans perdre une seule requête. Voir ci-dessous pour plus de détails.
- Le cas échéant, un démon doit informer le système init de l'achèvement du démarrage ou des mises à jour de statut via le
sd_notify(3)
interface.- Au lieu d'utiliser le
syslog()
appel pour se connecter directement au service système syslog, un démon de nouveau style peut choisir de se connecter simplement à l'erreur standard viafprintf()
, qui est ensuite transmis à syslog par le système init. Si des niveaux de journalisation sont nécessaires, ceux-ci peuvent être encodés en préfixant les lignes de journalisation individuelles avec des chaînes telles que "<4>" (pour le niveau de journalisation 4 "WARNING" dans le schéma de priorité syslog), en suivant un style similaire auprintk()
système de niveau. Pour plus de détails, voirsd-daemon(3)
etsystemd.exec(5)
.
Pour en savoir plus, lisez l'intégralité du man 7 daemon
.
Sous Linux, je souhaite ajouter un démon qui ne peut pas être arrêté et qui surveille les modifications du système de fichiers. Si des changements sont détectés, il doit écrire le chemin vers la console où il a été démarré + une nouvelle ligne.
Les démons fonctionnent en arrière-plan et (généralement...) n'appartiennent pas à un TTY, c'est pourquoi vous ne pouvez pas utiliser stdout/stderr comme vous le souhaitez probablement. Habituellement, un démon syslog (syslogd ) est utilisé pour consigner les messages dans les fichiers (débogage, erreur,...).
En plus de cela, il y a quelques étapes requises pour démoniser un processus.
Si je me souviens bien, ces étapes sont :
- fork désactiver le processus parent et le laisser se terminer si le fork a réussi. -> Parce que le processus parent s'est terminé, le processus enfant s'exécute maintenant en arrière-plan.
- setid - Créer une nouvelle session. Le processus appelant devient le leader de la nouvelle session et le leader du groupe de processus du nouveau groupe de processus. Le processus est maintenant détaché de son terminal de contrôle (CTTY).
- Capter les signaux - Ignorer et/ou gérer les signaux.
- fork à nouveau &laissez le processus parent se terminer pour vous assurer que vous vous débarrassez du processus de tête de session. (Seuls les animateurs de session peuvent obtenir à nouveau un ATS.)
- chdir - Changer le répertoire de travail du démon.
- umask - Modifiez le masque de mode de fichier en fonction des besoins du démon.
- fermer - Fermez tous les descripteurs de fichiers ouverts qui peuvent être hérités du processus parent.
Pour vous donner un point de départ :regardez ce code squelette qui montre les étapes de base. Ce code peut désormais également être forké sur GitHub :Squelette de base d'un démon Linux
/*
* 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;
}
- Compilez le code :
gcc -o firstdaemon daemonize.c
- Démarrer le démon :
./firstdaemon
-
Vérifiez si tout fonctionne correctement :
ps -xj | grep firstdaemon
-
Le résultat devrait ressembler à celui-ci :
+------+------+------+------+-----+-------+------+------+------+-----+ | PPID | PID | PGID | SID | TTY | TPGID | STAT | UID | TIME | CMD | +------+------+------+------+-----+-------+------+------+------+-----+ | 1 | 3387 | 3386 | 3386 | ? | -1 | S | 1000 | 0:00 | ./ | +------+------+------+------+-----+-------+------+------+------+-----+
Ce que vous devriez voir ici est :
- Le démon n'a pas de terminal de contrôle (TTY =? )
- L'identifiant du processus parent (PPID ) est 1 (Le processus d'initialisation)
- Le PID !=SID ce qui signifie que notre processus n'est PAS le leader de la session
(à cause du second fork()) - Parce que PID !=SID, notre processus ne peut plus reprendre le contrôle d'un TTY
Lecture du journal système :
- Recherchez votre fichier syslog. Le mien est ici :
/var/log/syslog
-
Faites un :
grep firstdaemon /var/log/syslog
-
Le résultat devrait ressembler à celui-ci :
firstdaemon[3387]: First daemon started. firstdaemon[3387]: First daemon terminated.
Remarque : En réalité, vous voudriez également implémenter un gestionnaire de signaux et configurer correctement la journalisation (fichiers, niveaux de journalisation...).
Autres lectures :
- Linux-UNIX-Programmierung - Allemand
- Programmation du serveur démon Unix