Un dimoni és una aplicació informàtica o procés que s'executa en segon pla. Un dimoni té les següents característiques:
Podeu descarregar tots els exemples de cop si executeu:
svn checkout https://svn.lafarga.cpl.upc.edu/dades/svn/plinux/sessio5
En cas que no tingueu subversion instal·lat podeu executar:
$ sudo apt-get install subversion
Exemple:
//El primer que fem és executar un fork per executar el procés //en segon pla status = fork(); switch (status) { case -1: perror(fork()); exit(1); case 0: /* El procés fill continua amb l'execució del programa */ break; default: /* El procés pare finalitza */ exit(0); } //resta del dimoni...
Exemple:
resourceLimit.rlim_max = 0; status = getrlimit(RLIMIT_NOFILE, &resourceLimit); if (-1 == status) /* shouldn't happen */ { perror("Error executant getrlimit()"); exit(1); } if (0 == resourceLimit.rlim_max) { //fprintf("El nombre màxim de fitxers oberts és 0!!\n"); exit(1); } for (i = 0; i < resourceLimit.rlim_max; i++) { (void) close(i); }
Exemple:
status = setsid(); if (-1 == status) { perror(setsid()); exit(1); }
Exemple:
status = fork(); switch (status) { case -1: perror("Error a l'executar fork()"); exit(1); case 0: /* segon procés fill */ break; default: /* Parem el pare */ exit(0); }
Exemple:
fileDesc = open("/dev/null", O_RDWR);/* stdin */ (void) dup(fileDesc); /* stdout */ (void) dup(fileDesc); /* stderr */
chdir("/");
umask(0);
#define PID_FILE "/var/run/echod.pid" int pid_file; char str[30]; pid_file=open(PID_FILE,O_RDWR|O_CREAT,0640); sprintf(str,"%d\n",getpid()); write(pid_file,str,strlen(str));
Recursos:
svn checkout https://svn.lafarga.cpl.upc.edu/dades/svn/plinux/sessio5/echoDimoni
Si no teniu el subversion instal·lat executeu:
$ sudo apt-get install subversion
El següent exemple converteix el servidor d'echos de l'article Programació en xarxes en un dimoni Linux, tot seguint la guia de bones pràctiques de la secció anterior:
#include <sys/time.h> #include <sys/resource.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <stdio.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <netdb.h> int port = 8000; #define MAXBUFLEN 2048 int main(int argc, char *argv) { struct rlimit resourceLimit = { 0 }; int status = -1; int fileDesc = -1; int i; struct sockaddr_in sin; struct sockaddr_in pin; int sock_descriptor; int temp_sock_descriptor; int address_size; char buf[MAXBUFLEN]; int len; /* * El primer que fem és executar un fork per executar el procés * en segon pla */ status = fork(); switch (status) { case -1: perror(fork()); exit(1); case 0: /* El procés fill continua amb l'execució del programa */ break; default: /* El procés pare finalitza */ exit(0); } //continuació del procés fill. resourceLimit.rlim_max = 0; status = getrlimit(RLIMIT_NOFILE, &resourceLimit); if (-1 == status) /* shouldn't happen */ { perror("Error executant getrlimit()"); exit(1); } if (0 == resourceLimit.rlim_max) { //fprintf("El nombre màxim de fitxers oberts és 0!!\n"); exit(1); } for (i = 0; i < resourceLimit.rlim_max; i++) { (void) close(i); } status = setsid(); if (-1 == status) { perror(setsid()); exit(1); } status = fork(); switch (status) { case -1: perror("Error a l'executar fork()"); exit(1); case 0: /* segon procés fill */ break; default: /* Parem el pare */ exit(0); } /* * Ara estem a una nova sessió i a un nou grup de processos * que els que hi havien a l'inici de l'execució de dimoni. * Tampoc no tenim un terminal controlador */ chdir("/"); umask(0); fileDesc = open("/dev/null", O_RDWR);/* stdin */ (void) dup(fileDesc); /* stdout */ (void) dup(fileDesc); /* stderr */ // A partir d'aquí executem el codi del dimoni sock_descriptor = socket(AF_INET, SOCK_STREAM, 0); if (sock_descriptor == -1) { perror("Error al cridar socket..."); exit(1); } memset(&sin,0 , sizeof(sin)); sin.sin_family = AF_INET; sin.sin_addr.s_addr = INADDR_ANY; sin.sin_port = htons(port); if (bind(sock_descriptor, (struct sockaddr *)&sin, sizeof(sin)) == -1) { perror("Error al cridar bind..."); exit(1); } if (listen(sock_descriptor, 20) == -1) { perror("Error al cridar listen..."); exit(1); } printf("El servidor esta a l'espera de rebre connexions ...\n"); // El bucle infinit fa que el servidor es quedi escoltant noves peticions // permanentment. Cada nova petició, s'obre un nou socket per fer la // la comunicació entre el client i el servidor. while(1) { address_size=sizeof(pin); temp_sock_descriptor = accept(sock_descriptor, (struct sockaddr *)&pin, &address_size); if (temp_sock_descriptor == -1) { perror("Error al cridar accept..."); exit(1); } if (recv(temp_sock_descriptor, buf, MAXBUFLEN, 0) == -1) { perror("Error al cridar recv..."); exit(1); } printf("S'ha rebut del client el següent String:%s\n", buf); if (send(temp_sock_descriptor, buf, strlen(buf), 0) == -1) { perror("Error al cridar send"); exit(1); } close(temp_sock_descriptor); } return 0; }
El més útil per veure com evoluciona el procés servidor (creació de fills en fork, canvis en descriptors de fitxers, etc.) és utilitzar el monitor de sistema de Gnome:
Un cop tingueu l'applet, aneu al menú visualitza i activeu tots els processos i les dependències. Aneu també a Edita/Preferències i activeu totes les columnes de visualització.
Si descarregueu [aquesta versió] modificada (amb sleeps als punts clau) del servidor d'ecos, podreu observar els canvis pel quals passa el procés servidor. Obriu una terminal, i identifique-la al monitor del sistema (si cal executeu una comanda com sleep 5):
Ara executeu el servidor:
./echod_estats
Anoteu l'identificador de procés inicial per comparar-lo amb el final. Aneu veient com evoluciona el procés, des de fill del bash, a procés orfe que és adoptat pel procés init, a com es torna a fer un fill del fill:
Veureu com el procés final ja no és el mateix que el procés pare:
$ ps aux | grep echod_estats sergi 14407 0.0 0.0 1504 244 ? S 13:55 0:00 ./echod_estats
Per comprovar com realment s'apunten els descriptors de fitxers al dispositiu /dev/nulll podeu utilitzar la comanda lsof:
$ lsof | grep echod ................... echod 12544 sergi 0u CHR 1,3 8226 /dev/null echod 12544 sergi 1u CHR 1,3 8226 /dev/null echod 12544 sergi 2u CHR 1,3 8226 /dev/null echod 12544 sergi 3u IPv4 43182 TCP *:8000 (LISTEN)
També es poden consultar, seleccionant el procés al monitor de sistemes i amb el menú contextual (botó dret) escollir obrir fitxers.
Per defecte s'ignoren totes les senyals excepte:
void ReapChild(int pid); void sortir(int pid); void reload(int pid); struct sigaction reapAction = { ReapChild, 0, SA_RESTART,NULL }; void ReapChild(int pid) { int status; wait(&status); } struct sigaction sigtermAction = { sortir, 0, SA_RESTART,NULL }; void sortir(int pid) { openlog("echod", 0, LOG_USER); syslog (LOG_INFO, "Rebuda la senyal SIGTERM. Finalitzant l'execució..."); exit (0); } struct sigaction sighupAction = { reload, 0, SA_RESTART,NULL }; void reload(int pid) { openlog("echod", 0, LOG_USER); syslog (LOG_INFO, "Reconfigurant el servidor..."); } int main(int argc, char *argv) { ............ //Control de senyals sigaction(SIGCHLD, &reapAction, NULL); sigaction(SIGTERM, &sigtermAction, NULL); sigaction(SIGHUP, &sighupAction, NULL); ............. }
El següent exemple mostra el Programació_de_Dimonis_Linux#Exemple._Dimoni_d.27echosdimoni servidor d'ecos d'apartats anteriors amb control de senyals. També hem aprofitat per afegir l'única recomanació de l'apartat bones pràctiques que encara no havíem implementat, el fitxer PIDFILE.
svn checkout https://svn.lafarga.cpl.upc.edu/dades/svn/plinux/sessio5/echoDimoniSenyals
Per provar les senyals podeu executar:
$ ps aux | grep echod root 14167 0.0 0.0 1508 280 ? S 10:38 0:00 /usr/sbin/echod root 14175 0.0 0.0 2900 764 pts/4 R+ 10:38 0:00 grep echod $ sudo kill -1 14167
$ ps aux | grep echod root 14167 0.0 0.0 1508 280 ? S 10:38 0:00 /usr/sbin/echod root 14175 0.0 0.0 2900 764 pts/4 R+ 10:38 0:00 grep echod $ sudo kill -15 14167
Per la resta de senyals consulteu el manual de kill.
Comproveu també que es crea un fitxer PIDFILE:
$ ps aux | grep echod root 14167 0.0 0.0 1508 280 ? S 10:38 0:00 /usr/sbin/echod sergi 14263 0.0 0.0 2896 760 pts/2 R+ 10:40 0:00 grep echod $ sudo cat /var/run/echod.pid 14167
Com hem comentat anteriorment, una de les caracterítiques principals dels dimonis és que no tenen una interfície directa amb els usuaris. Què passa doncs si volem notificar errors o enregistrar esdeveniments del dimoni?
En aquests casos és quan entra en joc el registre del sistema Syslog. Els sistemes Unix tenen un servei centralitzat de registre (logging en angles) on podem notificar els errors o esdeveniments del nostres dimonis. Syslog és una utilitat client-servidor que permet a les aplicacions (clients) enregistrar els seus esdeveniments en un fitxer centralitzat (servidor). Els missatges són guardats al servidor en diferents fitxers segons la configuració del fitxer /etc/syslog.conf.
El fitxer include:
/usr/include/syslog.h
Ens proporciona les següents funcions:
void openlog(char *ident, int option, int facility); void syslog(int priority, char *format, ...); void closelog(void);
La funció openlog() crea una connexió amb syslog. La cadena de caràcters ident és afegida a cada missatge i correspon normalment al nom del dimoni. Això permet identificar quines línies del registre pertanyen al nostre dimoni en el cas que compartim el registre amb altres aplicacions. El paràmetre option permet fer el log a la consola en cas d'error. L'argument facility classifica el tipus de programa o dimoni que estem executant. Per defecte LOG_USER.
La funció syslog() és l'encarregada d'afegir un missatge al registre. Els arguments són similars als de la funció printf() amb excepció de %m que serà reemplaçat pel missatge d'error per al valor actual de errno. El paràmetre priority indica la importància del missatge.
La funció closelog tanca la connexió amb syslog. És opcional.
Les utilitats de syslog estan incorporades al paquet libc6-dev:
$ dpkg -S /usr/include/syslog.h libc6-dev: /usr/include/syslog.h
Recursos:
svn checkout https://svn.lafarga.cpl.upc.edu/dades/svn/plinux/sessio5/echoDimoniSysLog
SVN:
$ svn chechout https://svn.lafarga.cpl.upc.edu/dades/svn/plinux/sessio5/echoDimoni
Els dimonis són processos que s'executen en segon pla i que normalment no són cridats explícitament per l'usuari. És el sistema qui s'encarrega de controlar l'execució de l'script. Molts sistemes Linux utilitzen el sistema d'arrancada de SystemV (entre ells Debian i Ubuntu).
Si voleu conèixer amb més detall com funciona aquest sistema consulteu l'article Configuració de serveis en Linux. Daemons. Des de la perspectiva del programador ens interessa conèixer les particularitats dels scripts d'inicialització System V.
Tal i com podem veure executant:
$ dpkg -S /etc/init.d/skeleton initscripts: /etc/init.d/skeleton
El paquet initscripts ens proporciona un esquelet de com haurien de ser aquests scripts en Debian. La primera secció conté tags informatius sobre l'script:
### BEGIN INIT INFO # Provides: skeleton # Required-Start: $local_fs $remote_fs # Required-Stop: $local_fs $remote_fs # Default-Start: 2 3 4 5 # Default-Stop: S 0 1 6 # Short-Description: Example initscript # Description: This file should be used to construct scripts to be # placed in /etc/init.d. ### END INIT INFO
Després comença la declaració de variables que permet fer que l'script sigui més robust i fàcil d'utilitzar:
# PATH should only include /usr/* if it runs after the mountnfs.sh script PATH=/usr/sbin:/usr/bin:/sbin:/bin DESC="Description of the service" NAME=daemonexecutablename DAEMON=/usr/sbin/$NAME DAEMON_ARGS="--options args" PIDFILE=/var/run/$NAME.pid SCRIPTNAME=/etc/init.d/$NAME
La gràcia del fitxer skeleton és que configurant correctament aquests paràmetres podem tenir un script d'inicialització System V per als nostres serveis sense necessitat de tocar garirabé res més de la resta del fitxer.
Les següents línies controlen que existeix el dimoni abans de continuar.
# Exit if the package is not installed [ -x "$DAEMON" ] || exit 0
NOTA: Recordeu que una desinstal·lació (P. ex. apt-get remove) sense l'opció purge (--purge) mantindrà els fitxers de configuració del paquet localitzats a la carpeta o subcarpetes /etc.
Per conveni s'estableix que el fitxer /etc/default/$NAME, si existeix, contindrà els paràmetres per defecte que configuren el dimoni.
# Read configuration variable file if it is present [ -r /etc/default/$NAME ] && . /etc/default/$NAME
Tot seguit incloem, si existeix, el fitxer /etc/default/rcS.
# Load the VERBOSE setting and other rcS variables
[ -f /etc/default/rcS ] && . /etc/default/rcS
I ara s'inclouen les funcions de log de LSB:
# Define LSB log_* functions. # Depend on lsb-base (>= 3.0-6) to ensure that this file is present. . /lib/lsb/init-functions
El següent són les definicions de les funcions encarregades d'executar, recarregar o parar el dimoni. La primera funció és start:
do_start() {
# Return # 0 if daemon has been started # 1 if daemon was already running # 2 if daemon could not be started start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \ || return 1 start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- \ $DAEMON_ARGS \ || return 2 # Add code here, if necessary, that waits for the process to be ready # to handle requests from services started subsequently which depend
# on this one. As a last resort, sleep for some time. }
start-stop-daemon és un binari proporcionat pel paquet dpkg:
$ dpkg -S /sbin/start-stop-daemon dpkg: /sbin/start-stop-daemon
Tal i com podem llegir al manual d'start-stop daemon la seva funció és encarregar-se de comprovar si el servei que volem executar ja s'estava executant abans de tornar-lo a executar. La primera línia:
start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null || return 1
Comprova si el procés ja existeix. Si existeix l'script s'atura tornant un 1. La segona línia:
start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- $DAEMON_ARGS || return 2
Executa el servei o retorna 2 si hi ha algun problema.
La següent part de l'script s'encarrega de la parada del servei. Aquesta funció és força equivalent a la funció anterior (start):
do_stop() {
# Return # 0 if daemon has been stopped # 1 if daemon was already stopped # 2 if daemon could not be stopped # other if a failure occurred start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $NAME RETVAL="$?" [ "$RETVAL" = 2 ] && return 2 # Wait for children to finish too if this is a daemon that forks # and if the daemon is only ever run from this initscript. # If the above conditions are not satisfied then add some other code # that waits for the process to drop all resources that could be # needed by services started subsequently. A last resort is to # sleep for some time. start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON [ "$?" = 2 ] && return 2 # Many daemons don't delete their pidfiles when they exit. rm -f $PIDFILE return "$RETVAL"
}
I finalment la funció reload:
# # Function that sends a SIGHUP to the daemon/service # do_reload() {
# # If the daemon can reload its configuration without # restarting (for example, when it is sent a SIGHUP), # then implement that here. # start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME return 0
}
Tal i com ja hem comentat anteriorment a la secció Dimonis i senyals, els dimonis han de capturar la senyal 'SIGHUP que serà l'encarregada de recarregar el servei (normalment tornar a llegir els seus fitxers de configuració). Com podeu veure, start-stop-daemon té un paràmetre (--signal) que permet enviar senyals al dimoni.
Les ultimes línies contenen la lògica d'execucio de l'script. Es tracta d'una estructura case que segons el valor del primer paràmetre (start, stop, reload, etc.) crida la funció previàment definida que correspongui:
case "$1" in start)
[ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME" do_start case "$?" in 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; esac ;;
stop)
[ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME" do_stop case "$?" in 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; esac ;;
#reload|force-reload)
# # If do_reload() is not implemented then leave this commented out # and leave 'force-reload' as an alias for 'restart'. # #log_daemon_msg "Reloading $DESC" "$NAME" #do_reload #log_end_msg $? #;;
restart|force-reload)
# # If the "reload" option is implemented then remove the # 'force-reload' alias # log_daemon_msg "Restarting $DESC" "$NAME" do_stop case "$?" in 0|1) do_start case "$?" in 0) log_end_msg 0 ;; 1) log_end_msg 1 ;; # Old process is still running *) log_end_msg 1 ;; # Failed to start esac ;; *) # Failed to stop log_end_msg 1 ;; esac ;;
*)
#echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2 echo "Usage: $SCRIPTNAME {start|stop|restart|force-reload}" >&2 exit 3 ;;
esac
Fixeu-vos que és en aquesta secció on s'utiltizen les funcions de log LSB (log_daemon_msg, log_end_msg 1 etc) i el control dels paràmetres d'execució.
Podeu trobar un altre exemple d'esquelet aquí.
Recordeu que l'usuari i la contrasenya d'accés a SVN són anonymous i anonymous respectivament.
Recursos:
$ dpkg -S /lib/lsb/init-functions lsb-base: /lib/lsb/init-functions
El fitxer /lib/lsb/init-functions proporciona d'utilitats de logging (registre) als dimonis Linux. Només comentarem les funcions:
Aquestes funcions ens permeten mostrar els típics missatges d'execució de serveis:
Per sistemes Red Hat, les funcions equivalents es troben a: /etc/rc.d/init.d/functions.
Recursos:
### BEGIN INIT INFO # Provides: skeleton # Required-Start: $local_fs $remote_fs # Required-Stop: $local_fs $remote_fs # Default-Start: 2 3 4 5 # Default-Stop: S 0 1 6 # Short-Description: Example initscript # Description: This file should be used to construct scripts to be # placed in /etc/init.d. ### END INIT INFO
1. chkconfig: <startlevellist> <startpriority> <endpriority>
Required. <startlevellist> is a list of levels in which the service should be started by default. <startpriority> and <endpriority> are priority numbers. For example: # chkconfig: 2345 20 80 Read 'man chkconfig' for more information.
Unless there is a VERY GOOD, EXPLICIT reason to the contrary, the <endpriority> should be equal to 100 - <startpriority>
1. config:
Optional, multiple entries allowed. For each static config file used by the daemon, use a single entry. For example:
1. config: /etc/httpd/conf/httpd.conf 2. config: /etc/httpd/conf/srm.conf
Optionally, if the server will automatically reload the config file if it is changed, you can append the word "autoreload" to the line:
1. config: /etc/foobar.conf autoreload 2. pidfile:
Optional, multiple entries allowed. Use just like the config entry, except that it points at pidfiles. It is assumed that the pidfiles are only updated at process creation time, and not later. The first line of this file should be the ASCII representation of the PID; a terminating newline is optional. Any lines other than the first line are not examined.
1. probe: true
Optional, used IN PLACE of processname, config, and pidfile. If it exists, then a proper reload-if-necessary cycle may be acheived by running these commands:
command=$(/etc/rd.d/init.d/SCRIPT probe) [ -n "$command" ] && /etc/rc.d/init.d/SCRIPT $command
where SCRIPT is the name of the service's sysv init script.
Scripts that need to do complex processing could, as an example, return "run /var/tmp/<servicename.probe.$$" and implement a "run" command which would execute the named script and then remove it. Note that the probe command should simply "exit 0" if nothing needs to be done to bring the service into sync with its configuration files.
El fitxer /usr/share/initscripts/default.rcS conté una sèrie de variables que controlen l'execució dels scripts d'inicialització:
$ cat /usr/share/initscripts/default.rcS
Quantes vegades heu perdut un fitxer que havieu descarregat de la web obrint en comptes de desant, i al reiniciar la màquina s'havia esborrat de la carpeta /tmp? Això es pot evitar indicant el nombre de dies que voleu que es mantinguin els fitxers a la carpeta tmp canviant la variable TMPTIME. Per exemple 30 dies:
$ sudo joe /usr/share/initscripts/default.rcS TMPTIME=30 SULOGIN=no DELAYLOGIN=no UTC=yes VERBOSE=no FSCKFIX=no
També hi ha el fitxer /etc/default/rcS:
$ cat /etc/default/rcS # # Defaults for the boot scripts in /etc/rcS.d. # This file originates from package initscripts. Because of # bug #213907, it is not possible to tell this to dpkg. # # Time files in /tmp are kept in days. TMPTIME=0 # Set to yes if you want sulogin to be spawned on bootup SULOGIN=no # Set to no if you want to be able to login over telnet/rlogin # before system startup is complete (as soon as inetd is started) DELAYLOGIN=no # Set UTC=yes if your system clock is set to UTC (GMT), and UTC=no if not. UTC=no # Set VERBOSE to "no" if you would like a more quiet bootup. VERBOSE=yes # Set EDITMOTD to "no" if you don't want /etc/motd to be regenerated # automatically EDITMOTD=yes # Set FSCKFIX to "yes" if you want to add "-y" to the fsck at startup. FSCKFIX=no
Per a més informació consulteu man 5 rcS.
Utilitzant el fitxer /etc/init.d/skeleton, creeu un fitxer init script pel dimoni d'ecos.
En aquest apartat podeu trobar una solució completa del servidor d'ecos amb les següents funcionalitats:
Podeu obtenir els fitxers de la solució a:
$ svn checkout https://svn.lafarga.cpl.upc.edu/dades/svn/plinux/sessio5/echoDimoniSolucio
TODO
Quan es vol executar un dimoni/servidor Linux, s'executa un programa que crea el socket i es posa en mode LISTENING a l'espera de rebre peticions dels clients.
Exemples de dimonis autònoms:
Normalment la majoria de dimonis tenen unes característiques comunes:
$kill -HUP comanda
Veieu l'article Configuració de serveis en Linux. Dimonis per obtenir més informació sobre l'administració i configuració de dimonis.
El sistema de dimonis autònoms té el següents inconvenients:
Llavors es va proposar la idea de tenir un sol dimoni, configurat per tal d'escoltar peticions de múltiples serveis. Així van néixer els "super-servidors d'Internet", primer inetd i després xinetd. "Internet super-server." Xinetd és un reemplaçament més segur de inetd.