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


110

В 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
Возможный дубликат: stackoverflow.com/q/5384168/1076451
Chimera

1
возможный дубликат: stackoverflow.com/questions/5384168/… для части демонизации, stackoverflow.com/questions/931093/… для просмотра файловой системы
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Если вам не нужно соответствие POSIX, вас может заинтересовать inotifyAPI. См: inotify_init, inotify_add_watch, inotify_rm_watch.
patryk.beza 07

Ответы:


216

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

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

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


Если я правильно помню, это следующие шаги:

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

Чтобы дать вам отправную точку: посмотрите на этот скелетный код, который показывает основные шаги. Этот код теперь также можно форкнуть на GitHub: Базовый скелет демона Linux

/*
 * 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 | СТАТ | UID | ВРЕМЯ | CMD |
+ ------ + ------ + ------ + ------ + ----- + ------- + ------ + ------ + ------ + ----- +
| 1 | 3387 | 3386 | 3386 | ? | -1 | S | 1000 | 0:00 | ./ |
+ ------ + ------ + ------ + ------ + ----- + ------- + ------ + ------ + ------ + ----- +

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

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

Чтение системного журнала:

  • Найдите свой файл системного журнала. Моя здесь:/var/log/syslog
  • Сделайте: grep firstdaemon /var/log/syslog

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

  firstdaemon [3387]: Первый демон запущен.
  firstdaemon [3387]: Первый демон остановлен.


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

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


Вау, спасибо! Замечательно. Итак, я должен поместить свой код в цикл while, и все?
chrisMe

В основном да. Но этот код - всего лишь пример. Это полностью зависит от того, чего вы хотите достичь с помощью процесса-демона. Обязательно прочтите и этот ответ: @Edwin
Pascal Werkl

1
Вместо второго fork(), почему бы просто не использовать setsid()?
Chimera

1
Обратите внимание, что эта sigaction()функция обеспечивает более полный и надежный механизм управления сигналами; новые приложения должны использовать, sigaction()а не signal().
patryk.beza 08

4
Зрителям следует отметить, что это «старый» способ. Новый рекомендуемый способ создания демона - это «демон нового стиля», который можно найти здесь: 0pointer.de/public/systemd-man/daemon.html#New-Style%20Daemons или
Starlord

30

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

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

  1. традиционные демоны SysV ( старый стиль ),
  2. Системные демоны ( новый стиль ).

SysV Демоны

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

  1. Закройте все дескрипторы открытых файлов, кроме стандартного ввода , вывода и ошибок (т.е. первых трех файловых дескрипторов 0, 1, 2). Это гарантирует, что в процессе демона не останется случайно переданный дескриптор файла. В Linux это лучше всего реализовать путем итерации /proc/self/fdс резервным вариантом итерации от файлового дескриптора 3 к значению, возвращаемому getrlimit()for RLIMIT_NOFILE.
  2. Сбросьте все обработчики сигналов к их значениям по умолчанию. Лучше всего это сделать, перебирая доступные сигналы до предела _NSIGи сбрасывая их на SIG_DFL.
  3. Сбросьте маску сигнала с помощью sigprocmask().
  4. Очистите блок среды, удалив или сбросив переменные среды, которые могут негативно повлиять на время выполнения демона.
  5. Звоните fork(), чтобы создать фоновый процесс.
  6. В дочернем вызове setsid()отключение от любого терминала и создание независимого сеанса .
  7. В дочернем элементе вызовите еще fork()раз, чтобы демон никогда не смог повторно получить терминал.
  8. Вызовите exit()первого дочернего элемента, чтобы остался только второй дочерний процесс (фактический процесс демона). Это гарантирует, что процесс-демон будет повторно связан с init / PID 1, как и должны быть все демоны.
  9. В процессе демона подключитесь /dev/nullк стандартному вводу , выводу и ошибке .
  10. В процессе демона, сбросить umaskв 0, так что режимы файл передается open(), mkdir()и таковою непосредственно контролировать режим доступа к файлам и каталогам.
  11. В процессе демона измените текущий каталог на корневой каталог ( /), чтобы демон случайно не блокировал размонтирование точек монтирования.
  12. В процессе демона запишите PID демона (возвращенный getpid()) в файл PID, например /run/foobar.pid(для гипотетического демона «foobar»), чтобы гарантировать, что демон не может быть запущен более одного раза. Это должно быть реализовано без гонок, чтобы файл PID обновлялся только тогда, когда он проверяется, в то же время, когда PID, ранее сохраненный в файле PID, больше не существует или принадлежит стороннему процессу.
  13. В процессе демона удалите привилегии, если это возможно и возможно.
  14. Из процесса демона уведомить запущенный исходный процесс о завершении инициализации. Это может быть реализовано через безымянный канал или аналогичный канал связи, который создается до первого fork()и, следовательно, доступен как в исходном процессе, так и в процессе демона.
  15. Звоните exit()в исходный процесс. Процесс, вызвавший демона, должен полагаться на то, что это exit()произойдет после завершения инициализации и установления и доступности всех внешних каналов связи.

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

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

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

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


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

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

  1. Если SIGTERMполучено, выключите демон и завершите работу без ошибок.
  2. Если SIGHUPполучено, перезагрузите файлы конфигурации, если это применимо.
  3. Укажите правильный код выхода из основного процесса демона, так как он используется системой инициализации для обнаружения ошибок и проблем службы. Рекомендуется следовать схеме кода выхода, определенной в рекомендациях LSB для сценариев инициализации SysV .
  4. Если возможно и возможно, откройте интерфейс управления демона через систему IPC D-Bus и выберите имя шины в качестве последнего шага инициализации.
  5. Для интеграции в systemd предоставьте файл модуля .service, который содержит информацию о запуске, остановке и другом обслуживании демона. Подробнее systemd.service(5)см.
  6. Насколько это возможно, полагайтесь на функциональность системы инициализации, чтобы ограничить доступ демона к файлам, службам и другим ресурсам, то есть в случае systemd полагайтесь на контроль ограничения ресурсов systemd вместо реализации своего собственного, полагайтесь на отбрасывание привилегий systemd код вместо его реализации в демоне и т.п. См. systemd.exec(5)Доступные элементы управления.
  7. Если используется D-Bus , сделайте ваш демон активируемым по шине, предоставив файл конфигурации активации службы D-Bus . У этого есть несколько преимуществ: ваш демон может запускаться по запросу лениво; он может быть запущен параллельно с другими демонами, которым это необходимо, что максимизирует распараллеливание и скорость загрузки ; ваш демон может быть перезапущен в случае сбоя без потери запросов шины, поскольку шина ставит в очередь запросы для активируемых служб. Подробнее см. Ниже .
  8. Если ваш демон предоставляет услуги другим локальным процессам или удаленным клиентам через сокет, его следует сделать активируемым через сокет, следуя схеме, указанной ниже . Как и активация D-Bus, это позволяет запускать службы по требованию, а также улучшает распараллеливание запуска служб. Кроме того, для протоколов без состояния (таких как syslog, DNS) демон, реализующий активацию на основе сокетов, может быть перезапущен без потери единственного запроса. Подробнее см. Ниже .
  9. Если возможно, демон должен уведомить систему инициализации о завершении запуска или обновлении статуса через sd_notify(3)интерфейс.
  10. Вместо использования syslog()вызова для регистрации непосредственно в системной службе системного журнала, демон нового стиля может выбрать просто регистрацию стандартной ошибки через fprintf(), которая затем перенаправляется в системный журнал системой инициализации. Если уровни журнала необходимы, их можно закодировать, добавив к отдельным строкам журнала префиксы типа «<4>» (для уровня журнала 4 «ПРЕДУПРЕЖДЕНИЕ» в схеме приоритета системного журнала), следуя стилю, аналогичному printk()системе уровней ядра Linux . Для получения дополнительной информации см sd-daemon(3)и systemd.exec(5).

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


11

Вы не можете создать в Linux процесс, который нельзя убить. Пользователь root (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 указывает поймать и проигнорировать сигнал. Вы можете создать обработчик сигнала, который может регистрировать получение сигнала и устанавливать флаги (например, флаг, указывающий на постепенное завершение работы).


8

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

#include <unistd.h>

int daemon(int nochdir, int noclose);

На странице руководства :

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

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

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


2
Обратите внимание, что daemon(7)руководство упоминает шаги по созданию демона и предупреждает, что: Функция BSD daemon()не должна использоваться, поскольку она реализует только подмножество этих шагов. daemonфункция впервые появилась в 4.4BSD и не совместима с POSIX .
patryk.beza

2
Также обратите внимание, что предупреждение об использовании daemon () находится в старом разделе SysV на странице руководства daemon (7) . Для systemd не рекомендуется использовать daemon ().
Грег Макферран

7

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

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

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

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


7
Я думаю, что, говоря «Демон, который не может быть остановлен», автор на самом деле означает, что демон всегда работает в фоновом режиме, когда сеанс завершается.
FaceBro

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

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

pm2 startup

pm2 save

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

service pm2 stop|restart|start|status

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


2
Это не имеет ничего общего с C.
melpomene

4
Я ценю наличие тега C. Однако OP не упоминает требование, касающееся C. Название создает демона в Linux. Этот ответ удовлетворяет это.
danday74

1
О, ты прав. Он помечен как C, но фактическое требование - C ++ (о чем свидетельствует код OP и связанная статья).
melpomene

3

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

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


2

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

В Windows вы создаете службу, регистрируете службу, а затем настраиваете ее автоматический запуск при загрузке в панели администрирования -> службы.


1
Спасибо. Так разве нет разницы между "демоном" и обычной программой? Я не хочу, чтобы он закрывался легко.
chrisMe

1
Нет, демон - это просто фоновый процесс. Точнее, вы выполняете ответвление от родительского, запускаете дочерний процесс и завершаете родительский процесс (так что терминальный доступ к программе отсутствует). это совсем не обязательно, чтобы быть «демоном»: en.wikipedia.org/wiki/Daemon_(computing)
Magn3s1um

1

Шаблон демона

Я написал шаблон демона после демона нового стиля: ссылка

Вы можете найти весь код шаблона на GitHub: здесь

Main.cpp

// This function will be called when the daemon receive a SIGHUP signal.
void reload() {
    LOG_INFO("Reload function called.");
}

int main(int argc, char **argv) {
    // The Daemon class is a singleton to avoid be instantiate more than once
    Daemon& daemon = Daemon::instance();
    // Set the reload function to be called in case of receiving a SIGHUP signal
    daemon.setReloadFunction(reload);
    // Daemon main loop
    int count = 0;
    while(daemon.IsRunning()) {
        LOG_DEBUG("Count: ", count++);
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
    LOG_INFO("The daemon process ended gracefully.");
}

Daemon.hpp

class Daemon {
    public:

    static Daemon& instance() {
        static Daemon instance;
        return instance;
    }

    void setReloadFunction(std::function<void()> func);

    bool IsRunning();

    private:

    std::function<void()> m_reloadFunc;
    bool m_isRunning;
    bool m_reload;

    Daemon();
    Daemon(Daemon const&) = delete;
    void operator=(Daemon const&) = delete;

    void Reload();

    static void signalHandler(int signal);
};

Daemon.cpp

Daemon::Daemon() {
    m_isRunning = true;
    m_reload = false;
    signal(SIGINT, Daemon::signalHandler);
    signal(SIGTERM, Daemon::signalHandler);
    signal(SIGHUP, Daemon::signalHandler);
}

void Daemon::setReloadFunction(std::function<void()> func) {
    m_reloadFunc = func;
}

bool Daemon::IsRunning() {
    if (m_reload) {
        m_reload = false;
        m_reloadFunc();
    }
    return m_isRunning;
}

void Daemon::signalHandler(int signal) {
    LOG_INFO("Interrup signal number [", signal,"] recived.");
    switch(signal) {
        case SIGINT:
        case SIGTERM: {
            Daemon::instance().m_isRunning = false;
            break;
        }
        case SIGHUP: {
            Daemon::instance().m_reload = true;
            break;
        }
    }
}

daemon-template.service

[Unit]
Description=Simple daemon template
After=network.taget

[Service]
Type=simple
ExecStart=/usr/bin/daemon-template --conf_file /etc/daemon-template/daemon-tenplate.conf
ExecReload=/bin/kill -HUP $MAINPID
User=root
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=daemon-template

[Install]
WantedBy=multi-user.target
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.