Как се пише Linux демон

1. Въведение: Какво е демон.

Демон е процес който е създаден да работи самостоятелно без почти никаква комуникация с потребителя. Apache уеб сървъра (httpd) е добър пример за демон. Той слуша на определен порт, доставя страници или изпълнява скриптове в завсимост от типа на заявката.
За да създадем демон трябва да изпълним специфични стъпки в определена последователност. Те могат да работят както в user  space така и в kernel space. Факт е че много малко демони работят с модулите на ядрото. Такива са демоните достъпващи хардуерни устройства контролери, принтери и т.н.. Демоните са един от фундаменталните градивни блокове на Linux който му дават невероятна гъвкавост и сила.
В този урок ще бъде създаден много упростен демон написан на C. С развитието на урока ще добавяме все повече код, показвайки правилния ред за изпълнението му.


2. Първи стъпки

Първо е необходимо да имате следните пъкети инсталирани на вашата Linux машина:

    GCC 3.2.2 или по висока версия
    Linux Development библиотеки и хедъри

Тези пъкети е необходимо да бъдат инсталирани за да можете да компилирате примерния код в този урок. За да определите с каква версия на GCC разполагате използвайте следната команда:

gcc –version


3. Създаване на демона

3.1 Какво трябва да прави?


Демона трябва да прави едно нещо и то добре. Това нещо може да е сложно например като управлението на хиляди електрони пощи на различни домейни или просто да пише доклат и да го изпраща по електроната поща до администратора.
И в двата случия трябва да имате добър план какво точно ще прави вашия демон. Ако ще взаймодеиства с други демони който са писани от вас или пък не трябва добре да премислите.

 

3.2 Колко да взаимодеистват?


Демоните никога не трябва да комуникират с потребителя чрез конзола.  В същност демоните въобще не трябва да комуникират директно с потребителите. Всякаква комуникация трябва да се извършва чрез някакъв вид интерфейс, който може да бъде сложен графичен интерфейс или просто изпращане на сигнал.


4. Основна структура на Демона


Когато демона се стартира той трябва да свърши малко предварителна работа за да бъде подготвен за неговата истинска работа. Това включва следнитестъпки:

  •     Създаване на дъщерен процес
  •     Промяна на фаиловата маска
  •     Отваряне на Log за писане
  •     Създаване на уникалeн идентификатор на сесията (SID)
  •     Смяна на текущата работна директория
  •     Затваряне на стандартните файлови дескриптори
  •     Въвеждане на същинския код на демона

 


4.1 Създаване на дъщерен процес


Демон може да бъде стартира вътрешно от системата, чрез потребителския терминал или  чрез скрипт. Когато се стартира той е като всеки друг процес в системата. За да го направим напълно анонимен трябва да бъде създаден дъщерен процес в който ще се изпълнява същинския  код. Това се постига с изпълнението на функцията fork().

  1. pid_t pid;
  2.  
  3. /* Fork off the parent process */      
  4.  
  5. pid = fork();
  6.  
  7. if (pid < 0) {
  8.  
  9.         exit(EXIT_FAILURE);
  10.  
  11. }
  12.  
  13. /* If we got a good PID, then
  14.  
  15.    we can exit the parent process. */
  16.  
  17. if (pid > 0) {
  18.  
  19.         exit(EXIT_SUCCESS);
  20.  
  21. }

Трябва да добавите и проверка за грешка непосредствено след извикването на fork функцията. Когато пишете демон трябва да пишете възможно най защитено. Факт е че по голямата частот кода в един демо служи за проверка и обработка на грешки.
fork() функцията връща идентификатора на дъщерния процес (различен от 0-ла) или -1 при грешка. Ако процеса не може да създаде дъщерен тогава демона трябва да прекрати своето изпълнение незабавно.
Ако функцията fork() приключи успешно процеса родител трябва да излезе красиво. Това може да ви изглежда странно но дъщерния процес ще продължи да съществува и ще продължи своето изпълнение.


4.2 Промяна на фаиловата маска


Когато демона създава файл или директория те се създават с права за достъп по подразбиране. За да може да пише във всеки файл саздаден от демона правата за достъп по подразбиране трябва да бъдат променени. Можем да използваме функцията umask().

  1. pid_t pid, sid;
  2.  
  3. /* Fork off the parent process */
  4.  
  5. pid = fork();
  6.  
  7. if (pid < 0) {
  8.  
  9.         /* Log failure (use syslog if possible) */
  10.  
  11.         exit(EXIT_FAILURE);
  12.  
  13. }
  14.  
  15. /* If we got a good PID, then
  16.  
  17.    we can exit the parent process. */
  18.  
  19. if (pid > 0) {
  20.  
  21.         exit(EXIT_SUCCESS);
  22.  
  23. }
  24.  
  25.  
  26.  
  27. /* Change the file mode mask */
  28.  
  29. umask(0);
Извикването на umask с 0-ла ни гарантира че ще имаме пълен достъп до файловетe генерирани от демона.

 

4.3  Отваряне на Log за писане


Отварянето на Log файлове не е задължитилно но е препоръчително. Това може да е единственното място където можем да погледнем за допълнителна информация.

4.4  Създаване на уникалeн идентификатор на сесията (SID)


Дъщерния процес трябва да вземе уникален SID от операционната система. В противен случай дъщерния процес ще стане сирак. Това можем да направим с функцията setsid().

  1. pid_t pid, sid;
  2.  
  3. /* Fork off the parent process */
  4.  
  5. pid = fork();
  6.  
  7. if (pid < 0) {
  8.  
  9.         exit(EXIT_FAILURE);
  10.  
  11. }
  12.  
  13. /* If we got a good PID, then
  14.  
  15.    we can exit the parent process. */
  16.  
  17. if (pid > 0) {
  18.  
  19.         exit(EXIT_SUCCESS);
  20.  
  21. }
  22.  
  23.  
  24.  
  25. /* Change the file mode mask */
  26.  
  27. umask(0);
  28.  
  29.  
  30.  
  31. /* Open any logs here */
  32.  
  33.  
  34.  
  35. /* Create a new SID for the child process */
  36.  
  37. sid = setsid();
  38.  
  39. if (sid < 0) {
  40.  
  41.         /* Log any failure */
  42.  
  43.         exit(EXIT_FAILURE);
  44.  
  45. }

 

setsid връща същия тип стойност както fork. Можем да приложим същия подход за проверка за грешка за да проверим дали е създаден SID за дъщерния процес.


4.5  Смяна на текущата работна директория



Текущата работна директория трябва да бъде смененна до такава която е гарантирано че винаги ще бъде там. Тъйкато много от Linux дистрибуциите не следват напълно стандартната файлова иерархия единственната директория която е гарантирано че ще бъде там е root (/) директорията. Можем да сменим текущата директория с функцията chdir().

  1. pid_t pid, sid;
  2. /* Fork off the parent process */
  3.  
  4. pid = fork();
  5.  
  6. if (pid < 0) {
  7.  
  8.         exit(EXIT_FAILURE);
  9.  
  10. }
  11.  
  12. /* If we got a good PID, then
  13.  
  14.    we can exit the parent process. */
  15.  
  16. if (pid > 0) {
  17.  
  18.         exit(EXIT_SUCCESS);
  19.  
  20. }
  21.  
  22.  
  23.  
  24. /* Change the file mode mask */
  25.  
  26. umask(0);      
  27.  
  28.  
  29.  
  30. /* Open any logs here */       
  31.  
  32.        
  33.  
  34. /* Create a new SID for the child process */
  35.  
  36. sid = setsid();
  37.  
  38. if (sid < 0) {
  39.  
  40.         /* Log any failure here */
  41.  
  42.         exit(EXIT_FAILURE);
  43.  
  44. }
  45.  
  46.  
  47.  
  48. /* Change the current working directory */
  49.  
  50. if ((chdir("/")) < 0) {
  51.  
  52.         /* Log any failure here */
  53.  
  54.         exit(EXIT_FAILURE);
  55.  
  56. }
chdir() функцията връща -1 при грешка. Трябва да проверите върнатат стойност след смяната към root директорията.


4.6  Затваряне на стандартните файлови дескриптори


Една от последните стъпки в разработването на демон е затварянето на стандартните файлови дескриптори (STDIN, STDOUT, STDERR). Демона не може да използва терминал, следователно тези файлови дескриптори са ненужни и  са потенциален проблем за сигурноста. Функцията close() затваря файлови дескриптори.

  1. pid_t pid, sid;
  2.  
  3. /* Fork off the parent process */
  4.  
  5. pid = fork();
  6.  
  7. if (pid < 0) {
  8.  
  9.         exit(EXIT_FAILURE);
  10.  
  11. }
  12.  
  13. /* If we got a good PID, then
  14.  
  15.    we can exit the parent process. */
  16.  
  17. if (pid > 0) {
  18.  
  19.         exit(EXIT_SUCCESS);
  20.  
  21. }
  22.  
  23.  
  24.  
  25. /* Change the file mode mask */
  26.  
  27. umask(0);      
  28.  
  29.  
  30.  
  31. /* Open any logs here */
  32.  
  33.  
  34.  
  35. /* Create a new SID for the child process */
  36.  
  37. sid = setsid();
  38.  
  39. if (sid < 0) {
  40.  
  41.         /* Log any failure here */
  42.  
  43.         exit(EXIT_FAILURE);
  44.  
  45. }
  46.  
  47.  
  48.  
  49. /* Change the current working directory */
  50.  
  51. if ((chdir("/")) < 0) {
  52.  
  53.         /* Log any failure here */
  54.  
  55.         exit(EXIT_FAILURE);
  56.  
  57. }
  58.  
  59.  
  60. /* Close out the standard file descriptors */
  61.  
  62. close(STDIN_FILENO);
  63.  
  64. close(STDOUT_FILENO);
  65.  
  66. close(STDERR_FILENO);
Добра идия е да използвате #define константи за имената на стандартните файлови дескриптори. Така ще осигурите по голяма преносимост на кода.

5. Писане Кода на Демона


5.1 Инициализация


До този момент вие сте заявили на операционата сестема че вашия процес е демон. Сега е момента да напишете същинския код на демона.  Инициализацията е първата стъпка. След това могат да бъдат извиквани много различни функции който да свършат специфичната работа на демона.
Важното в тази точка е че когато извъшвате инициализация в демона вие трябва да използвате всички добри практики за предпазване от грешки. Добавете достатъчно информация в записите който правите в syslog или във вашите Log файлове. Отстраняването на грешки може да бъде доста трудно когато няма достатъчно налична информация за статуса на демона.


5.2 Безкрайния цикъл


Основния код на демона е във вътрешноста на безклаен цикъл. Технически не е безкраен  цикъл но е структориран  като такъв.

  1. pid_t pid, sid;
  2.  
  3.  
  4.  
  5. /* Fork off the parent process */
  6.  
  7. pid = fork();
  8.  
  9. if (pid < 0) {
  10.  
  11.         exit(EXIT_FAILURE);
  12.  
  13. }
  14.  
  15. /* If we got a good PID, then
  16.  
  17.    we can exit the parent process. */
  18.  
  19. if (pid > 0) {
  20.  
  21.         exit(EXIT_SUCCESS);
  22.  
  23. }
  24.  
  25.  
  26.  
  27. /* Change the file mode mask */
  28.  
  29. umask(0);      
  30.  
  31.  
  32.  
  33. /* Open any logs here */
  34.  
  35.  
  36.  
  37. /* Create a new SID for the child process */
  38.  
  39. sid = setsid();
  40.  
  41. if (sid < 0) {
  42.  
  43.         /* Log any failures here */
  44.  
  45.         exit(EXIT_FAILURE);
  46.  
  47. }
  48.  
  49.  
  50.  
  51.  
  52.  
  53. /* Change the current working directory */
  54.  
  55. if ((chdir("/")) < 0) {
  56.  
  57.         /* Log any failures here */
  58.  
  59.         exit(EXIT_FAILURE);
  60.  
  61. }
  62.  
  63.  
  64.  
  65. /* Close out the standard file descriptors */
  66.  
  67. close(STDIN_FILENO);
  68.  
  69. close(STDOUT_FILENO);
  70.  
  71. close(STDERR_FILENO);
  72.  
  73.  
  74.  
  75. /* Daemon-specific initialization goes here */
  76.  
  77.  
  78.  
  79. /* The Big Loop */
  80.  
  81. while (1) {
  82.  
  83.    /* Do some task here ... */
  84.  
  85.    sleep(30); /* wait 30 seconds */
  86.  
  87. }
Това е типичен цикъл. Обикновенно се използва while конструкция с безкрайно условие в която се извиква sleep за да се отложи повторното изпълнение на кода за  определен интервал.


6. Пълния пример


По долу е даден завършения код на демона. Той показва всички стъпки необходими за създаването му. За да го пуснете просто го компилирайте с gcc и го стартирайте от терминала. За да го спрете използвайте kill командата.

  1. #include <sys/types.h>
  2.  
  3. #include <sys/stat.h>
  4.  
  5. #include <stdio.h>
  6.  
  7. #include <stdlib.h>
  8.  
  9. #include <fcntl.h>
  10.  
  11. #include <errno.h>
  12.  
  13. #include <unistd.h>
  14.  
  15. #include <syslog.h>
  16.  
  17. #include <string.h>
  18.  
  19. int main(void) {
  20.  
  21.     /* Our process ID and Session ID */
  22.  
  23.     pid_t pid, sid;
  24.  
  25.    
  26.  
  27.     /* Fork off the parent process */
  28.  
  29.     pid = fork();
  30.  
  31.     if (pid < 0) {
  32.  
  33.             exit(EXIT_FAILURE);
  34.  
  35.     }
  36.  
  37.     /* If we got a good PID, then
  38.  
  39.        we can exit the parent process. */
  40.  
  41.     if (pid > 0) {
  42.  
  43.             exit(EXIT_SUCCESS);
  44.  
  45.     }
  46.  
  47.  
  48.  
  49.     /* Change the file mode mask */
  50.  
  51.     umask(0);
  52.  
  53.            
  54.  
  55.     /* Open any logs here */       
  56.  
  57.            
  58.  
  59.     /* Create a new SID for the child process */
  60.  
  61.     sid = setsid();
  62.  
  63.     if (sid < 0) {
  64.  
  65.             /* Log the failure */
  66.  
  67.             exit(EXIT_FAILURE);
  68.  
  69.     }
  70.  
  71.    
  72.  
  73.  
  74.  
  75.    
  76.  
  77.     /* Change the current working directory */
  78.  
  79.     if ((chdir("/")) < 0) {
  80.  
  81.             /* Log the failure */
  82.  
  83.             exit(EXIT_FAILURE);
  84.  
  85.     }
  86.  
  87.    
  88.  
  89.     /* Close out the standard file descriptors */
  90.  
  91.     close(STDIN_FILENO);
  92.  
  93.     close(STDOUT_FILENO);
  94.  
  95.     close(STDERR_FILENO);
  96.  
  97.    
  98.  
  99.     /* Daemon-specific initialization goes here */
  100.  
  101.    
  102.  
  103.     /* The Big Loop */
  104.  
  105.     while (1) {
  106.  
  107.        /* Do some task here ... */
  108.  
  109.       
  110.  
  111.        sleep(30); /* wait 30 seconds */
  112.  
  113.     }
  114.  
  115.    exit(EXIT_SUCCESS);
  116.  
  117. }
От тук нататък можете да използвате този код за да създадете ваш собствен демон. Добавете ваш Log или използвайте syslog. Прилагайте стриктно всички добри практики за защита от грешки.