Category Archives: C

Crear un Demonio en Linux

En esta entrada vamos a explicar cómo proceder para escribir un demonio bajo GNU/Linux. Lo primero es definir que se entiende por un demonio, y citamos a la wikipedia:

Un demonio, daemon o dæmon (de sus siglas en inglés Disk And Execution MONitor), es un tipo especial de proceso informático no interactivo, es decir, que se ejecuta en segundo plano en vez de ser controlado directamente por el usuario. Este tipo de programas se ejecutan de forma continua (infinita), vale decir, que aunque se intente cerrar o matar el proceso, este continuará en ejecución o se reiniciará automáticamente. Todo esto sin intervención de terceros y sin dependencia de consola alguna.

Un ejemplo de demonio es Apache, permanece en ejecución en segundo plano esperando peticiones de páginas web para ser servidas. O cron que es un demonio temporal, es decir, realiza acciones basadas en el tiempo.

El primer paso que debemos tener en cuenta es qué va a realizar el demonio, Linux basa su programación en que cada programa resuelve un único problema pero lo hace bien. Para ilustrar esta entrada construiremos un demonio que supervise si el demonio Apache está funcionando.

Requisitos

Necesitamos un compilador de C, normalmente GCC, y las cabezeras de C para linux, esto en Debian se consigue con:

# apt-get install build-essential

Necesitaremos también un editor para escribir el código.

Estructura básica de un demonio

Durante la ejecución del demonio debemos realizar algunas tareas básicas que se resumen en:

  • Hacer fork del proceso padre.
  • Cambiar la máscara de fichero.
  • Abrir los archivos de registros necesarios.
  • Crear un identificador de sesión único.
  • Cambiar el directorio de trabajo hacia un sitio más seguro.
  • Cerrar los descriptores de ficheros estandard.
  • Escribir el código del demonio propiamente.

El código

He comentado el código con lo más importante y está bastante autoexplicativo:


#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <syslog.h>
#include <string.h>

int main(void) {
  
  pid_t pid, sid;
  int apachePIDfile, apachePIDread, apachePID, daemonLogFileDesc;
  char buf[5], filename[255];        
          
  /* Forkeamos el proceso padre */
  pid = fork();
  if (pid < 0) {
    exit(EXIT_FAILURE);
  }
  /* Cuando tenemos un PID correcto podemos cerrar
   * el proceso padre.
   * Atención al control de errores, es una buena
   * técnica de programación comprobar todas las
   * situaciones donde se pueden dar errores. */
  if (pid > 0) {
    exit(EXIT_SUCCESS);
  }

  /* Cambiamos el modo de la mascara de ficheros */
  /* Hacemos esto para que los fichero generados por el
   * demonio sean accesibles por todo el mundo */
  umask(0);
          
  /* Abrimos los ficheros de logs del demonio */        
  /* Esto es opcional pero como vamos a cerrar los descriptores 
   * hacemos esto para que exista algo de comunicación con el demonio */
  daemonLogFileDesc = open ("log", O_WRONLY | O_CREAT, 0600);
  if (daemonLogFileDesc == -1) {
    perror ("Error en la apertura del log");
    exit (EXIT_FAILURE);
  }
  
  /* Creamos un nuevo SID */
  /* Esto se hace porque al haber matado al padre el hijo puede quedarse 
   * en el sistema como un proceso zombie, generando un nuevo SID hacemos
   * que el sistema se haga cargo del proceso huérfano otorgándole un nuevo SID */
  sid = setsid();
  if (sid < 0) {
    perror("new SID failed");
    exit(EXIT_FAILURE);
  }
    
  /* Por seguridad, cambiamos el directorio de trabajo */
  if ((chdir("/")) < 0) {
    perror("Change the current work directory failed");
    exit(EXIT_FAILURE);
  }
    
  /* Cerramos los descriptores standard */
  /* El demonio no puede usar la terminal, por lo que estos
   * descriptores son inútiles y un posible riesgo de seguridad.*/
  close(STDIN_FILENO);
  close(STDOUT_FILENO);
  close(STDERR_FILENO);
    
  /* El código del demonio */
  /* Obtenemos el PID con el que está corriendo el proceso apache */
  apachePIDfile = open("/var/run/apache2.pid", O_RDONLY, 0600);
  if (apachePIDfile == -1) {
    perror("Error en la apertura del fichero");
    exit(EXIT_FAILURE);
  }
  apachePIDread = read (apachePIDfile, buf, sizeof(buf));
    
  /* El gran bucle! */
  /* El demonio ejecutara este bucle toda su vida,
   * abrirá un archivo del pseudo sistema de ficheros /proc
   * y comprobará que existe, si existe Apache está corriendo y lo escribe
   * en el log, en caso contrario sale. */
  while (1) {
    apachePID = atoi (buf);
    snprintf(filename, sizeof(filename), "/proc/%d/cmdline", apachePID);
  
    if ((open (filename, O_RDONLY, 0600)) == -1) {
      perror ("No puedo abrir el fichero en proc");
      exit(EXIT_FAILURE);
    } else {
      write (daemonLogFileDesc, "Apache running\n", 15);
      sleep(30); /* espera 30 segundos */
    }
  }
  exit(EXIT_SUCCESS);
}

Compilamos

Para compilar hacemos lo siguiente:

# cc daemon1.c -o daemon1

A falta de un nombre más original, he llamado al demonio daemon1.

Ejecución

Para ejecutarlo hacemos:

# ./daemon1

Podemos ver que está corriendo tanto en el lista de procesos con ps como mirando el fichero log que hemos creado.

tailf log
...
Apache running
Apache running
Apache running
...

Referencias

Es una traducción/adaptación de la guía Linux Daemon Writing HOWTO, que es más teórica y aquí he querido dar una aplicación práctica, aunque es obvio que existe demonios de monitorización más avanzado quería marcar bien los conceptos de la construcción de un demonio aplicados a un caso particular.

Dos formas de mandar señales a procesos en Linux

Resulta que por temas de Copyright, no puedo usar el artículo publicado en The Geek Stuff (ver comentarios) así que como su contenido no es original y la temática me parece de conocimiento público, vamos a reescribirlo.

1. Enviar una señal a un proceso usando el comando kill.

Estando en consola, podemos enviar una señal a un proceso mediante el comando kill. Según Wikipedia:

En Unix y los sistemas operativos tipo Unix, kill es un comando utilizado para enviar mensajes sencillos a los procesos ejecutándose en el sistema.

Así si queremos mandar una señal a un proceso con el PID 1234 haríamos lo siguiente:

$ kill -s VALUE 1234

Las señales que le podemos mandar a un proceso son las siguientes (observar Value):

$ man 7 signal
[...]
Signal     Value     Action   Comment
----------------------------------------------------------------------
SIGHUP        1       Term    Hangup detected on controlling terminal or death of controlling process
SIGINT        2       Term    Interrupt from keyboard
SIGQUIT       3       Core    Quit from keyboard
SIGILL        4       Core    Illegal Instruction
SIGABRT       6       Core    Abort signal from abort(3)
SIGFPE        8       Core    Floating point exception
SIGKILL       9       Term    Kill signal
SIGSEGV      11       Core    Invalid memory reference
SIGPIPE      13       Term    Broken pipe: write to pipe with no readers
SIGALRM      14       Term    Timer signal from alarm(2)
SIGTERM      15       Term    Termination signal
SIGUSR1   30,10,16    Term    User-defined signal 1
SIGUSR2   31,12,17    Term    User-defined signal 2
SIGCHLD   20,17,18    Ign     Child stopped or terminated
SIGCONT   19,18,25    Cont    Continue if stopped
SIGSTOP   17,19,23    Stop    Stop process
SIGTSTP   18,20,24    Stop    Stop typed at tty
SIGTTIN   21,21,26    Stop    tty input for background process
SIGTTOU   22,22,27    Stop    tty output for background process
[...]

2. Enviar la señal al proceso a través de otro proceso.

Para enviar señales a un proceso dentro de un programa C se emplea la siguiente llamada.

int kill(int pid, int sig);

La llamada al sistema kill toma dos argumentos:

  1. El PID (process ID, identificador de proceso) del proceso que tiene que ser “señalizado”.
  2. La señal que le enviaremos al proceso. kill devuelve 0 si todo ha ido bien, -1 si ha ocurrido algún error, se asignará el valor apropiado a errno

Vamos a verlo en un ejemplo de mis apuntes de Sistemas Operativos (3º Ingeniería Técnica de Sistemas):

//Envia al proceso con PID pasado como parámetro la señal SIGTERM
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <signal.h>
 
int main(int argc, char *argv[])
{
  if (argc != 2) {
    printf("Uso: %s pid\n", argv[0]);
    exit(EXIT_FAILURE);
  }
  if (kill(atoi(argv[1]), SIGTERM) == -1) {
    perror("fallo en kill");
    exit(EXIT_FAILURE);
  }
}

Es una pena que no se suban al carro de compartir el conocimiento, es sólo una opinión, y hay que respetarla, pero es lo bueno de Internet, la posibilidad de intercambiar la información, no se trata de presentarla como obra tuya, si no de dar a conocer lo que cualquiera quiera, por eso, es de etiqueta, citar fuentes y referencias. En fin, que como decimos por aquí, “hay gente pa tó