In questo articolo vedremo come scrivere un deamon per Linux usando il linguaggio di programmazione C.
In Unix i processi lavorano siano in background che in foreground. Un processo che viene eseguito foreground interagisce con l’ utente tramite il terminale, mentre il processo in background lavora da solo.Il termine “deamon” viene utilizzato per definire i processi che eseguono servizi in background, di cui l’ utente può controllare lo stato ma non ha bisogno di sapere esattamente cosa sta facendo in ogni preciso istante.
Un esempio potrebbe essere un Server: è un processo deamon che inizia l’ esecuzione all’ avvio del sistema (non necessariamente), rimane in esecuzione per sempre, di solito non muore ne viene riavviato, opera in background, e aspetta l’ arrivo di richieste da servire e in caso spawna altri processi per gestire queste richieste.
Secondo Wikipedia, un deamon è definito come:
Un demone (daemon in inglese) è un programma eseguito in background, cioè senza che sia sotto il controllo diretto dell’utente, tipicamente fornendo un servizio all’utente.
Deamonizzare un processo
I Deamon iniziano come processi ordinari che eseguono un “fork and die” per continuare l’ esecuzione in background. Nonostante molti eseguano solo questo passaggio, ce ne sono anche altri da seguire per demonizzare correttamente un processo, e sono:
- Fork: Creazione del figlio, uscita dal processo padre
- Umask: Cambiare il valore di umask
- Logs: Aprire i logs dove poter scrivere (per esempio in caso di errori)
- Session Id: Creare un nuovo session id e distaccarsi dalla sessione corrente
- WD: Cambiare la Working Directory in un posto che non verrà “smontato”
- File Descriptors: Chiudere STDIN, STDOUT, e STDERR.
Questi sono tutti i passaggi necessari per demonizzare un processo correttamente. Vediamo i passaggi nel dettaglio:
1 – Fork
In Unix, la funzione fork è l’ unica chiamata di sistema (system call) che ritorna due volte: una al padre, ed una al figlio. Un processo richiama questa funzione per avviare un nuovo processo, che sarà una sua copia identica(stesse strutture dati, stessi file descriptor ecc). In caso di successo della funzione tornerà al padre il valore del pid (process id) del figlio, o -1 in caso di insuccesso, e al figlio tornerà 1.
La prima cosa da fare quindi è eseguire una fork e controllare i valori di ritorno:
pid_t pid;
pid = fork();
if (pid < 0)
{
perror("Errore nella fork");
exit(EXIT_FAILURE);
}
if(pid > 0)
{
//Addio al padre
exit(EXIT_SUCCESS);
}
2 – Umask
Fra le altre cose, vengono ereditati anche i permessi di lettura e scrittura dal padre. Visto che potremmo non avere permessi di lettura o scrittura, ci assicuriamo di averli impostando l’ umask a zero usando l’apposita system call:
umask(0)
3- Logs
Questo passaggio può essere fatto in vari modi. Possiamo scegliere di utilizzare un semplice file, un database o anche syslog. Quest’ ultimo permette di inviare i tuoi messaggi a un logger di sistema, che permette poi di scriverli in un file o di inviarli via rete, o filtrarli.
/* Open a connection to the syslog server */
openlog(argv[0],LOG_NOWAIT|LOG_PID,LOG_USER);
/* Sends a message to the syslog daemon */
syslog(LOG_NOTICE, "Successfully started daemon\n");
Al momento dell’ uscita del nostro deamon, opzionalmente dovremo fare:
4 – Session Id
Il Session Id corrisponde al PID del processo che ha creato la sessione. Una volta che la sessione viene terminata, tutti i processi collegati vengono terminati dal kernel. Visto che non vogliamo ciò, avremo bisogno di un nuovo session ID, e per farlo usiamo setsid (senza argomenti) che ritorna un nuovo sessione id:
pid_t sid;
sid = setsid();
if(sid < 0)
{
syslog(LOG_ERR, "Could not create new session id\n");
exit(EXIT_FAILURE);
}
5 – WD
Ci siamo quasi! Al momento della fork, fra le altre cose, abbiamo anche ereditato la working directory del processo padre. Visto che l’ amministratore potrebbe voler eseguire l’ umount della locazione che contiene la wd del nostro deamon, dovremmo fare in modo di non impedirglielo. Anche perchè l’ unmount ucciderebbe tutti i processi che stanno eseguendo operazioni sui file contenuti nella locazione da smontare. Per questa ragione dobbiamo cambiare la directory di lavoro, solitamente per i deamon si imposta la root directory (‘/’) di cui si ha la certezza che non verrà smontata, però potrebbe in alcuni casi avere senso sceglierne una apposita (ad esempio per l’ esecuzione di un server una path tipo “/servers”).
Ecco il codice:
if(chdir("/"))< 0) {syslog(LOG_ERR,
"Could not change working directory to /\n"
);
exit
(EXIT_FAILURE); }
6 – File Descriptors
Il nostro deamon non interagisce con l’ utente direttamente, quindi non ha bisogno di utilizzare i file descriptors di STDIN, STDOU, e STDERR; anche perchè non sapendo dove sono connessi e non sapremmo dove andrebbe a finire qualunque cosa vorremmo scrivervi. Visto che sono aperti ma non verranno utilizzati, per motivi di sicurezza e per risparmiare risorse di sistema, aggiungiamo queste tre righe per chiudere il relativi file descriptors:
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
Codice completo per demonizzare un processo
Ecco il codice finale che ci permette di demonizzare un processo:
#include
#include
#include
int main(int argc, char *argv[]) {
pid_t pid, sid;
// 1 - Fork
pid = fork();
if (pid < 0)
{
exit(EXIT_FAILURE);
}
if (pid > 0)
{
exit(EXIT_SUCCESS);
}
//2 - Umask
umask(0);
//3 - Logs
openlog(argv[0],LOG_NOWAIT|LOG_PID,LOG_USER);
syslog(LOG_NOTICE, "Successfully started daemon\n");
//4 - Session Id
sid = setsid();
if (sid < 0) {
syslog(LOG_ERR, "Could not create process group\n");
exit(EXIT_FAILURE);
}
//5 - WD
if ((chdir("/")) < 0) {
syslog(LOG_ERR, "Could not change working directory to /\n");
exit(EXIT_FAILURE);
}
//6 - FD
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
// Il cuore del nostro deamon:
while (1) {
syslog(LOG_INFO, "I'm alive\n");
sleep(15);
}
// Quando il nostro deamon ha finito il suo lavoro:
closelog();
}
Usare la funzione deamon()
In caso stiate sviluppando per sistemi non System V Unix, in Linux è presente una funzione che permette di demonizzare al volo un programma:
#include
int daemon(int nochdir, int noclose);
Gli input permettono di definire:
- nochdir: Se deve essere cambiata la wd nella root directory (passo 5)
- noclose: Se devono essere chiusi i FD STDIN, STDOUT, STDERR (passo 6)
Il problema di questa funzione è che non è standard: potrebbe non assumere lo stesso comportamento su sistemi diversi.
Altri modi per demonizzare un programma
Abbiamo a disposizione il programma nohup che ci permette di filtrare il segnale di SIGHUP ( inviato al momento della chiusura del terminale), che unito alla redirezione dell’ input ed output ci permette di avviare un programma in modalità deamon. Di solito questa metodo viene utilizzato quando vogliamo avviare un programma che sappiamo che richiederà del tempo per effettuare (in autonomia) un lavoro, senza l ‘intervento dell’ utente.
nohup program < /dev/null > /dev/null 2>&1
Dove:
- program è il programma da avviare;
< /dev/null
indica /dev/null come STDIN;> /dev/null
reindirizza lo STDOUT a /dev/null;2>&1
reindirizza STDERR alla stessa destinazione dello STDOUT (cioè /dev/null);- lo
&
finale indica di eseguire il programma in background.
Referenze:
- Linux Daemon Writing HOWTO – Devin Watson
- https://stackoverflow.com/questions/4797050/how-to-run-process-as-background-and-never-die
- https://superuser.com/questions/1025443/why-does-a-deamon-process-need-a-new-sid/1025494#1025494