Saltar al contenido

Creando un demonio en Linux

Solución:

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, un demonio de syslog (syslogd) se utiliza para registrar mensajes en archivos (depuración, error, …).

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


Si recuerdo correctamente estos pasos son:

  • tenedor Apague 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 – Crea 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 – Ignorar y / o manejar señales.
  • tenedor de nuevo & deje que el proceso principal finalice para asegurarse de que se deshace del proceso principal de la sesión. (Solo los líderes de sesión pueden volver a obtener un TTY).
  • chdir – Cambiar el directorio de trabajo del demonio.
  • umask – Cambie la máscara del modo de archivo según las necesidades del demonio.
  • cerrar – Cierre todos los descriptores de archivos abiertos que puedan heredarse del proceso principal.

Para darle un punto de partida: mire 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("https://foroayuda.es/");

    /* 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  | TIME | CMD |
+------+------+------+------+-----+-------+------+------+------+-----+
|    1 | 3387 | 3386 | 3386 | ?   |    -1 | S    | 1000 | 0:00 | ./  |
+------+------+------+------+-----+-------+------+------+------+-----+

Lo que debería ver aquí es:

  • El demonio no tiene una terminal de control (TTY =?)
  • El ID del proceso principal (PPID) es 1 (El proceso de inicio)
  • los 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 puedo 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]: First daemon started.
  firstdaemon[3387]: First daemon terminated.

Una nota:
En realidad, también querría implementar un controlador de señales y configurar el registro correctamente (archivos, niveles de registro …).

Otras lecturas:

  • Linux-UNIX-Programmierung – Alemán
  • Programación del servidor Unix Daemon

man 7 daemon describe 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 (estilo nuevo).

Demonios SysV

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

  1. Cerrar todos los descriptores de archivos abiertos excepto los estándar aporte, producción, y error (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 a través de /proc/self/fd, con una alternativa de iterar desde el descriptor de archivo 3 al valor devuelto por getrlimit() por 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 _NSIG y restablecerlos 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() de nuevo, para asegurarse de que el demonio no pueda volver a adquirir una terminal nunca más.
  8. Llamada exit() en el primer hijo, de modo que solo el segundo hijo (el proceso del demonio real) permanece 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, conecte /dev/null al estándar aporte, producción, y error.
  10. En el proceso del demonio, restablezca el umask a 0, de modo que los modos de archivo pasaron a open(), mkdir() y similares controlan 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 devuelve getpid()) a un archivo PID, por ejemplo /run/foobar.pid (para un demonio hipotético “foobar”) para garantizar 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 primera fork() y, por tanto, disponible tanto en el proceso original como en el daemon.
  15. Llamada exit() en el proceso original. El proceso que invocó el demonio debe poder confiar en que este exit() sucede después La inicialización está completa y todos los canales de comunicación externos están establecidos y accesibles.

Tenga en cuenta esta advertencia:

El BSD daemon() función no debe ser utilizado, ya que implementa sólo 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 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 los demonios de estilo nuevo, se recomiendan los siguientes pasos:

  1. Si SIGTERM se recibe, cierre el demonio y salga limpiamente.
  2. Si SIGHUP se 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 tal como se define 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 del sistema 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. Ver systemd.service(5) para 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 la eliminación de privilegios de systemd código en lugar de implementarlo en el demonio, y similares. Ver systemd.exec(5) para 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 demonio puede iniciarse de forma perezosa bajo demanda; puede iniciarse en paralelo a otros demonios que lo requieran, lo que maximiza la velocidad de arranque y paralelización; su demonio se puede reiniciar en caso de falla sin perder ninguna solicitud de bus, ya que el bus pone en cola las solicitudes de servicios activables. Consulte los detalles a continuación.
  8. Si su demonio proporciona servicios a otros procesos locales o clientes remotos a través de un socket, debería ser habilitado por 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 también 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. Consulte los detalles a continuación.
  9. Si corresponde, un demonio debe notificar al sistema init sobre la finalización del inicio o las actualizaciones de estado a través del sd_notify(3) interfaz.
  10. En lugar de usar el syslog() llamar para iniciar sesión directamente en el servicio syslog del sistema, un demonio de nuevo estilo puede elegir simplemente 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 de syslog), siguiendo un estilo similar al del kernel de Linux. printk() sistema de nivel. Para obtener más detalles, consulte sd-daemon(3) y systemd.exec(5).

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

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 “https://foroayuda.es/” 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="https://foroayuda.es/"; }
    if(!name) { name="medaemon"; }
    if(!infile) { infile="/dev/null"; }
    if(!outfile) { outfile="/dev/null"; }
    if(!errfile) { errfile="/dev/null"; }
    //printf("%s %s %s %sn",name,path,outfile,infile);
    pid_t child;
    //fork, detach from process group leader
    if( (child=fork())<0 ) { //failed fork
        fprintf(stderr,"error: failed forkn");
        exit(EXIT_FAILURE);
    }
    if (child>0) { //parent
        exit(EXIT_SUCCESS);
    }
    if( setsid()<0 ) { //failed to become session leader
        fprintf(stderr,"error: failed setsidn");
        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 forkn");
        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 failedn");
        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 capturar e ignorar la señal. Puede crear un controlador de señales que pueda registrar la recepción de señales y establecer indicadores (como un indicador para indicar un apagado correcto).

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *