Creando un demonio en Linux

110

En Linux, quiero agregar un demonio que no se puede detener y que monitorea los cambios en el sistema de archivos. Si se detectan cambios, debe escribir la ruta a la consola donde se inició más una nueva línea.

Ya tengo el código de cambio del sistema de archivos casi listo, pero no puedo averiguar cómo crear un demonio.

Mi código es de aquí: http://www.yolinux.com/TUTORIALS/ForkExecProcesses.html

¿Qué hacer después del tenedor?

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;
}
chrisMe
fuente
1
Posible duplicado: stackoverflow.com/q/5384168/1076451
Chimera
1
posible duplicado de: stackoverflow.com/questions/5384168/… para la parte daemonize, stackoverflow.com/questions/931093/… para la vigilancia del sistema de archivos
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
Si no necesita el cumplimiento de POSIX, puede estar interesado en inotifyAPI. Ver: inotify_init, inotify_add_watch, inotify_rm_watch.
patryk.beza

Respuestas:

216

En Linux, quiero agregar un demonio que no se puede detener y que monitorea los cambios en el sistema de archivos. Si se detectan cambios, debe escribir la ruta a la consola donde se inició + una nueva línea.

Los demonios funcionan en segundo plano y (generalmente ...) no pertenecen a un TTY, por eso no puede usar stdout / stderr de la manera que probablemente desee. Por lo general, se utiliza un demonio syslog ( syslogd ) para registrar mensajes en archivos (depuración, error, ...).

Además de eso, hay algunos pasos necesarios para demonizar un proceso.


Si recuerdo correctamente estos pasos son:

  • bifurque el proceso principal y déjelo terminar si la bifurcación fue exitosa. -> Debido a que el proceso padre ha terminado, el proceso hijo ahora se ejecuta en segundo plano.
  • setid : una nueva sesión. El proceso de llamada se convierte en el líder de la nueva sesión y el líder del grupo de procesos del nuevo grupo de procesos. El proceso ahora está separado de su terminal de control (CTTY).
  • Capturar señales : ignore y / o maneje las señales.
  • tenedor de nuevo y deje que el proceso padre termine para asegurarse de que se deshaga del proceso principal de sesión. (Solo los líderes de sesión pueden volver a obtener un TTY).
  • chdir : cambia el directorio de trabajo del demonio.
  • umask : cambia la máscara del modo de archivo según las necesidades del demonio.
  • cerrar : cierra todos los descriptores de archivos abiertos que puedan heredarse del proceso principal.

Para darte un punto de partida: mira este código esqueleto que muestra los pasos básicos. Este código ahora también se puede bifurcar en GitHub: Esqueleto básico de un demonio de 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 el código: gcc -o firstdaemon daemonize.c
  • Inicie el demonio: ./firstdaemon
  • Compruebe si todo funciona correctamente: ps -xj | grep firstdaemon

  • La salida debería ser similar a esta:

+ ------ + ------ + ------ + ------ + ----- + ------- + ------ + ------ + ------ + ----- +
| PPID | PID | PGID | SID | TTY | TPGID | STAT | UID | TIEMPO | CMD |
+ ------ + ------ + ------ + ------ + ----- + ------- + ------ + ------ + ------ + ----- +
| 1 | 3387 | 3386 | 3386 | ? | -1 | S | 1000 | 0:00 | ./ |
+ ------ + ------ + ------ + ------ + ----- + ------- + ------ + ------ + ------ + ----- +

Lo que debería ver aquí es:

  • El demonio no tiene terminal de control ( TTY =? )
  • El ID del proceso principal ( PPID ) es 1 (El proceso de inicio)
  • El PID! = SID, lo que significa que nuestro proceso NO es el líder de la sesión
    (debido a la segunda bifurcación ())
  • Porque PID! = SID, nuestro proceso no puede volver a tomar el control de un TTY

Leyendo el syslog:

  • Busque su archivo syslog. El mío está aquí:/var/log/syslog
  • Haz una: grep firstdaemon /var/log/syslog

  • La salida debería ser similar a esta:

  firstdaemon [3387]: Se inició el primer demonio.
  firstdaemon [3387]: primer demonio terminado.


Nota: en realidad, también querrá implementar un controlador de señales y configurar el registro correctamente (archivos, niveles de registro ...).

Otras lecturas:

Pascal Werkl
fuente
¡Wow gracias! Eso es genial. Así que tengo que poner mi código en el bucle while y eso es todo?
chrisMe
Básicamente sí. Pero este código es solo un ejemplo. Depende completamente de lo que desee lograr utilizando un proceso demonio. Asegúrese de leer esta respuesta también: @Edwin
Pascal Werkl
1
En lugar del segundo fork(), ¿por qué no usarlo setsid()?
Quimera
1
Tenga en cuenta que la sigaction()función proporciona un mecanismo más completo y confiable para controlar señales; las nuevas aplicaciones deberían usar en sigaction()lugar de signal().
patryk.beza
4
Debe notarse a los espectadores que este método es el "antiguo". La nueva forma recomendada de crear un demonio es con el "demonio de estilo nuevo" que se encuentra aquí: 0pointer.de/public/systemd-man/daemon.html#New-Style%20Daemons o
Starlord
30

man 7 daemondescribe cómo crear un demonio con gran detalle. Mi respuesta es solo un extracto de este manual.

Hay al menos dos tipos de demonios:

  1. demonios SysV tradicionales ( estilo antiguo ),
  2. demonios systemd ( nuevo estilo ).

Demonios SysV

Si está interesado en el demonio SysV tradicional , debe implementar los siguientes pasos :

  1. Cierre todos los descriptores de archivo abiertos excepto la entrada , la salida y el error estándar (es decir, los tres primeros descriptores de archivo 0, 1, 2). Esto asegura que ningún descriptor de archivo pasado accidentalmente permanezca en el proceso del demonio. En Linux, esto se implementa mejor iterando /proc/self/fd, con una alternativa de iterar desde el descriptor de archivo 3 al valor devuelto por getrlimit()for RLIMIT_NOFILE.
  2. Restablezca todos los manejadores de señales a sus valores predeterminados. Esto se hace mejor iterando a través de las señales disponibles hasta el límite de _NSIGy restableciéndolas a SIG_DFL.
  3. Restablezca la máscara de señal usando sigprocmask().
  4. Desinfecte el bloque de entorno, eliminando o restableciendo las variables de entorno que podrían afectar negativamente el tiempo de ejecución del demonio.
  5. Llamada fork() , para crear un proceso en segundo plano.
  6. En el niño, llame setsid() para desconectarse de cualquier terminal y crear una sesión independiente .
  7. En el niño, llame fork() vuelva a para asegurarse de que el daemon no pueda volver a adquirir una terminal nunca más.
  8. Llamada exit() al primer hijo, de modo que solo el segundo hijo (el proceso del demonio real) permanezca alrededor. Esto asegura que el proceso del demonio se vuelva a parentalizar en init / PID 1, como deberían ser todos los demonios.
  9. En el proceso del demonio, conéctese /dev/nulla la entrada , salida y error .
  10. En el proceso del demonio, restablezca umaska 0, de modo que los modos de archivo pasados ​​a open(), mkdir()y similares controlen directamente el modo de acceso de los archivos y directorios creados.
  11. En el proceso del demonio, cambie el directorio actual al directorio raíz ( /), para evitar que el demonio bloquee involuntariamente los puntos de montaje para que no se desmonten.
  12. En el proceso del demonio, escriba el PID del demonio (como lo devuelvegetpid() ) en un archivo PID, por ejemplo /run/foobar.pid(para un demonio hipotético "foobar") para asegurarse de que el demonio no se pueda iniciar más de una vez. Esto debe implementarse sin carreras para que el archivo PID solo se actualice cuando se verifique al mismo tiempo que el PID almacenado previamente en el archivo PID ya no existe o pertenece a un proceso extraño.
  13. En el proceso del demonio, elimine los privilegios, si es posible y aplicable.
  14. Desde el proceso del demonio, notifique al proceso original iniciado que la inicialización está completa. Esto se puede implementar a través de una tubería sin nombre o un canal de comunicación similar que se crea antes de la primerafork() y, por lo tanto, está disponible tanto en el proceso original como en el daemon.
  15. Llame exit()al proceso original. El proceso que invocó el demonio debe poder confiar en que esto exit()sucede después de que se completa la inicialización y todos los canales de comunicación externos están establecidos y accesibles.

Tenga en cuenta esta advertencia:

No se debe utilizar la daemon()función BSD , ya que implementa solo un subconjunto de estos pasos.

Un demonio que necesita proporcionar compatibilidad con sistemas SysV debe implementar el esquema señalado anteriormente. Sin embargo, se recomienda hacer que este comportamiento sea opcional y configurable a través de un argumento de línea de comando para facilitar la depuración y para simplificar la integración en los sistemas que utilizan systemd.

Tenga en cuenta que daemon()no es compatible con POSIX .


Demonios de nuevo estilo

Para demonios de nuevo estilo, se recomiendan los siguientes pasos :

  1. Si SIGTERMse recibe, apague el demonio y salga limpiamente.
  2. Si SIGHUPse recibe, vuelva a cargar los archivos de configuración, si corresponde.
  3. Proporcione un código de salida correcto del proceso del demonio principal, ya que el sistema init lo utiliza para detectar errores y problemas del servicio. Se recomienda seguir el esquema de código de salida definido en las recomendaciones de LSB para los scripts de inicio de SysV .
  4. Si es posible y aplicable, exponga la interfaz de control del demonio a través de D-Bus IPC y tome un nombre de bus como último paso de inicialización.
  5. Para la integración en systemd, proporcione un archivo de unidad .service que contenga información sobre cómo iniciar, detener y mantener el demonio. Versystemd.service(5) para obtener más detalles.
  6. En la medida de lo posible, confíe en la funcionalidad del sistema init para limitar el acceso del demonio a archivos, servicios y otros recursos, es decir, en el caso de systemd, confíe en el control de límite de recursos de systemd en lugar de implementar el suyo propio, confíe en el de systemd. eliminación de privilegios código en lugar de implementarlo en el demonio, y similares. Consulte systemd.exec(5)los controles disponibles.
  7. Si se utiliza D-Bus , haga que su bus daemon sea activable proporcionando un archivo de configuración de activación del servicio D-Bus . Esto tiene múltiples ventajas: su daemon puede iniciarse de forma perezosa bajo demanda; puede iniciarse en paralelo a otros demonios que lo requieran, lo que maximiza la paralelización y la velocidad de inicio ; su daemon puede reiniciarse en caso de falla sin perder ninguna solicitud de bus, ya que el bus pone en cola las solicitudes de servicios activables. Véase a continuación para más detalles.
  8. Si su daemon proporciona servicios a otros procesos locales o clientes remotos a través de un socket, debe habilitarse mediante socket siguiendo el esquema que se indica a continuación . Al igual que la activación de D-Bus, esto permite el inicio bajo demanda de servicios y permite una mejor paralelización del inicio del servicio. Además, para los protocolos sin estado (como syslog, DNS), se puede reiniciar un demonio que implemente la activación basada en socket sin perder una sola solicitud. Véase a continuación para más detalles.
  9. Si corresponde, un demonio debe notificar al sistema init sobre la finalización del inicio o las actualizaciones de estado a través de la sd_notify(3)interfaz.
  10. En lugar de usar la syslog()llamada para iniciar sesión directamente en el servicio syslog del sistema, un demonio de nuevo estilo puede optar por iniciar sesión en el error estándar a través de fprintf(), que luego es reenviado a syslog por el sistema init. Si los niveles de registro son necesarios, estos pueden codificarse prefijando líneas de registro individuales con cadenas como "<4>" (para el nivel de registro 4 "ADVERTENCIA" en el esquema de prioridad syslog), siguiendo un estilo similar al del sistema de printk()niveles del kernel de Linux . Para obtener más información, consulte sd-daemon(3)y systemd.exec(5).

Para obtener más información, lea todo man 7 daemon.

patryk.beza
fuente
11

No puede crear un proceso en Linux que no se pueda matar. El usuario root (uid = 0) puede enviar una señal a un proceso y hay dos señales que no se pueden capturar, SIGKILL = 9, SIGSTOP = 19. Y otras señales (cuando no se detectan) también pueden provocar la terminación del proceso.

Es posible que desee una función de demonización más general, donde puede especificar un nombre para su programa / demonio y una ruta para ejecutar su programa (tal vez "/" o "/ tmp"). Es posible que también desee proporcionar archivo (s) para stderr y stdout (y posiblemente una ruta de control usando stdin).

Aquí están los incluidos necesarios:

#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)

Y aquí hay una función más general,

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);
}

Aquí hay un programa de muestra, que se convierte en un demonio, se queda y luego se va.

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);
}

Tenga en cuenta que SIG_IGN indica captar e ignorar la señal. Puede crear un manejador de señales que pueda registrar la recepción de señales y establecer banderas (como una bandera para indicar un apagado ordenado).

ChuckCottrill
fuente
8

Intente usar la daemonfunción:

#include <unistd.h>

int daemon(int nochdir, int noclose);

Desde la página del manual :

La función daemon () es para programas que deseen separarse del terminal de control y ejecutarse en segundo plano como demonios del sistema.

Si nochdir es cero, daemon () cambia el directorio de trabajo actual del proceso de llamada al directorio raíz ("/"); de lo contrario, el directorio de trabajo actual no se modifica.

Si noclose es cero, daemon () redirige la entrada estándar, la salida estándar y el error estándar a / dev / null; de lo contrario, no se realizan cambios en estos descriptores de archivo.

Weiyin
fuente
2
Tenga en cuenta que el daemon(7)manual menciona los pasos para crear un demonio y advierte que: La daemon()función BSD no debe usarse, ya que implementa solo un subconjunto de estos pasos. daemonLa función apareció por primera vez en 4.4BSD y no es compatible con POSIX .
patryk.beza
2
Tenga en cuenta también que la advertencia sobre el uso de daemon () está en la sección SysV de estilo antiguo de la página de manual de daemon (7) . No se desaconseja el uso de daemon () para systemd.
Greg McPherran
7

Puedo detenerme en el primer requisito "Un demonio que no se puede detener ..."

No es posible mi amigo; sin embargo, puede lograr lo mismo con una herramienta mucho mejor, un módulo del kernel.

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

Todos los demonios se pueden detener. Algunos se detienen más fácilmente que otros. Incluso una pareja de demonios con el compañero en espera, reapareciendo al compañero si se pierde, puede detenerse. Solo tienes que trabajar un poco más en eso.

Edwin Buck
fuente
7
Creo que al decir "Un demonio que no se puede detener", el autor en realidad quiere decir que el demonio siempre se está ejecutando en segundo plano cuando finaliza la sesión.
FaceBro
6

Si su aplicación es una de:

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

y no le importa una dependencia de NodeJS, luego instale NodeJS y luego:

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

Para mantener todas las aplicaciones ejecutándose al reiniciar (y demonizar pm2):

pm2 startup

pm2 save

Ahora usted puede:

service pm2 stop|restart|start|status

(también le permite observar fácilmente los cambios de código en el directorio de su aplicación y reiniciar automáticamente el proceso de la aplicación cuando ocurre un cambio de código)

danday74
fuente
2
Esto no tiene nada que ver con C.
melpomene
4
Aprecio que haya una etiqueta C. Sin embargo, OP no menciona un requisito relativo a C en la pregunta. El título está creando un demonio en Linux. Esta respuesta satisface eso.
danday74
1
Oh, tienes razón. Está etiquetado como C, pero el requisito real es C ++ (como lo demuestra el código de OP y el artículo vinculado).
melpomene
3

Al llamar a fork (), ha creado un proceso hijo. Si la bifurcación tiene éxito (la bifurcación devolvió un PID distinto de cero), la ejecución continuará desde este punto desde el proceso hijo. En este caso, queremos salir elegantemente del proceso padre y luego continuar nuestro trabajo en el proceso hijo.

Quizás esto ayude: http://www.netzmafia.de/skripten/unix/linux-daemon-howto.html

Doug Morrow
fuente
2

Un demonio es solo un proceso en segundo plano. Si desea iniciar su programa cuando arranca el sistema operativo, en Linux, agregue su comando de inicio a /etc/rc.d/rc.local (ejecutar después de todos los demás scripts) o /etc/startup.sh

En Windows, crea un servicio, registra el servicio y luego lo configura para que se inicie automáticamente al arrancar en la administración -> panel de servicios.

Magn3s1um
fuente
1
Gracias. Entonces, ¿no hay diferencia entre un "demonio" y un programa normal? No quiero que se cierre fácilmente.
chrisMe
1
No, un demonio es solo un proceso en segundo plano. Más específicamente, se bifurca desde un padre, ejecuta el proceso hijo y termina el padre (para que no haya acceso de terminal al programa). aunque eso no es relativamente necesario para ser un "daemon": en.wikipedia.org/wiki/Daemon_(computing)
Magn3s1um
1

Plantilla de demonio

Escribí una plantilla de demonio siguiendo el demonio de nuevo estilo: enlace

Puede encontrar el código de plantilla completo en GitHub: aquí

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
Fabiano Traple
fuente