Archivio mensile:Novembre 2015

Creare un demone in Qt

Premessa:

Un demone, in linux, è sostanzialmente un processo eseguito in background che offre dei servizi in maniera autonoma. Un processo in background, a differenza dei normali processi in foreground, non ha iterazione con l’utente, quindi non gestisce lo standard di input (ad esempio la tastiera) e lo standard di output (ad esempio in monitor), nonché il canale standard degli errori (ad esempio il terminale che ha avviato il processo). Il demone viene controllato dal sistema operativo per essere avviato e quindi arrestato in determinate fasi. Per fare questo è necessario corredarlo da uno script che verrà utilizzato dal kernel per il totale controllo del processo.

Scrivere un demone per linux:

Prima di passare alla creazione di un demone in Qt è necessario creare un demone indipendente dalla libreria. Il demone è compilabile solo in ambiente linux, ed in particolare, in questo esempio, per la distribuzione Debian e la sua derivata Ubuntu. Il demone è un programma normalissimo che parte nella funzione main, ma prima del corpo principale effettua una serie di istruzioni racchiuse per comodità nella funzione daemonize.

int main(int argc,char *argv[])
  {
  daemonize();
  
  // Core
  // ...
  // ...
  
  return(EXIT_SUCCESS);
  }

La funzione daemonize permette di effettuare le seguenti operazioni:

  • crea un fork del processo (una copia di se stesso) e termina quello principale. In questo modo il processo che viene chiamato dallo script controllato dal kernel termina immediatamente.
  • modifica la maschera di gestione dei file, permettendo al demone di operare con i massimi privilegi.
  • crea un nuovo sid nel sistema operativo altrimenti, visto che il padre è stato terminato, resterebbe un processo orfano.
  • è consigliabile collegare i segnali del sistema operativo di SIGTERM (quando viene richiesta la terminazione del processo) e SIGHUP (quando viene richiesta una ricarica della configurazione).
  • è consigliabile modificare la directory corrente in quella di root (perché si è certi che esiste), oppure in un’altra conosciuta.
void daemonize(void)
  {
  pid_t pid,sid;
  
  // Fork off the parent process
  pid=fork();
  if(pid<0)
    {
    exit(EXIT_FAILURE); // Fork error
    }

  if(pid>0)
    {
    exit(EXIT_SUCCESS); // This is a parent
    }

  // This is a child

  umask(0); // Change umask
  
  sid=setsid(); // New process group
  
  if(sid<0)
    {
    exit(EXIT_FAILURE);
    }
  
  // Connect the signals
  signal(SIGCHLD,SIG_IGN);
  signal(SIGTSTP,SIG_IGN);
  signal(SIGTTOU,SIG_IGN);
  signal(SIGTTIN,SIG_IGN);
  signal(SIGHUP,signalHandler);
  signal(SIGTERM,signalHandler);
  
  // Change current directory
  if((chdir("/"))<0)
    {
    exit(EXIT_FAILURE);
    }
  }

Dopo aver chiamato la funzione daemonize il nuovo processo è pronto per eseguire il codice vero e proprio.

Scrivere un demone per la libreria Qt:

Uno dei vantaggi della libreria Qt è quello della gestione degli eventi. Integrando la libreria Qt in un demone siamo in grado di sfruttare al meglio tutti i vantaggi di creare un processo in background controllato dal sistema operativo ma scritto con un eccellente libreria di supporto. Il primo passo è quello di creare una funzione engine che permetta di creare un’istanza di QCoreApplication (il demone non può avere interfaccia grafica), collegare i segnali del sistema operativo, istanziare il Core della nostra applicazione e quindi avviare il gestore eventi.

int engine(int argc,char *argv[])
  {
  QCoreApplication a(argc,argv);
  
  Core core;
  
  return(a.exec());
  }

Il main assumerà quindi questa forma:

int main(int argc,char *argv[])
  {
  daemonize();
  return(engine(argc,argv));
  }

Una volta preparato il demone si tratta quindi di eseguire un normalissimo programma Qt che però non interagisce con l’utente.

Se vogliamo conoscere la versione del demone e quindi avere un piccola iterazione con esso possiamo gestirlo parzialmente come un normale programma.

bool command(int argc,char *argv[])
  {
  if((argc==2)&&(QString(argv[1]).toLower()==QString("version")))
    {
    QTextStream stream(stdout);
    stream << "Daemon version X.Y.Z\n";
    stream.flush();
    return(true);
    }
  return(false);
  }

int main(int argc,char *argv[])
  {
  if(command(argc,argv))
    {
    return(EXIT_SUCCESS);
    }

  daemonize();
  return(engine(argc,argv));
  }

In questo modo se alla chiamata del processo si aggiunge il parametro version il demone si comporta come un programma normale, stampando a video la sua versione.

Controllo del demone:

Un demone per essere installato nel sistema operativo, in particolare con la distribuzione Debian, deve essere corredato da uno script di supporto. Lo script permette di gestire dei parametri che ne controllano il suo avvio, la sua terminazione, il riavvio, lo stato ed eventuali opzioni come per esempio la richiesta della versione.

#!/bin/bash

# Get lsb function
. /lib/lsb/init-functions

case "$1" in
  start)
    echo "Starting Daemon"
    if start-stop-daemon --start --quiet --oknodo --exec /opt/daemon/daemond; then
    echo "ok"
    else
    echo "error"
    fi
    ;;
  stop)
    echo "Stopping Daemon"
    if start-stop-daemon --stop --quiet --oknodo  --exec /opt/daemon/daemond; then
    echo "ok"
    else
    echo "error"
    fi
    ;;
  restart)
    echo "Restarting Daemon"
    $0 stop
    $0 start
    ;;
  version)
    /opt/daemon/daemond version
    ;;
  status)
    status_of_proc /opt/daemon/daemond daemon
    ;;
  *)
    echo "Usage: /etc/init.d/daemon {start|stop|restart|status}"
    ;;
esac

exit 0

Per migliorare la leggibilità è consigliabile aggiungere al nome del demone il suffisso d, mentre lo script lasciarlo con il nome originario. Per esempio il demone si potrebbe chiamare demoned mentre lo script solo demone.

Installazione:

Il demone prima di tutto deve essere copiato in una directory del sistema operativo, ad esempio /opt/daemon
Lo script invece deve essere copiato nella directory /etc/init.d con i permessi di esecuzione.
Lanciando con l’utente di root il comando ‘update-rc.d demone defaults’ viene installato lo script di controllo del demone nei vari stati del sistema operativo (avvio, funzionamento, arresto, ecc). Eseguendo il riavvio della macchina il demone viene avviato.

Per interagire con il demone, sempre con l’utenza di root, è necessario utilizzare il comando service seguito dal nome del demone e quindi dal parametro che verrà passato allo script.
Ad esempio:
service demone status

Conclusioni:

Poter creare un demone in linux è molto importante per elaborare servizi indipendenti dall’iterazione con l’utente, farlo in Qt è ancora più interessante. Una progettazione lungimirante permette di realizzare un software che potrà essere eseguito sia normalmente che come demone.

Esempio completo in Qt5