man 7 daemon
descrive in dettaglio come creare un demone. La mia risposta è solo un estratto di questo manuale.
Esistono almeno due tipi di demoni:
- demoni SysV tradizionali (vecchio stile),
- demoni systemd (nuovo stile).
Demoni SysV
Se sei interessato al demone SysV tradizionale, dovresti implementare i seguenti passaggi:
- Chiudi tutti i descrittori di file aperti tranne l'input standard , output e errore (ovvero i primi tre descrittori di file 0, 1, 2). Ciò garantisce che nessun descrittore di file passato accidentalmente rimanga nel processo del demone. Su Linux, questo è meglio implementato ripetendo
/proc/self/fd
, con un fallback dell'iterazione dal descrittore di file 3 al valore restituito dagetrlimit()
perRLIMIT_NOFILE
.- Ripristina tutti i gestori di segnale ai valori predefiniti. È meglio farlo iterando i segnali disponibili fino al limite di
_NSIG
e reimpostandoli suSIG_DFL
.- Ripristina la maschera del segnale usando
sigprocmask()
.- Ripulisci il blocco dell'ambiente, rimuovendo o reimpostando le variabili di ambiente che potrebbero avere un impatto negativo sul runtime del daemon.
- Chiama
fork()
, per creare un processo in background.- Nel bambino, chiama
setsid()
per staccarsi da qualsiasi terminale e creare una sessione indipendente.- Nel bambino, chiama
fork()
di nuovo, per garantire che il demone non possa mai riacquisire nuovamente un terminale.- Chiama
exit()
nel primo figlio, in modo che rimanga solo il secondo figlio (l'effettivo processo daemon). Ciò garantisce che il processo daemon sia nuovamente associato a init/PID 1, come dovrebbero essere tutti i daemon.- Nel processo del demone, connetti
/dev/null
all'input standard , output e errore .- Nel processo del demone, reimposta
umask
a 0, in modo che le modalità del file passassero aopen()
,mkdir()
e simili controllano direttamente la modalità di accesso dei file e delle directory creati.- Nel processo del demone, cambia la directory corrente nella directory root (
/
), al fine di evitare che il demone blocchi involontariamente i punti di montaggio dallo smontaggio.- Nel processo del demone, scrivi il PID del demone (come restituito da
getpid()
) in un file PID, ad esempio/run/foobar.pid
(per un ipotetico demone "foobar") per garantire che il demone non possa essere avviato più di una volta. Questo deve essere implementato in modo race-free in modo che il file PID venga aggiornato solo quando viene verificato contemporaneamente che il PID precedentemente memorizzato nel file PID non esiste più o appartiene a un processo esterno.- Nel processo del demone, elimina i privilegi, se possibile e applicabile.
- Dal processo daemon, notifica al processo originale avviato che l'inizializzazione è completa. Questo può essere implementato tramite una pipe senza nome o un canale di comunicazione simile creato prima del primo
fork()
e quindi disponibile sia nel processo originale che in quello demone.- Chiama
exit()
nel processo originario. Il processo che ha invocato il demone deve poter contare su questoexit()
accade dopo l'inizializzazione è completa e tutti i canali di comunicazione esterni sono stabiliti e accessibili.
Nota questo avviso:
Il BSD
daemon()
la funzione non dovrebbe essere utilizzato, in quanto implementa solo un sottoinsieme di questi passaggi.Un demone che deve fornire compatibilità con i sistemi SysV dovrebbe implementare lo schema sopra indicato. Tuttavia, si consiglia di rendere questo comportamento facoltativo e configurabile tramite un argomento della riga di comando per facilitare il debug e semplificare l'integrazione nei sistemi che utilizzano systemd.
Nota che daemon()
non è conforme a POSIX.
Demoni di nuovo stile
Per i demoni di nuovo stile si consigliano i seguenti passaggi:
- Se
SIGTERM
viene ricevuto, chiudi il demone ed esci in modo pulito.- Se
SIGHUP
viene ricevuto, ricaricare i file di configurazione, se applicabile.- Fornire un codice di uscita corretto dal processo del demone principale, poiché viene utilizzato dal sistema init per rilevare errori e problemi del servizio. Si consiglia di seguire lo schema del codice di uscita come definito nelle raccomandazioni LSB per gli script SysV init.
- Se possibile e applicabile, esponi l'interfaccia di controllo del demone tramite il sistema IPC D-Bus e prendi un nome bus come ultimo passaggio dell'inizializzazione.
- Per l'integrazione in systemd, fornire un file di unità .service che contenga informazioni sull'avvio, l'arresto e la manutenzione del demone. Vedi
systemd.service(5)
per i dettagli.- Per quanto possibile, affidati alla funzionalità del sistema init per limitare l'accesso del demone a file, servizi e altre risorse, ad esempio nel caso di systemd, affidati al controllo del limite delle risorse di systemd invece di implementare il tuo, affidati a il privilegio di systemd che elimina il codice invece di implementarlo nel demone e simili. Vedi
systemd.exec(5)
per i controlli disponibili.- Se si utilizza D-Bus, rendere il demone attivabile tramite bus fornendo un file di configurazione per l'attivazione del servizio D-Bus. Ciò ha molteplici vantaggi:il tuo demone può essere avviato pigramente su richiesta; può essere avviato in parallelo ad altri demoni che lo richiedono, il che massimizza la parallelizzazione e la velocità di avvio; il tuo demone può essere riavviato in caso di errore senza perdere alcuna richiesta di bus, poiché il bus accoda le richieste di servizi attivabili. Vedi sotto per i dettagli.
- Se il tuo demone fornisce servizi ad altri processi locali o client remoti tramite un socket, dovrebbe essere reso attivabile tramite socket seguendo lo schema indicato di seguito. Come l'attivazione di D-Bus, ciò consente l'avvio su richiesta dei servizi e consente una migliore parallelizzazione dell'avvio del servizio. Inoltre, per i protocolli senza stato (come syslog, DNS), un demone che implementa l'attivazione basata su socket può essere riavviato senza perdere una sola richiesta. Vedi sotto per i dettagli.
- Se applicabile, un demone dovrebbe notificare al sistema init il completamento dell'avvio o gli aggiornamenti dello stato tramite
sd_notify(3)
interfaccia.- Invece di usare il
syslog()
chiamata per accedere direttamente al servizio syslog di sistema, un demone di nuovo stile può scegliere di accedere semplicemente all'errore standard tramitefprintf()
, che viene quindi inoltrato a syslog dal sistema init. Se i livelli di log sono necessari, questi possono essere codificati anteponendo alle singole righe di log stringhe come "<4>" (per il livello di log 4 "WARNING" nello schema di priorità syslog), seguendo uno stile simile alprintk()
sistema di livelli. Per i dettagli, vedisd-daemon(3)
esystemd.exec(5)
.
Per saperne di più leggi tutto man 7 daemon
.
In Linux voglio aggiungere un demone che non può essere fermato e che monitora le modifiche al filesystem. Se vengono rilevate modifiche, dovrebbe scrivere il percorso della console in cui è stato avviato + una nuova riga.
I demoni funzionano in background e (di solito...) non appartengono a un TTY, ecco perché non puoi usare stdout/stderr nel modo che probabilmente vorresti. Di solito un demone syslog (syslogd ) viene utilizzato per registrare i messaggi nei file (debug, errore,...).
Oltre a ciò, ci sono alcuni passaggi obbligatori per demonizzare un processo.
Se non ricordo male questi passaggi sono:
- forchetta fuori dal processo genitore e lasciarlo terminare se il fork ha avuto successo. -> Poiché il processo padre è terminato, il processo figlio ora viene eseguito in background.
- setsid - Crea una nuova sessione. Il processo chiamante diventa il leader della nuova sessione e il leader del gruppo di processi del nuovo gruppo di processi. Il processo è ora scollegato dal suo terminale di controllo (CTTY).
- Segnali di cattura - Ignora e/o gestisci i segnali.
- biforca di nuovo &lascia terminare il processo genitore per assicurarti di sbarazzarti del processo principale della sessione. (Solo i leader di sessione possono ottenere di nuovo un TTY.)
- chdir - Cambia la directory di lavoro del demone.
- umask - Modificare la maschera della modalità file in base alle esigenze del demone.
- chiudi - Chiude tutti i descrittori di file aperti che possono essere ereditati dal processo padre.
Per darti un punto di partenza:guarda questo codice scheletro che mostra i passaggi di base. Questo codice può ora essere biforcato anche su GitHub:scheletro di base di un demone 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;
}
- Compila il codice:
gcc -o firstdaemon daemonize.c
- Avvia il demone:
./firstdaemon
-
Controlla se tutto funziona correttamente:
ps -xj | grep firstdaemon
-
L'output dovrebbe essere simile a questo:
+------+------+------+------+-----+-------+------+------+------+-----+ | PPID | PID | PGID | SID | TTY | TPGID | STAT | UID | TIME | CMD | +------+------+------+------+-----+-------+------+------+------+-----+ | 1 | 3387 | 3386 | 3386 | ? | -1 | S | 1000 | 0:00 | ./ | +------+------+------+------+-----+-------+------+------+------+-----+
Quello che dovresti vedere qui è:
- Il demone non ha un terminale di controllo (TTY =? )
- L'ID del processo principale (PPID ) è 1 (Il processo init)
- Il PID !=SID il che significa che il nostro processo NON è il leader della sessione
(a causa del secondo fork()) - Poiché PID !=SID il nostro processo non può riprendere il controllo di un TTY
Lettura del registro di sistema:
- Individua il tuo file syslog. Il mio è qui:
/var/log/syslog
-
Fai un:
grep firstdaemon /var/log/syslog
-
L'output dovrebbe essere simile a questo:
firstdaemon[3387]: First daemon started. firstdaemon[3387]: First daemon terminated.
Una nota: In realtà vorresti anche implementare un gestore di segnale e configurare correttamente la registrazione (file, livelli di registro...).
Ulteriori letture:
- Linux-UNIX-Programmierung - Tedesco
- Programmazione Unix Daemon Server