Создание демона в Linux

В Linux я хочу добавить демона, который не может быть остановлен и который отслеживает изменения файловой системы. Если какие-либо изменения обнаружены, он должен написать путь к консоли, где он был запущен, плюс новая строка.

У меня уже почти полностью изменен код файловой системы, но я не могу понять, как создать демона.

Мой код отсюда: http://www.yolinux.com/TUTORIALS/ForkExecProcesses.html

Что делать после вилки?

int main (int argc, char **argv) {

  pid_t pID = fork();
  if (pID == 0)  {              // child
          // Code only executed by child process    
      sIdentifier = "Child Process: ";
    }
    else if (pID < 0) {
        cerr << "Failed to fork" << endl;
        exit(1);
       // Throw exception
    }
    else                                   // parent
    {
      // Code only executed by parent process

      sIdentifier = "Parent Process:";
    }       

    return 0;
}

Ответ 1

В Linux я хочу добавить демона, который не может быть остановлен и который отслеживает изменения файловой системы. Если какие-либо изменения будут обнаружены, он должен написать путь к консоли, на которой он был запущен + новая строка.

Демоны работают в фоновом режиме и (обычно...) не относятся к TTY, поэтому вы не можете использовать stdout/stderr так, как вы, вероятно, хотите. Обычно демона syslog (syslogd) используется для регистрации сообщений в файлах (отладка, ошибка,...).

Кроме того, есть несколько необходимых шагов для демонстрации процесса.


Если я правильно помню эти шаги:

  • fork от родительского процесса и пусть он завершится, если форсирование было успешным. → Поскольку родительский процесс завершен, дочерний процесс теперь работает в фоновом режиме.
  • setsid. Создайте новый сеанс. Вызывающий процесс становится лидером нового сеанса и лидером группы процессов новой группы процессов. Теперь процесс отсоединяется от управляющего терминала (CTTY).
  • Сигналы улавливания - Игнорировать и/или обрабатывать сигналы.
  • fork again, и пусть родительский процесс завершится, чтобы вы избавились от процесса, ведущего к сеансу. (Только лидеры сеансов могут снова получить TTY.)
  • chdir. Измените рабочий каталог демона.
  • umask. Измените маску режима файла в соответствии с потребностями демона.
  • закрыть - закрыть все дескрипторы открытых файлов, которые могут быть унаследованы от родительского процесса.

Чтобы дать вам исходную точку: посмотрите на этот код скелета, который показывает основные этапы:

/*
 * daemonize.c
 * This example daemonizes a process, writes a few log messages,
 * sleeps 20 seconds and terminates afterwards.
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <syslog.h>

static void skeleton_daemon()
{
    pid_t pid;

    /* Fork off the parent process */
    pid = fork();

    /* An error occurred */
    if (pid < 0)
        exit(EXIT_FAILURE);

    /* Success: Let the parent terminate */
    if (pid > 0)
        exit(EXIT_SUCCESS);

    /* On success: The child process becomes session leader */
    if (setsid() < 0)
        exit(EXIT_FAILURE);

    /* Catch, ignore and handle signals */
    //TODO: Implement a working signal handler */
    signal(SIGCHLD, SIG_IGN);
    signal(SIGHUP, SIG_IGN);

    /* Fork off for the second time*/
    pid = fork();

    /* An error occurred */
    if (pid < 0)
        exit(EXIT_FAILURE);

    /* Success: Let the parent terminate */
    if (pid > 0)
        exit(EXIT_SUCCESS);

    /* Set new file permissions */
    umask(0);

    /* Change the working directory to the root directory */
    /* or another appropriated directory */
    chdir("/");

    /* Close all open file descriptors */
    int x;
    for (x = sysconf(_SC_OPEN_MAX); x>=0; x--)
    {
        close (x);
    }

    /* Open the log file */
    openlog ("firstdaemon", LOG_PID, LOG_DAEMON);
}
 
int main()
{
    skeleton_daemon();

    while (1)
    {
        //TODO: Insert daemon code here.
        syslog (LOG_NOTICE, "First daemon started.");
        sleep (20);
        break;
    }

    syslog (LOG_NOTICE, "First daemon terminated.");
    closelog();

    return EXIT_SUCCESS;
}


  • Скомпилируйте код: gcc -o firstdaemon daemonize.c
  • Запустите демон: ./firstdaemon
  • Проверьте, все ли работает нормально: ps -xj | grep firstdaemon

  • Результат должен быть похож на этот:

+------+------+------+------+-----+-------+------+------+------+-----+
| PPID | PID  | PGID | SID  | TTY | TPGID | STAT | UID  | TIME | CMD |
+------+------+------+------+-----+-------+------+------+------+-----+
|    1 | 3387 | 3386 | 3386 | ?   |    -1 | S    | 1000 | 0:00 | ./  |
+------+------+------+------+-----+-------+------+------+------+-----+

Что вы должны увидеть здесь:

  • Демон не имеет управляющего терминала (TTY =?)
  • Идентификатор родительского процесса (PPID) 1 (процесс инициализации)
  • PID!= SID, что означает, что наш процесс НЕ является лидером сеанса
    (из-за второго fork())
  • Поскольку PID!= SID, наш процесс не может снова управлять TTY

Чтение syslog:

  • Найдите файл syslog. Мой здесь: /var/log/syslog
  • Сделайте a: grep firstdaemon /var/log/syslog

  • Результат должен быть похож на этот:

  firstdaemon[3387]: First daemon started.
  firstdaemon[3387]: First daemon terminated.


Примечание: В действительности вы также захотите реализовать обработчик сигналов и правильно настроить журнал (файлы, уровни журналов...).

Дальнейшее чтение:

Ответ 2

man 7 daemon описывается, как создавать демона очень подробно. Мой ответ - просто выдержка из этого руководства.

Есть как минимум два типа демонов:


Демоны SysV

Если вам интересен традиционный демон SysV, вы должны выполнить следующие шаги:

  • Закройте все дескрипторы открытых файлов, кроме стандартного ввода, вывода и ошибки (т.е. первых трех файловых дескрипторов 0, 1, 2). Это гарантирует, что случайно не передается файловый дескриптор в процессе демона. В Linux это лучше всего реализовать путем повторения с помощью /proc/self/fd с возвратом итерации из дескриптора файла 3 в значение, возвращаемое getrlimit() для RLIMIT_NOFILE.
  • Reset все обработчики сигналов по умолчанию. Это лучше всего сделать путем повторения доступных сигналов до предела _NSIG и сброса их до SIG_DFL.
  • Reset сигнальная маска с использованием sigprocmask().
  • Санируйте блок среды, удаляя или перезагружая переменные среды, которые могут негативно повлиять на время выполнения демон.
  • Вызовите fork(), чтобы создать фоновый процесс.
  • Для ребенка откройте setsid(), чтобы отсоединиться от любого терминала и создать независимый сеанс .
  • В дочернем устройстве снова вызовите fork(), чтобы убедиться, что демон больше не может снова получить терминал.
  • Вызовите exit() в первом дочернем элементе, так что останется только второй ребенок (фактический процесс демон). Это гарантирует, что процесс демона повторно зарегистрирован для init/PID 1, поскольку все демоны должны быть.
  • В процессе daemon подключите /dev/null к стандартным вводам, выводам и ошибкам.
  • В процессе daemon reset umask до 0, так что режимы файла передаются open(), mkdir() и таким образом напрямую управлять режимом доступа созданных файлов и каталогов.
  • В процессе daemon измените текущий каталог в корневой каталог (/), чтобы избежать того, что демон невольно блокирует точки монтирования от размонтирования.
  • В процессе daemon напишите daemon PID (возвращенный getpid()) на PID файл, например /run/foobar.pid (для гипотетического демона "foobar" ), чтобы гарантировать, что демон не может быть запущен более одного раза. Это должно быть реализовано без гонок, так что файл PID обновляется только тогда, когда он проверяется одновременно с тем, что PID, ранее сохраненный в PID файле, больше не существует или принадлежит к внешнему процессу.
  • В процессе daemon удалите привилегии, если это возможно и применимо.
  • Из процесса daemon уведомите начальный процесс, что инициализация завершена. Это может быть реализовано через неназванный канал или аналогичный канал связи, который создается до первого fork() и, следовательно, доступен как в процессе оригинала, так и в процессе демона.
  • Вызовите exit() в исходном процессе. Процесс, вызывающий демона, должен быть в состоянии полагаться на то, что это exit() происходит после после инициализации, и все внешние каналы связи установлены и доступны.

Обратите внимание на это предупреждение:

Функция BSD daemon() не должна использоваться, поскольку она реализует только подмножество этих шагов.

Демон, который должен обеспечить совместимость с системами SysV, должен реализовать описанную выше схему. Тем не менее, рекомендуется сделать это поведение дополнительным и настраиваемым с помощью аргумента командной строки, чтобы облегчить отладку, а также упростить интеграцию в системы с помощью systemd.

Обратите внимание, что daemon() не соответствует POSIX.


Демоны нового стиля

Для демонов нового стиля рекомендуется следующие шаги:

  • Если получен SIGTERM, выключите демон и завершите работу.
  • Если получено SIGHUP, перезагрузите файлы конфигурации, если это применимо.
  • Предоставьте правильный код выхода из процесса основного демона, поскольку он используется системой init для обнаружения ошибок и проблем службы. Рекомендуется следовать схеме кода выхода, как определено в рекомендациях LSB для скриптов инициализации SysV.
  • Если возможно и применимо, ознакомьтесь с интерфейсом управления демонами с помощью системы D-Bus IPC и возьмите имя шины в качестве последнего шага инициализации.
  • Для интеграции в systemd создайте файл .service unit, который содержит информацию о запуске, останавливая и поддерживая тем самым демона. Подробнее см. systemd.service(5).
  • Насколько это возможно, полагайтесь на функциональность системы init, чтобы ограничить доступ демона к файлам, службам и другим ресурсам, то есть в случае systemd, полагаться на systemd ресурс лимит управления вместо того, чтобы внедрять свои собственные, полагайтесь на systemd привилегию, выкидывающую код вместо того, чтобы внедрять его в демона и тому подобное. См. systemd.exec(5) для доступных элементов управления.
  • Если используется D-Bus, активируйте свою шину-демона, предоставив конфигурационный файл службы D-Bus. Это имеет несколько преимуществ: ваш демон может быть запущен лениво по требованию; его можно запустить параллельно с другими демонами, требующими его - что максимизирует распараллеливание и скорость загрузки; ваш демон может быть перезапущен при сбое без потери запросов на шину, поскольку шина запрашивает запросы для активируемых сервисов. Подробнее см. .
  • Если ваш демон предоставляет услуги другим локальным процессам или удаленным клиентам через сокет, он должен быть сделан сокет-активируемым в соответствии с приведенной схемой ниже. Подобно активации D-Bus, это позволяет запускать службы по требованию, а также позволяет улучшить распараллеливание запуска службы. Кроме того, для протоколов без учета состояния (например, syslog, DNS) демон, реализующий активацию на основе сокетов, может быть перезапущен без потери одного запроса. Подробнее см. .
  • Если применимо, демон должен уведомлять систему инициализации о завершении запуска или обновлениях состояния через интерфейс sd_notify(3).
  • Вместо того, чтобы использовать вызов syslog() для регистрации непосредственно в службе системного syslog, демон нового стиля может выбрать простое обращение к стандартной ошибке через fprintf(), который затем пересылается в syslog системой init. Если необходимы уровни журнала, они могут быть закодированы путем префикса отдельных строк журнала с строками типа "< 4" (для уровня 4 журнала "WARNING" в схеме приоритета syslog), следуя аналогичному стилю с ядром Linux printk(). Подробнее см. sd-daemon(3) и systemd.exec(5).

Чтобы узнать больше, прочитайте целые man 7 daemon.

Ответ 3

Я могу остановиться при первом требовании "Демон , который нельзя остановить..."

Невозможно, мой друг; однако вы можете добиться того же самого с помощью гораздо лучшего инструмента, модуля ядра.

http://www.infoq.com/articles/inotify-linux-file-system-event-monitoring

Все демоны могут быть остановлены. Некоторые из них легче останавливаются, чем другие. Даже пара демона с партнером в удержании, возвращая партнера, если он потерян, может быть остановлен. Вам просто нужно немного усердствовать.

Ответ 4

Вы не можете создать процесс в Linux, который нельзя убить. Корневой пользователь (uid = 0) может отправить сигнал процессу, и есть два сигнала, которые невозможно поймать, SIGKILL = 9, SIGSTOP = 19. И другие сигналы (когда неотображаемые) также могут привести к завершению процесса.

Вам может понадобиться более общая функция daemonize, где вы можете указать имя вашей программы/демона и путь для запуска вашей программы (возможно, "/" или "/tmp" ). Вы также можете предоставить файл для stderr и stdout (и, возможно, путь управления с помощью stdin).

Вот необходимые сведения:

#include <stdio.h>    //printf(3)
#include <stdlib.h>   //exit(3)
#include <unistd.h>   //fork(3), chdir(3), sysconf(3)
#include <signal.h>   //signal(3)
#include <sys/stat.h> //umask(3)
#include <syslog.h>   //syslog(3), openlog(3), closelog(3)

И вот более общая функция,

int
daemonize(char* name, char* path, char* outfile, char* errfile, char* infile )
{
    if(!path) { path="/"; }
    if(!name) { name="medaemon"; }
    if(!infile) { infile="/dev/null"; }
    if(!outfile) { outfile="/dev/null"; }
    if(!errfile) { errfile="/dev/null"; }
    //printf("%s %s %s %s\n",name,path,outfile,infile);
    pid_t child;
    //fork, detach from process group leader
    if( (child=fork())<0 ) { //failed fork
        fprintf(stderr,"error: failed fork\n");
        exit(EXIT_FAILURE);
    }
    if (child>0) { //parent
        exit(EXIT_SUCCESS);
    }
    if( setsid()<0 ) { //failed to become session leader
        fprintf(stderr,"error: failed setsid\n");
        exit(EXIT_FAILURE);
    }

    //catch/ignore signals
    signal(SIGCHLD,SIG_IGN);
    signal(SIGHUP,SIG_IGN);

    //fork second time
    if ( (child=fork())<0) { //failed fork
        fprintf(stderr,"error: failed fork\n");
        exit(EXIT_FAILURE);
    }
    if( child>0 ) { //parent
        exit(EXIT_SUCCESS);
    }

    //new file permissions
    umask(0);
    //change to path directory
    chdir(path);

    //Close all open file descriptors
    int fd;
    for( fd=sysconf(_SC_OPEN_MAX); fd>0; --fd )
    {
        close(fd);
    }

    //reopen stdin, stdout, stderr
    stdin=fopen(infile,"r");   //fd=0
    stdout=fopen(outfile,"w+");  //fd=1
    stderr=fopen(errfile,"w+");  //fd=2

    //open syslog
    openlog(name,LOG_PID,LOG_DAEMON);
    return(0);
}

Вот пример программы, которая становится демонами, зависает и затем уходит.

int
main()
{
    int res;
    int ttl=120;
    int delay=5;
    if( (res=daemonize("mydaemon","/tmp",NULL,NULL,NULL)) != 0 ) {
        fprintf(stderr,"error: daemonize failed\n");
        exit(EXIT_FAILURE);
    }
    while( ttl>0 ) {
        //daemon code here
        syslog(LOG_NOTICE,"daemon ttl %d",ttl);
        sleep(delay);
        ttl-=delay;
    }
    syslog(LOG_NOTICE,"daemon ttl expired");
    closelog();
    return(EXIT_SUCCESS);
}

Обратите внимание, что SIG_IGN указывает на улавливание и игнорирование сигнала. Вы можете создать обработчик сигнала, который может регистрировать получение сигнала, и устанавливать флаги (например, флаг, указывающий на изящное завершение работы).

Ответ 5

Попробуйте использовать функцию daemon:

#include <unistd.h>

int daemon(int nochdir, int noclose);

На странице man:

Функция daemon() предназначена для программ, желающих отделить себя от управляющего терминала и работать в фоновом режиме как система Демоны.

Если nochdir равно нулю, daemon() изменяет текущий вызывающий процесс рабочий каталог в корневой каталог ( "/" ); в противном случае текущий рабочий каталог не изменяется.

Если noclose равен нулю, daemon() перенаправляет стандартный ввод, стандартный вывод и стандартная ошибка в /dev/null; в противном случае никаких изменений нет. сделанные для этих файловых дескрипторов.

Ответ 6

Если ваше приложение является одним из:

{
  ".sh": "bash",
  ".py": "python",
  ".rb": "ruby",
  ".coffee" : "coffee",
  ".php": "php",
  ".pl" : "perl",
  ".js" : "node"
}

и вы не возражаете против зависимости NodeJS, затем установите NodeJS, а затем:

npm install -g pm2

pm2 start yourapp.yourext --name "fred" # where .yourext is one of the above

pm2 start yourapp.yourext -i 0 --name "fred" # run your app on all cores

pm2 list

Чтобы все приложения работали при перезагрузке (и daemonise pm2):

pm2 startup

pm2 save

Теперь вы можете:

service pm2 stop|restart|start|status

(также легко позволяет просматривать изменения кода в каталоге приложения и автоматически перезапускать процесс приложения при изменении кода)

Ответ 7

Демон - это просто процесс в фоновом режиме. Если вы хотите запустить свою программу при загрузке ОС, то в linux вы добавите свою команду запуска в /etc/rc.d/rc.local(запустите после всех других скриптов) или /etc/startup.sh

В окнах вы создаете службу, регистрируете службу и затем устанавливаете ее для автоматического запуска при загрузке в панели администрирования → services.

Ответ 8

Вызывая fork(), вы создали дочерний процесс. Если вилка успешна (fork возвращает ненулевой PID), выполнение будет продолжаться с этой точки изнутри дочернего процесса. В этом случае мы хотим изящно выйти из родительского процесса, а затем продолжить работу в дочернем процессе.

Возможно, это поможет: http://www.netzmafia.de/skripten/unix/linux-daemon-howto.html