Démoni

Démon (anglicky daemon) je program, jehož účelem je zpomalit start systému a zabírat systémové zdroje, aniž by o něm uživatel něco věděl.

Ale ne, teď vážně, démoni jsou programy, které běží ve vašem počítači na pozadí, bez přímé komunikace s uživatelem. Obvykle čekají na nějakou událost, kterou pak ošetří. Jako démon běží třeba databázový server, který čeká na požadavky databázového klienta, http server, který čeká na požadavek od http prohlížeče, tisková fronta atp.

V této kapitole vám ukáži, jak napsat démona a jak nakonfigurovat jeho automatické spouštění při startu.

Démon

Démon je program jako každý jiný. Jen musíte zajistit jeho spuštění na pozadí a to, aby se neukončil, když ukončíte svoje sezení (odhlásíte se z terminálu, kde byl program spuštěný).

Jednou z možností, jak zajistit neukončení programu při odhlášení je použití nohup. Pokud napíšete démona třeba v javě, asi vám ani nic jiného nezbyde. Já vám ale ukáži, jak na to v C.

Typický postup při programování démona je:

  1. Odpojení od terminálu (pomocí fork())
  2. Nastavení umask(0)
  3. Otevření souborů (logů atp.)
  4. Vytvoření nového session ID (pomocí setsid())
  5. Změna aktuálního adresáře (pomocí chdir())
  6. Zavření standardních IO
  7. Zachytávání signálů
  8. Spuštění práce démona

Krom toho je ještě dobré uložit do nějakého souboru PID démona, aby mohl uživatel snadno zjistit, zda démon běží.

Odpojení od terminálu

Když spustíte program v terminálu, terminál čeká na jeho dokončení a vy, jako uživatelé, komunikujete se spuštěným programem. Terminál sleduje spuštěný proces.

Pokud spustíte nový proces pomocí fork() a ten původní ukončíte, vrátíte se do terminálu a nový proces poběží na pozadí.

Nastavení umask

Nastavení umask na 0 se doporučuje proto, aby démon nebyl ovlivněn nastavením prostředí a pracoval tak vždycky a všude stejně.

Nastavení na 0 znamená, že se nebudou odebírat žádná práva.

#include <sys/types.h>
#include <sys/stat.h>
mode_t umask(mode_t mask);

Otevření souborů

Předtím, než uzavřete standardní IO (vstup a výstup) je dobré otevřít všechny prostředky, které bude démon potřebovat (obvykle soubory).

Dokud nezavřete stderr, můžete ještě pořád vypsat chybové hlášení v případě, že se něco nepovede.

Soubor s PID démona

Démon by měl uložit do nějakého souboru svůj PID. Použití tohoto souboru uvidíte v části popisující automatické spuštění démona.

Vytvoření nového session ID

Už víte, že každý proces v Linuxu má své ID a také rodičovské ID. Nový proces vytvořený forkem má jako rodiče původní proces. Když rodiče zabijete, bude mít jako rodiče proces s id 1 (init).

Kromě těchto ID má ale každý proces také session ID. Když se třeba odhlašujete od terminálu, jádro potřebuje zabít všechny procesy, které jste v tomto terminálu spustili. K zjednodušení tohoto úkolu združuje kernel všechny procesy pod jedno session ID.

Každý proces dědí session ID od svého rodiče. Pomocí funkce setsid() můžete vytvořit nové session ID, které bude stejné jako ID procesu, a tím se z původního session vyloučit. Díky tomu nebude démon zabit, když se odhlásíte.

#include <unistd.h>
pid_t setsid(void);

Proces se zároveň stane tzv. session lídrem. Všechny procesy které spustí (např. pomocí execl()), získají stejné session ID.

Změna aktuálního adresáře

Nikdy nevíte, odkud bude démon spuštěn, takže je dobré mu napevno nastavit aktuální adresář pro případ, že byste někdy používali relativní cesty.

Nejlepší je nastavit aktuální adresář na /. Nebude tak docházet k žádným problémům při připojování a odpojování disků. (Disk, jehož složku má nějaký program jako aktuální adresář, obvykle nejde jednoduše odpojit. A pokud se násilně odpojí, obvykle to znamená pro takový program katastrofu).

Zavření standardních IO

Démon běží na pozadí, takže stdin, stdout ani stderr nepotřebuje, proto je dobré je uzavřít.

Může se ale stát, že démon spustí nějakou funkci nebo program, který očekává, že je některý z těcho standardních IO otevřen. Proto je dobré znovu otevřít souborové proudy s čísli deskriptorů standardních IO. Obvykle stačí otevřít /dev/null. Když se z něho někdo pokusí číst, dostane ihned „konec souboru“. Při zápisu do /dev/null se zase všechno potichu a bez řečí zapíše a zmizí. Můžete samozřejmě stdout a stderr přesměrovat do nějakého logovacího souboru …

Zachytávání signálů

Démon může komunikovat s okolím pomocí jakékoliv IPC technologie. Určitě by měl ale zachytávat minimálně tyto dva signály:

SIGHUP se obvykle používá pro znovunačtení konfigurace / restart démona.

SIGTERM se používá pro ukončení démona. Démon by tento signál měl zachytit a nějak mírumilovně a rychle se ukončit (uvolnit získanou paměť, uzavřít komunikaci atp.).

Spuštění práce démona

Když už je vše připraveno, může začít démon čekat na událost, kterou obsouží. Může poslouchat na nějaké pipe nebo socketu, používat semafory, či co vás napadne. Já v příkladě spustím nekonečný cyklus, který se vždy na 5 sekund zastaví a pak zapíše větu do logu.

Příklad

V tomto příkladu ukáži všechny výše zmíněné kroky.

Jako první se podívejte na funkce, které bude používat démon pro zápis do logu. Démon do něj bude zapisovat každých 5 vteřin "Pracuji.", pak taky informaci o požadavku na znovunačtení konfigurace (HUP signál) a nakonec informaci o svém ukončení (při ukončení signálem TERM).

Navíc zapíše na začátku nepovinný argument, který budu používat pro odlišení spuštěných instancí démona.

Hlavičkový soubor je jednoduchý:

  1. /*------------------------------------------------*/
  2. /* 25daemon/log.h                                 */
  3. #ifndef _LOG_H
  4. #define _LOG_H
  5. /**
  6.  * Otevře soubor
  7.  * @return 0 - soubor se podarilo otevrit
  8.  *         2 - chyba
  9.  */
  10. int openLog();
  11. int writeLog(char *format, ...);
  12. int closeLog();
  13. #endif
  14. /*------------------------------------------------*/

Definice funkcí taky nejsou nijak těžké. Všiměte si, že proměnná logfile je označená jako static, což znamená, že je viditelná jen v rámci souboru log.c. To je dobrý způsob, jak si nezaneřádit zdrojové kódy globálními proměnnými.

Jméno logovacího souboru bude demon.PID.log, kde PID je PID démona.

  1. /*------------------------------------------------*/
  2. /* 25daemon/log.c                                 */
  3. #include <stdio.h>
  4. #include <time.h>
  5. #include <stdarg.h>
  6. #include <sys/types.h>
  7. #include <unistd.h>
  8. #include "log.h"
  9.  
  10. static FILE *logfile;
  11.  
  12. int openLog()
  13. {
  14.     char tmp[25];
  15.     sprintf(tmp, "/tmp/demon.%i.log", getpid());
  16.     logfile = fopen(tmp, "a+");
  17.     if (logfile == NULL) {
  18.         perror("fopen");
  19.         return 2;
  20.     }
  21.     return 0;
  22. }
  23.  
  24. int writeLog(char *format, ...)
  25. {
  26.     time_t sec;
  27.     struct tm *cas;
  28.     char scas[50];
  29.     char text[512];
  30.     va_list ap;
  31.  
  32.     sec = time(NULL);
  33.     cas = localtime(&sec);
  34.     strftime(scas, 50, "%F %T", cas);
  35.  
  36.     va_start(ap, format);
  37.     vsnprintf(text, 512, format, ap);
  38.     va_end(ap);
  39.  
  40.     fprintf(logfile, "[%5d,  %s] %s\n", getpid(), scas, text);
  41.     return fflush(logfile);
  42. }
  43.  
  44. int closeLog()
  45. {
  46.     return fclose(logfile);
  47. }
  48.  
  49. /*------------------------------------------------*/

Funkce writeLog() zapisuje s logovacím textem i PID a čas logování. Využívá k tomu funkce, které už znáte: strftime() a vsnprintf().

Po každém zapsaném řádku také zavolám fflush(), aby se vyprázdnila vyrovnávací paměť. Kdyby náhodou démon neočekávaně skončil (byl zabit), tak abych nepřišel o žádné informace.

K zapsání PID do souboru používám funkce z pidfile.h. Těla funkcí popíšu později, teď se podívejte jen na hlavičkový soubor.

  1. /*------------------------------------------------*/
  2. /* 25daemon/pidfile.h                             */
  3. #ifndef _PIDFILE_H
  4. #define _PIDFILE_H
  5. #include <stdio.h>
  6.  
  7. /**
  8.  * Otevře a zablokuje soubor
  9.  * @return 0 - soubor se podarilo otevrit
  10.  *         1 - bezi uz jiny demon
  11.  *         2 - chyba
  12.  */
  13. int openPidFile(char *filename);
  14.  
  15. /**
  16.  * Zapíše PID aktuálního procesu a uzavře soubor
  17.  */
  18. void closePidFile();
  19.  
  20. /**
  21.  * Smaže soubor
  22.  */
  23. int removePidFile(char *filename);
  24. #endif
  25. /*------------------------------------------------*/

Funkci removePidFile() zavolám ve chvíli, kdy se démon bude ukončovat.

Funkce pro zachytávání signálů jsem vyčlenil do signal.c. Hlavičkový soubor k nim vypadá takto:

  1. /*------------------------------------------------*/
  2. /* 25daemon/signals.h                             */
  3. #ifndef _SIGNALS_H
  4. #define _SIGNALS_H
  5. void setSignalHandlers(int *running, int *sighup);
  6. #endif
  7. /*------------------------------------------------*/

Funkce zachytávající singál nedělají nic jiného, než že nastaví hodnoty running a sighup. Jak na tuto změnu zareagovat, to už je problém démona. Jak by měl reagovat je asi jasné. Pokud se nastaví running na 0, měl by se ukončit a pokud se nastaví sighup na 1, měl by znovu načíst konfiguraci (zrestartovat se).

A teď přichází na řadu ta nejdůležitější část - démon.

  1. /*------------------------------------------------*/
  2. /* 25daemon/demon.c                               */
  3. #include <stdlib.h>
  4. #include <unistd.h>
  5. #include <fcntl.h>
  6. #include <sys/stat.h>
  7. #include "pidfile.h"
  8. #include "log.h"
  9. #include "signals.h"
  10.  
  11. static void usage()
  12. {
  13.     printf("demon path_to_pid_file [log_message]\n");
  14. }
  15.  
  16. static void printPidInfo()
  17. {
  18.     printf("process id = %i, session id = %i, parent id = %i\n",
  19.            getpid(), getsid(0), getppid());
  20. }
  21.  
  22. static int closeStandardDescriptors()
  23. {
  24.     close(STDIN_FILENO);
  25.     close(STDOUT_FILENO);
  26.     close(STDERR_FILENO);
  27.     if (open("/dev/null", O_RDONLY) == -1) {
  28.         perror("open 1");
  29.         return 2;
  30.     }
  31.     if (open("/dev/null", O_WRONLY) == -1) {
  32.         perror("open 2");
  33.         return 2;
  34.     }
  35.     if (open("/dev/null", O_RDWR) == -1) {
  36.         perror("open 3");
  37.         return 2;
  38.     }
  39.     return 0;
  40. }
  41.  

Na začátku si definuji pomocné funkce. Program demon bude zpracovávat 2 argumenty. První je název souboru, do kterého se bude ukládat PID. Druhý argument se zapíše do logu. To můžete použít při automatickém spuštění démona, abyste si označili, odkud byl démon spuštěn.

Ve funkci closeStandardDescriptors() nejdříve zavírám standardní IO. Pak otvírám /dev/null. Protože funkce open() vždy pro deskriptor souboru používá nejnižší dostupné číslo, bude první otevření mít číslo 1, tedy stejné, jako má stdin, druhé 2, jako má stdout a třetí 3, jako má stderr. Všiměte si, že u stderr se očekává otevření pro čtení i zápis.

Ve funkci main() se pak provádí postupně všechny výše popsané kroky.

  1. int main(int argc, char *argv[])
  2. {
  3.     pid_t pid, sid;
  4.     int ret;
  5.     char *pidfile;
  6.     int running = 1;
  7.     int sighup = 0;
  8.  
  9.     if (argc < 2) {
  10.         usage();
  11.         return 2;
  12.     }
  13.     pidfile = argv[1];
  14.     ret = openPidFile(pidfile);
  15.     if (ret) {
  16.         exit(ret);
  17.     }
  18.  
  19.     printPidInfo();
  20.  
  21.     /* Forking and killing father */
  22.     pid = fork();
  23.     if (pid < 0) {
  24.         perror("fork");
  25.         exit(2);
  26.     }
  27.     if (pid > 0) {
  28.         exit(0);                /* Rodičovský proces */
  29.     }
  30.     printPidInfo();
  31.  
  32.     umask(0);
  33.     openLog();
  34.     if (argc > 2) {
  35.         writeLog("%s", argv[2]);
  36.     }
  37.  
  38.     /* vytvoreni noveho session ID */
  39.     sid = setsid();
  40.     if (sid < 0) {
  41.         perror("setsid");
  42.         exit(2);
  43.     }
  44.     printPidInfo();
  45.  
  46.     /* zmena pracovniho adresare */
  47.     if (chdir("/") < 0) {
  48.         perror("chdir");
  49.         exit(2);
  50.     }
  51.  
  52.     setSignalHandlers(&running, &sighup);
  53.  
  54.     closeStandardDescriptors();
  55.  
  56.     closePidFile();
  57.  
  58.     /* Demon zacina svou praci */
  59.     while (running) {
  60.         if (sighup) {
  61.             sighup = 0;
  62.             writeLog("Načítám znovu konfiguraci.");
  63.             continue;
  64.         }
  65.         writeLog("Pracuji.");
  66.         ret = sleep(5);
  67.         if (ret) {
  68.             writeLog("Sleep byl přerušen o %i sec dříve.", ret);
  69.         }
  70.     }
  71.  
  72.     removePidFile(pidfile);
  73.     writeLog("%s", "Končím v míru.");
  74.     closeLog();
  75.  
  76.     return EXIT_SUCCESS;
  77. }
  78.  
  79. /*------------------------------------------------*/

Funkce main() vrací 0 v případě úspěšného spuštění démona, 2 v případě chyby a 1 v případě, že už démon běží (viz návratová hodnota funkce openPidFile()). Rozlišení těchto návratových hodnot se mi bude hodit při automatickém spouštění, viz dále.

Nastasvené obsluhy signálů jenom nastavují running a sighup. Zatímco signál SIGTERM stačí odchytit jednou, signál SIGHUP budud chtít zachytávat opakovaně, proto k jeho zachytávání používám funkci sigaction().

  1. /*------------------------------------------------*/
  2. /* 25daemon/signals.c                             */
  3. #include <stdio.h>
  4. #include <signal.h>
  5. #include "signals.h"
  6.  
  7.  
  8. static void sigterm_handler(int sig)
  9. {
  10.     *r = 0;
  11. }
  12.  
  13. static void sighup_handler(int sig)
  14. {
  15.     *s = 1;
  16. }
  17.  
  18. void setSignalHandlers(int *running, int *sighup)
  19. {
  20.     struct sigaction sa;
  21.     sigset_t set;
  22.  
  23.     r = running;
  24.     s = sighup;
  25.  
  26.     signal(SIGTERM, sigterm_handler);
  27.  
  28.     sigemptyset(&set);
  29.     sa.sa_handler = sighup_handler;
  30.     sa.sa_mask = set;
  31.     sa.sa_flags = SA_RESTART;
  32.     sa.sa_restorer = NULL;
  33.     sigaction(SIGHUP, &sa, NULL);
  34. }
  35.  
  36. /*------------------------------------------------*/

Poslední kus kódu se stará o zápis PID. Všiměte si, že k souboru přistupuju s exclusivním zámkem. To proto, aby se dva současně sputění démoni nepodívali do souboru, že je prázdný, nezapsali své ID (druhý by přepsal ID toho prvního) a nespustili se.

PID zapisuji až v closePidFile(). Tuto funkci volám, až když jsou nastavené hanldery na mírumilovné ukončení démona, při kterém soubor s PID zase smažu.

  1. /*------------------------------------------------*/
  2. /* 25daemon/pidfile.c                             */
  3. #include <errno.h>
  4. #include <sys/file.h>
  5. #include <unistd.h>
  6. #include "pidfile.h"
  7.  
  8. static FILE *pidfile;
  9.  
  10. int openPidFile(char *filename)
  11. {
  12.     pid_t oldpid = 0;
  13.     int ret;
  14.  
  15.     pidfile = fopen(filename, "a+");
  16.     if (!pidfile) {
  17.         perror("fopen");
  18.         return 2;
  19.     }
  20.  
  21.     ret = flock(fileno(pidfile), LOCK_EX);
  22.     if (ret) {
  23.         perror("flock");
  24.         return 2;
  25.     }
  26.  
  27.     ret = fscanf(pidfile, "%u", &oldpid);
  28.     if (errno != 0) {
  29.         perror("fscanf");
  30.         return 2;
  31.     }
  32.  
  33.     if (ret >= 0 && oldpid != getpid()) {
  34.         fprintf(stderr, "Demon už běží. PID = %i\n", oldpid);
  35.         return 1;
  36.     }
  37.     return 0;
  38. }
  39.  
  40. void closePidFile()
  41. {
  42.     fprintf(pidfile, "%d", getpid());
  43.     fclose(pidfile);
  44. }
  45.  
  46. int removePidFile(char *filename)
  47. {
  48.     return unlink(filename);
  49. }
  50.  
  51. /*------------------------------------------------*/

Spuštění programu může vypadat takto:

$ demon /tmp/demon.pid test
process id = 7093, session id = 2132, parent id = 2132
process id = 7094, session id = 2132, parent id = 7093                                                                                                                
process id = 7094, session id = 7094, parent id = 1                                                                                                                
                                                                                                                                                                     
$ kill -HUP 7094
$ kill -TERM 7094
$ cat /tmp/demon.7094.log
[ 7094,  2014-09-15 17:41:36] test
[ 7094,  2014-09-15 17:41:36] Pracuji.
[ 7094,  2014-09-15 17:41:41] Pracuji.
[ 7094,  2014-09-15 17:41:45] Sleep byl přerušen o 1 sec dříve.
[ 7094,  2014-09-15 17:41:45] Načítám znovu konfiguraci.
[ 7094,  2014-09-15 17:41:45] Pracuji.
[ 7094,  2014-09-15 17:41:50] Pracuji.
[ 7094,  2014-09-15 17:41:51] Sleep byl přerušen o 4 sec dříve.
[ 7094,  2014-09-15 17:41:51] Končím v míru.

Všiměte si, že funkce sleep() je přerušena, pokud program dostane nějaký signál. Takže nemusíte čekat (až) 5 sekund na to, než se démon ukončí.

Automatické spuštění po startu OS

První proces, který se spustí po spuštění jádra linuxu je proces init, který má ID 1. Ten se pak stará o spuštění dalších procesů.

Zbývá už jen otázka, kam co zapsat, aby init spustil vašeho démona (nebo jakýkoliv program, který chcete spustit po statrtu).

Tady ale vyvstává nová otázka, co je vlastně init zač? Bohužel, bylo vytvořeno spoustu programů, které si hrají na init. Takže odpověď na první otázku není jednoduchá a na tu druhou je ještě složitější. Dá se říct, že init je různý nejen systém od systému (Linux vs BSD vs OSX atp), ale i distribuce od distribuce a dokonce verze distribuce od verze distribuce.

Soubor rc.local

Bývalo zvykem, že jednoduché příkazy stačilo zapsat do souboru /etc/rc.local. Třeba v Debianu to funguje dodnes. V OpenSuSE už ne, ale místo toho máte /etc/rc.d/after.local a /etc/rc.d/boot.local.

Do všech těchto souborů stačí napsat něco takového, aby se program spustil při startu:

su - petr -c "/home/petr/bin/demon /tmp/rc.local.deamon.pid /etc/rc.local" &

Tento příkaz spustí program /home/petr/bin/deamon pod uživatelem petr. Příkaz je ukončen &, aby se mohl hned spustit další příkaz z rc.local (pokud nějaký existuje). U démona & možná není úplně nutné, protože se sám rychle odpojí od terminálu, ale je to stále dobrý zvyk (co kdyby se démon z nějakého důvodu zaseknul, pak by se zaseknul celý start systému …).

Pokud chcete spustit skript jako root, vynechte prostě su - petr -c a uvozovky.

Všiměte si, že cesta k programu je absolutní. To byste měli používat i v případě programů, které jsou umístěny v systémových adresářích jako je /bin, /usr/bin atp.

Rozdíl mezi after.local a boot.local je ten, kdy se skripty spouští. boot.local se spouští jako jeden z prvních, před init skritpy (o kterých bude řeč dále) zatímco after.local až po init skriptech. Lepší je dávat příkazy do after.local, kdy jsou ostatní služby už nastartované (např. jsou přimountované disky atp). Do boot.local dávejte něco jen tehdy, když k tomu budete mít důvod.

System V init

Klasickým, oblíbeným, nejvíce rozšířeným a snadným init systémem byl System V init. A někde pořád ještě je. Bohužel, System V init není zrovna nejrychlejší ve spouštění programů. Ke spuštění programu používá skritpy napsané v bashi a pouští je paralelně (jeden za druhým). Takže se snažili vývojáři vytvořit nějakou rychlejší alternativu. Vytvořili jich bohužel hodně, ale žádná z nich se nijak masivně neujala.

Skripty pro sysvinit se ukládají do adresáře /etc/init.d/.

V /etc/init.d/skeleton najdete příklad toho, jak má takový skript vypadat. V OpenSuSE tento skript už nenajdete, protože OpenSuSE už System V nepoužívá.

Vytvoření skritpu pro démona vypadá zhruba takto: Zkopíruji skeleton, nastavím mu právo spuštění, zedituju jeho obsah a pak příkazem insserv jmeno_skritpu jej přidám mezi skripty, které se mají spustit.

$ cp /etc/init.d/skeleton /etc/init.d/demon
$ chmod +x /etc/init.d/demon
## ... zedituj demon ...
$ insserv -v demon

Já jsem skeleton ještě trochu osekal a udělal z něj příklad pro náš program deamon.

To, co je mezi BEGIN INIT INFO a END INIT INFO není jen obyčejný komentář. Program insserv si z toho přečte, kteří démoni mají být spuštěni před naším démonem (v příkladu je to remote_fs a syslog), v jakých run-levelech se má démon spustit (v příkladu jsou to 2, 3, 4 a 5) a v jakých se má démon vypnout.

Zbytek skriptu je asi srozumitelný, tedy alespoň pokud umíte trochu programovat v bashi.

#! /bin/sh
### BEGIN INIT INFO
# Provides:          demon
# Required-Start:    $remote_fs $syslog
# Required-Stop:     $remote_fs $syslog
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Test demona demon
# Description:       Libovolny popis ...      
### END INIT INFO

# Author: Foo Bar <foobar@baz.org>

# PATH should only include /usr/* if it runs after the mountnfs.sh script
PATH=/sbin:/usr/sbin:/bin:/usr/bin
DESC="Ukazka demona"
NAME=demon
DAEMON=/home/petr/bin/$NAME
DAEMON_ARGS="/tmp/init.d.demon.pid /etc/init.d/demon"
PIDFILE=/var/run/$NAME.pid
SCRIPTNAME=/etc/init.d/$NAME

# Exit if the package is not installed
[ -x "$DAEMON" ] || exit 0

# Read configuration variable file if it is present
[ -r /etc/default/$NAME ] && . /etc/default/$NAME

#
# Function that starts the daemon/service
#
do_start()
{
        $DAEMON $DAEMON_ARGS
}

#
# Function that stops the daemon/service
#
do_stop()
{
        echo killall $NAME
        killall $DAEMON
}

#
# Function that sends a SIGHUP to the daemon/service
#
do_reload() {
        killall -HUP $DAEMON
}

case "$1" in
  start)
        do_start
        ;;
  stop)
        do_stop
        ;;
  status)
        echo "Neni implementovano ... "
        ;;
  reload)
        do_reload
        ;;
  restart|force-reload)
        do_stop
        do_start
        ;;
  *)
        echo "Usage: $SCRIPTNAME {start|stop|status|restart|reload|force-reload}" >&2
        exit 3
        ;;
esac

Init pak volá při startu /etc/init.d/deamon start a při ukončení zase /etc/init.d/deamon stop.

Všiměte si, že když posílám signál pomocí killall, neposílám ho jen na demon, ale na /home/petr/bin/demon. Jinak by totiž skript zabil sám sebe (taky se jmenuje demon).

Při startu skriptu by měl skript vrátit 0 v případě úspěchu, 1 v případě, že démon už běžel a 2 v případě chyby. Víc se k tomuto dočtete ve skutečném /etc/init.d/skeleton (pokud ho tedy v systému máte).

Přidání skriptu mezi spouštěné pak může vypadat takto:

$ insserv -v demon
insserv: enable service ../init.d/demon -> /etc/init.d/../rc0.d/K01demon
insserv: enable service ../init.d/demon -> /etc/init.d/../rc1.d/K01demon
insserv: enable service ../init.d/demon -> /etc/init.d/../rc2.d/S18demon
insserv: enable service ../init.d/demon -> /etc/init.d/../rc3.d/S18demon
insserv: enable service ../init.d/demon -> /etc/init.d/../rc4.d/S18demon
insserv: enable service ../init.d/demon -> /etc/init.d/../rc5.d/S18demon
insserv: enable service ../init.d/demon -> /etc/init.d/../rc6.d/K01demon
insserv: creating .depend.boot
insserv: creating .depend.start
insserv: creating .depend.stop

V zásadě se jen vytvoří symbolické linky do adresářů /tec/init.d/rc*.d/. Symbolický link začínající S se spouští při startu, link začínající K se spouští při vypínání. Číslo pak určuje pořadí, v jakém se skripty spoutějí. Číslo v adresáři rc*.d znamená, při jakém runlevelu se skripty spustí. Z příkladu je jasné, že se bude démon spouštět při runelevelech 2 až 5, při runlevelech 0, 1 a 6 se bude vypínat.

Jenom pro úplnost, odebrání démona vypdá takto:

$ insserv -v -r demon
insserv: remove service /etc/init.d/../rc0.d/K01demon
insserv: remove service /etc/init.d/../rc1.d/K01demon
insserv: remove service /etc/init.d/../rc2.d/S18demon
insserv: remove service /etc/init.d/../rc3.d/S18demon
insserv: remove service /etc/init.d/../rc4.d/S18demon
insserv: remove service /etc/init.d/../rc5.d/S18demon
insserv: remove service /etc/init.d/../rc6.d/K01demon
insserv: creating .depend.boot
insserv: creating .depend.start
insserv: creating .depend.stop

Systemd

Jednou z úspěšných náhrad System V init se zdá být Systemd. Najdete ho třeba v mém oblíbeném OpenSuSE. Zatím se zdá, že ze všech alternativ má největší budoucnost.

Dobrá zpráva je, že Systemd je kompatibilní s System V init. Můžet tak použít skript napsaný v minulé části a dokonce máte k dispozici i příkaz insserv, který se používá stejně, jen připraví skript pro prácí s Systemd.

Systemd, na rozdíl od System V init, si neklade za cíl jen služby (démony) spouštět, vypínat a restartovat. Chce i sledovat jejich stav, zda běží, neběží atp.

K tomu potřebuje znát PID aplikace. A k tomu je potřeba nainstalovat démona podle Systemd způsobu. Než s tím začnete, odinstalujte skript posaný výše (pokud ho máte instalovaný).

Způsob instalace démona do Systemd je překvapivě jednoduchý. Vytvořte soubor deamon.service v adresáři /usr/lib/systemd/system/ a zapište do něj následující:

[Unit]
Description=Priklad Demona
Wants=remote-fs.target syslog.service

[Service]
Type=forking
ExecStart=/home/petr/bin/demon /var/run/demon.pid  /usr/lib/systemd/system/demon.service
PIDFile=/var/run/demon.pid

[Install]
WantedBy=multi-user.target
Alias=demon.service

Description je libovolný popis démona (uvidíte ho při dotazech na status služeb app.). Wants říká, které služby by měly být spuštěny před spuštěním tohoto démona. ExecStart říká, jak se má démon spustit. Systemd obsluhuje různé typy služeb, jako jsou service (což je třeba náš démon), target (což je třeba připojování disků) atd. Podrobnosti si nastudujte kdyžtak z dokumentace.

Type=forking říká, že se jedná o démona, který se forkuje. Přesně tak, jak jsme jej naprogramovali. Systemd dokáže spustit na pozadí i programy, které jako démoni naprogramovaní nebyly. Vlastně má takové radči, protože může sám zjistit jejich PID a nepotřebuje k tomu PIDFile, jako v našem příkladě. (Systemd může zjistit jen PID jím spuštěného procesu. Náš program po forkování tento proces hned zabije, proto potřebuje systemd znát PIDFile.) To je jedna z výhod Systemd, protože psaní démonů tím usnadňuje. Mimoto Systemd spoutší programy paralelně najednou, a má i pár dalších vychytávek, proč je oblíbenější než System V init.

WantedBy říká, jaký runlevel jej vyžaduje. multi-user.target odpovídá runlevelu 3, ale vyžaduje jej i runlevel 5.

A teď si můžete hrát s systemctl. Příkazy enable / disable službu přidávají / odebírají z automatického startu. Příkazy start / stop službu ihned zapínají / vypínají. A příkaz status vám zobrazí status služby (jesti běží, je enabled atp.).

$ systemctl enable demon
ln -s '/usr/lib/systemd/system/demon.service' '/etc/systemd/system/demon.service'
ln -s '/usr/lib/systemd/system/demon.service' '/etc/systemd/system/multi-user.target.wants/demon.service'
$ systemctl status demon
demon.service - Priklad Demona
   Loaded: loaded (/usr/lib/systemd/system/demon.service; enabled)
   Active: inactive (dead)
$ systemctl start demon
$ systemctl status demon
demon.service - Priklad Demona
   Loaded: loaded (/usr/lib/systemd/system/demon.service; enabled)
   Active: active (running) since Po 2014-09-15 23:11:34 CEST; 9s ago
  Process: 7294 ExecStart=/home/petr/bin/demon /var/run/demon.pid /usr/lib/systemd/system/demon.service (code=exited, status=0/SUCCESS)
 Main PID: 7295 (demon)
   CGroup: /system.slice/demon.service
           └─7295 /home/petr/bin/demon /var/run/demon.pid /usr/lib/systemd/system/demon.service

zář 15 23:11:34 linux-nrse.site demon[7294]: process id = 7294, session id = 7294, parent id = 1
zář 15 23:11:34 linux-nrse.site systemd[1]: Started Priklad Demona
$ systemctl stop demon
$ systemctl disable demon
rm '/etc/systemd/system/multi-user.target.wants/demon.service'
rm '/etc/systemd/system/demon.service'

A to je k těm malým ďálíkům vše :-). Když budete mít štěstí, bude vaše distribuce podporovat jeden ze 3 probraných způsobů automatického startu. Když ne, budete si muset nastudovat vaší init alternativu sami.

Komentář Hlášení chyby
Vytvořeno: 13.9.2014
Naposledy upraveno: 10.10.2014
Tato stánka používá ke svému běhu cookies, díky kterým je možné monitorovat, co tu provádíte (ne že bych to bez cookies nezvládl). Také vás tu bude špehovat google analytics. Jestli si myslíte, že je to problém, vypněte si cookies ve vašem prohlížeči, nebo odejděte a už se nevracejte :-). Prohlížením tohoto webu souhlasíte s používáním cookies. Dozvědět se více..