Creazione di un demone in Linux


110

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 più una nuova riga.

Ho già il codice che cambia il filesystem quasi pronto ma non riesco a capire come creare un demone.

Il mio codice proviene da qui: http://www.yolinux.com/TUTORIALS/ForkExecProcesses.html

Cosa fare dopo la forcella?

int main (int argc, char **argv) {

  pid_t pID = fork();
  if (pID == 0)  {              // child
          // Code only executed by child process    
      sIdentifier = "Child Process: ";
    }
    else if (pID < 0) {
        cerr << "Failed to fork" << endl;
        exit(1);
       // Throw exception
    }
    else                                   // parent
    {
      // Code only executed by parent process

      sIdentifier = "Parent Process:";
    }       

    return 0;
}



Se non hai bisogno della conformità POSIX potresti essere interessato inotifyall'API. Vedi: inotify_init, inotify_add_watch, inotify_rm_watch.
patryk.beza

Risposte:


216

In Linux voglio aggiungere un demone che non può essere arrestato e che monitora le modifiche al filesystem. Se vengono rilevate modifiche, dovrebbe scrivere il percorso della console da 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 desideri. Di solito un demone syslog ( syslogd ) viene utilizzato per registrare i messaggi nei file (debug, errore, ...).

Oltre a ciò, ci sono alcuni passaggi necessari per demonizzare un processo.


Se ricordo bene questi passaggi sono:

  • biforcare il processo genitore e lasciarlo terminare se il fork ha avuto successo. -> Poiché il processo genitore è 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 del processo del nuovo gruppo del processo. Il processo è ora scollegato dal suo terminale di controllo (CTTY).
  • Segnali di cattura : ignorare e / o gestire i segnali.
  • fork di nuovo e lascia che il processo genitore termini 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 - Cambia la maschera della modalità file in base alle esigenze del demone.
  • close - Chiude tutti i descrittori di file aperti che possono essere ereditati dal processo genitore.

Per darti un punto di partenza: guarda questo codice scheletrico che mostra i passaggi di base. Questo codice può ora essere forkato 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 genitore ( PPID ) è 1 (processo di inizializzazione)
  • Il PID! = SID che significa che il nostro processo NON è il leader della sessione
    (a causa del secondo fork ())
  • Perché PID! = SID il nostro processo non può riprendere il controllo di un TTY

Leggere il syslog:

  • Individua il tuo file syslog. Il mio è qui:/var/log/syslog
  • Fai una: grep firstdaemon /var/log/syslog

  • L'output dovrebbe essere simile a questo:

  firstdaemon [3387]: il primo daemon è stato avviato.
  firstdaemon [3387]: primo daemon terminato.


Una nota: in realtà vorresti anche implementare un gestore di segnali e impostare correttamente la registrazione (file, livelli di log ...).

Ulteriore lettura:


Wow grazie! È fantastico. Quindi devo inserire il mio codice nel ciclo while e basta?
chrisMe

Fondamentalmente sì. Ma questo codice è solo un esempio. Dipende interamente da ciò che si desidera ottenere utilizzando un processo daemon. Assicurati di leggere anche questa risposta: @Edwin
Pascal Werkl

1
Invece del secondo fork(), perché non usarlo setsid()?
Chimera

1
Notare che la sigaction()funzione fornisce un meccanismo più completo e affidabile per il controllo dei segnali; le nuove applicazioni dovrebbero usare sigaction()invece di signal().
patryk.beza

4
Va notato ai telespettatori che questo metodo è il "vecchio" modo. Il nuovo modo consigliato per creare un demone è con il "demone nuovo stile" che si trova qui: 0pointer.de/public/systemd-man/daemon.html#New-Style%20Daemons o
Starlord

30

man 7 daemondescrive come creare un demone in grande dettaglio. La mia risposta è solo un estratto da questo manuale.

Esistono almeno due tipi di daemon:

  1. demoni SysV tradizionali ( vecchio stile ),
  2. demoni di systemd ( nuovo stile ).

SysV Daemons

Se sei interessato al demone SysV tradizionale , dovresti implementare i seguenti passaggi :

  1. Chiude tutti i descrittori di file aperti eccetto input , output ed errore standard (cioè i primi tre descrittori di file 0, 1, 2). Ciò garantisce che nessun descrittore di file passato accidentalmente rimanga nel processo daemon. Su Linux, questo è implementato al meglio iterando attraverso /proc/self/fd, con un fallback di iterazione dal descrittore di file 3 al valore restituito da getrlimit()for RLIMIT_NOFILE.
  2. Reimposta tutti i gestori di segnali ai valori predefiniti. Questo è meglio farlo iterando attraverso i segnali disponibili fino al limite di _NSIGe ripristinandoli SIG_DFL.
  3. Resettare la maschera del segnale usando sigprocmask().
  4. Disinfetta il blocco dell'ambiente, rimuovendo o ripristinando le variabili di ambiente che potrebbero avere un impatto negativo sul runtime del daemon.
  5. Chiama fork(), per creare un processo in background.
  6. Nel bambino, chiama setsid()per scollegarti da qualsiasi terminale e creare una sessione indipendente .
  7. Nel bambino, chiama di fork()nuovo, per assicurarti che il demone non possa mai riacquisire di nuovo un terminale.
  8. Chiama exit()il primo figlio, in modo che rimanga solo il secondo figlio (il processo daemon effettivo). Ciò garantisce che il processo del daemon venga re-genitore a init / PID 1, come dovrebbero essere tutti i daemon.
  9. Nel processo daemon, connettersi /dev/nulla input , output ed errore standard .
  10. Nel processo del demone, reimpostare umaska 0, in modo che le modalità file passate a open(), mkdir()e simili controllino direttamente la modalità di accesso dei file e delle directory creati.
  11. Nel processo del daemon, modificare la directory corrente nella directory root ( /), in modo da evitare che il daemon blocchi involontariamente i punti di montaggio dallo smontaggio.
  12. 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 assicurarti che il demone non possa essere avviato più di una volta. Questo deve essere implementato in modo da non correre in modo che il file PID venga aggiornato solo quando viene verificato allo stesso tempo che il PID precedentemente memorizzato nel file PID non esiste più o appartiene a un processo esterno.
  13. Nel processo daemon, eliminare i privilegi, se possibile e applicabile.
  14. Dal processo daemon, notificare al processo originale avviato che l'inizializzazione è stata completata. 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 daemon.
  15. Chiama exit()nel processo originale. Il processo che ha richiamato il daemon deve essere in grado di fare affidamento sul fatto che ciò exit()avvenga dopo che l' inizializzazione è stata completata e tutti i canali di comunicazione esterna sono stabiliti e accessibili.

Nota questo avviso:

La daemon()funzione BSD non deve essere utilizzata, poiché 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 opzionale e configurabile tramite un argomento della riga di comando per facilitare il debug e per semplificare l'integrazione nei sistemi utilizzando systemd.

Nota che daemon()non è conforme a POSIX .


Demoni di nuovo stile

Per i demoni di nuovo stile si consigliano i seguenti passaggi :

  1. Se SIGTERMviene ricevuto, chiudi il daemon ed esci in modo pulito.
  2. Se SIGHUPviene ricevuto, ricaricare i file di configurazione, se applicabile.
  3. Fornire un codice di uscita corretto dal processo del daemon 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 di inizializzazione SysV .
  4. Se possibile e applicabile, esporre l'interfaccia di controllo del daemon tramite il sistema IPC D-Bus e acquisire un nome bus come ultimo passaggio di inizializzazione.
  5. Per l'integrazione in systemd, fornire un file di unità .service che contenga informazioni sull'avvio , l'arresto e la manutenzione in altro modo del daemon. Vedere per i dettagli.systemd.service(5)
  6. Per quanto possibile, fare affidamento sulla funzionalità del sistema init per limitare l'accesso del demone a file, servizi e altre risorse, cioè, nel caso di systemd, fare affidamento sul controllo del limite delle risorse di systemd invece di implementare il proprio, fare affidamento sull'abbandono dei privilegi di systemd codice invece di implementarlo nel daemon e simili. Vedere systemd.exec(5)per i controlli disponibili.
  7. Se viene utilizzato D-Bus , rendere il daemon attivabile tramite bus fornendo un file di configurazione per l' attivazione del servizio D-Bus . Questo ha molteplici vantaggi: il tuo demone può essere avviato pigramente su richiesta; può essere avviato parallelamente ad altri demoni che lo richiedono, il che massimizza la parallelizzazione e la velocità di avvio ; il demone può essere riavviato in caso di errore senza perdere alcuna richiesta di bus, poiché il bus accoda le richieste per i servizi attivabili. Vedi sotto per i dettagli.
  8. Se il tuo demone fornisce servizi ad altri processi locali o client remoti tramite un socket, dovrebbe essere attivato tramite socket seguendo lo schema indicato di seguito . Come l'attivazione 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 singola richiesta. Vedi sotto per i dettagli.
  9. Se applicabile, un daemon dovrebbe notificare al sistema init il completamento dell'avvio o gli aggiornamenti di stato tramite l' sd_notify(3)interfaccia.
  10. Invece di usare la syslog()chiamata per accedere direttamente al servizio syslog del sistema, un demone di nuovo stile può scegliere di accedere semplicemente allo standard error tramite fprintf(), che viene poi inoltrato a syslog dal sistema init. Se sono necessari livelli di log, questi possono essere codificati aggiungendo un prefisso alle singole righe di log con stringhe come "<4>" (per il livello di log 4 "WARNING" nello schema di priorità syslog), seguendo uno stile simile al printk()sistema a livelli del kernel Linux . Per i dettagli, vedere sd-daemon(3)e systemd.exec(5).

Per saperne di più leggi tutto man 7 daemon.


11

Non è possibile creare un processo in Linux che non possa essere ucciso. L'utente root (uid = 0) può inviare un segnale a un processo e ci sono due segnali che non possono essere catturati, SIGKILL = 9, SIGSTOP = 19. E altri segnali (se non rilevati) possono anche provocare la conclusione del processo.

Potresti volere una funzione daemonize più generale, in cui puoi specificare un nome per il tuo programma / daemon e un percorso per eseguire il tuo programma (forse "/" o "/ tmp"). Potresti anche voler fornire file per stderr e stdout (e possibilmente un percorso di controllo usando stdin).

Ecco gli include necessari:

#include <stdio.h>    //printf(3)
#include <stdlib.h>   //exit(3)
#include <unistd.h>   //fork(3), chdir(3), sysconf(3)
#include <signal.h>   //signal(3)
#include <sys/stat.h> //umask(3)
#include <syslog.h>   //syslog(3), openlog(3), closelog(3)

Ed ecco una funzione più generale,

int
daemonize(char* name, char* path, char* outfile, char* errfile, char* infile )
{
    if(!path) { path="/"; }
    if(!name) { name="medaemon"; }
    if(!infile) { infile="/dev/null"; }
    if(!outfile) { outfile="/dev/null"; }
    if(!errfile) { errfile="/dev/null"; }
    //printf("%s %s %s %s\n",name,path,outfile,infile);
    pid_t child;
    //fork, detach from process group leader
    if( (child=fork())<0 ) { //failed fork
        fprintf(stderr,"error: failed fork\n");
        exit(EXIT_FAILURE);
    }
    if (child>0) { //parent
        exit(EXIT_SUCCESS);
    }
    if( setsid()<0 ) { //failed to become session leader
        fprintf(stderr,"error: failed setsid\n");
        exit(EXIT_FAILURE);
    }

    //catch/ignore signals
    signal(SIGCHLD,SIG_IGN);
    signal(SIGHUP,SIG_IGN);

    //fork second time
    if ( (child=fork())<0) { //failed fork
        fprintf(stderr,"error: failed fork\n");
        exit(EXIT_FAILURE);
    }
    if( child>0 ) { //parent
        exit(EXIT_SUCCESS);
    }

    //new file permissions
    umask(0);
    //change to path directory
    chdir(path);

    //Close all open file descriptors
    int fd;
    for( fd=sysconf(_SC_OPEN_MAX); fd>0; --fd )
    {
        close(fd);
    }

    //reopen stdin, stdout, stderr
    stdin=fopen(infile,"r");   //fd=0
    stdout=fopen(outfile,"w+");  //fd=1
    stderr=fopen(errfile,"w+");  //fd=2

    //open syslog
    openlog(name,LOG_PID,LOG_DAEMON);
    return(0);
}

Ecco un programma di esempio, che diventa un demone, rimane in sospeso e poi esce.

int
main()
{
    int res;
    int ttl=120;
    int delay=5;
    if( (res=daemonize("mydaemon","/tmp",NULL,NULL,NULL)) != 0 ) {
        fprintf(stderr,"error: daemonize failed\n");
        exit(EXIT_FAILURE);
    }
    while( ttl>0 ) {
        //daemon code here
        syslog(LOG_NOTICE,"daemon ttl %d",ttl);
        sleep(delay);
        ttl-=delay;
    }
    syslog(LOG_NOTICE,"daemon ttl expired");
    closelog();
    return(EXIT_SUCCESS);
}

Notare che SIG_IGN indica di catturare e ignorare il segnale. È possibile creare un gestore del segnale in grado di registrare la ricezione del segnale e impostare flag (come un flag per indicare un arresto regolare).


8

Prova a utilizzare la daemonfunzione:

#include <unistd.h>

int daemon(int nochdir, int noclose);

Dalla pagina man :

La funzione daemon () è per i programmi che desiderano scollegarsi dal terminale di controllo ed essere eseguiti in background come demoni di sistema.

Se nochdir è zero, daemon () cambia la directory di lavoro corrente del processo chiamante nella directory root ("/"); in caso contrario, la directory di lavoro corrente rimane invariata.

Se noclose è zero, daemon () reindirizza lo standard input, lo standard output e lo standard error a / dev / null; in caso contrario, non vengono apportate modifiche a questi descrittori di file.


2
Si noti che il daemon(7)manuale menziona i passaggi per creare il demone e avverte che: La daemon()funzione BSD non deve essere utilizzata, poiché implementa solo un sottoinsieme di questi passaggi. daemonla funzione è apparsa per la prima volta in 4.4BSD e non è conforme a POSIX .
patryk.beza

2
Notare anche che l'avvertenza sull'uso di daemon () si trova nella sezione SysV vecchio stile della pagina man di daemon (7) . L'uso di daemon () non è sconsigliato per systemd.
Greg McPherran

7

Posso fermarmi al primo requisito "Un demone che non può essere fermato ..."

Non è possibile amico mio; tuttavia, puoi ottenere lo stesso risultato con uno strumento molto migliore, un modulo del kernel.

http://www.infoq.com/articles/inotify-linux-file-system-event-monitoring

Tutti i daemon possono essere fermati. Alcuni vengono fermati più facilmente di altri. Anche una coppia di demoni con il partner in attesa, che fa rinascere il partner se perso, può essere fermata. Devi solo lavorare un po 'di più.


7
Penso che dicendo "Un demone che non può essere fermato", l'autore in realtà significa che il demone è sempre in esecuzione in background quando la sessione viene terminata.
FaceBro

6

Se la tua app è una di:

{
  ".sh": "bash",
  ".py": "python",
  ".rb": "ruby",
  ".coffee" : "coffee",
  ".php": "php",
  ".pl" : "perl",
  ".js" : "node"
}

e non ti dispiace una dipendenza NodeJS quindi installa NodeJS e poi:

npm install -g pm2

pm2 start yourapp.yourext --name "fred" # where .yourext is one of the above

pm2 start yourapp.yourext -i 0 --name "fred" # run your app on all cores

pm2 list

Per mantenere tutte le app in esecuzione al riavvio (e daemonise pm2):

pm2 startup

pm2 save

Ora puoi:

service pm2 stop|restart|start|status

(consente anche di controllare facilmente le modifiche al codice nella directory dell'app e di riavviare automaticamente il processo dell'app quando si verifica una modifica del codice)


2
Questo non ha niente a che fare con C.
melpomene il

4
Apprezzo che ci sia un tag C. Tuttavia, OP non menziona un requisito riguardante C nella domanda. Il titolo sta creando un demone in Linux. Questa risposta lo soddisfa.
danday74

1
Oh, hai ragione. È etichettato C, ma il requisito effettivo è C ++ (come evidenziato dal codice di OP e dall'articolo collegato).
melpomene

3

Chiamando fork () hai creato un processo figlio. Se il fork ha esito positivo (il fork ha restituito un PID diverso da zero) l'esecuzione continuerà da questo punto all'interno del processo figlio. In questo caso vogliamo uscire con garbo dal processo genitore e poi continuare il nostro lavoro nel processo figlio.

Forse questo aiuterà: http://www.netzmafia.de/skripten/unix/linux-daemon-howto.html


2

Un demone è solo un processo in background. Se vuoi avviare il tuo programma all'avvio del sistema operativo, su Linux, aggiungi il tuo comando di avvio a /etc/rc.d/rc.local (eseguito dopo tutti gli altri script) o /etc/startup.sh

Su Windows, si crea un servizio, si registra il servizio e quindi lo si imposta per l'avvio automatico all'avvio in amministrazione -> pannello servizi.


1
Grazie. Quindi non c'è differenza tra un "demone" e un normale programma? Non voglio che si chiuda facilmente.
chrisMe

1
No, un demone è solo un processo in background. Più specificamente, si esegue il fork da un genitore, si esegue il processo figlio e si termina il genitore (in modo che non ci sia accesso da terminale al programma). questo non è necessariamente necessario per essere un "demone": en.wikipedia.org/wiki/Daemon_(computing)
Magn3s1um

1

Modello daemon

Ho scritto un modello daemon seguendo il nuovo stile daemon: link

Puoi trovare l'intero codice del modello su GitHub: qui

main.cpp

// This function will be called when the daemon receive a SIGHUP signal.
void reload() {
    LOG_INFO("Reload function called.");
}

int main(int argc, char **argv) {
    // The Daemon class is a singleton to avoid be instantiate more than once
    Daemon& daemon = Daemon::instance();
    // Set the reload function to be called in case of receiving a SIGHUP signal
    daemon.setReloadFunction(reload);
    // Daemon main loop
    int count = 0;
    while(daemon.IsRunning()) {
        LOG_DEBUG("Count: ", count++);
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
    LOG_INFO("The daemon process ended gracefully.");
}

Daemon.hpp

class Daemon {
    public:

    static Daemon& instance() {
        static Daemon instance;
        return instance;
    }

    void setReloadFunction(std::function<void()> func);

    bool IsRunning();

    private:

    std::function<void()> m_reloadFunc;
    bool m_isRunning;
    bool m_reload;

    Daemon();
    Daemon(Daemon const&) = delete;
    void operator=(Daemon const&) = delete;

    void Reload();

    static void signalHandler(int signal);
};

Daemon.cpp

Daemon::Daemon() {
    m_isRunning = true;
    m_reload = false;
    signal(SIGINT, Daemon::signalHandler);
    signal(SIGTERM, Daemon::signalHandler);
    signal(SIGHUP, Daemon::signalHandler);
}

void Daemon::setReloadFunction(std::function<void()> func) {
    m_reloadFunc = func;
}

bool Daemon::IsRunning() {
    if (m_reload) {
        m_reload = false;
        m_reloadFunc();
    }
    return m_isRunning;
}

void Daemon::signalHandler(int signal) {
    LOG_INFO("Interrup signal number [", signal,"] recived.");
    switch(signal) {
        case SIGINT:
        case SIGTERM: {
            Daemon::instance().m_isRunning = false;
            break;
        }
        case SIGHUP: {
            Daemon::instance().m_reload = true;
            break;
        }
    }
}

daemon-template.service

[Unit]
Description=Simple daemon template
After=network.taget

[Service]
Type=simple
ExecStart=/usr/bin/daemon-template --conf_file /etc/daemon-template/daemon-tenplate.conf
ExecReload=/bin/kill -HUP $MAINPID
User=root
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=daemon-template

[Install]
WantedBy=multi-user.target
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.