Пишем собственный linux демон с возможностью автовосстановления работы

Уважаемые хабрапользователи, хотелось бы поделиться с вами опытом написания серверных демонов. В Рунете очень много статей по этому поводу, но большинство из них не даёт ответы на такие важные вопросы как:
  • Как добавить демона в автозагрузку?
  • Что делать, если в процессе работы произошла ошибка и демон рухнул?
  • Каким образом обновить конфигурацию демона без прерывания его работы?

В рамках данной части рассмотрим следующие моменты:
  • Принцип работы демона.
  • Основы разработки мониторинга состояния демона.
  • Обработка ошибок при работе, с подробным отчетом в лог.
  • Некоторые вопросы связанные с ресурсами системы.

Для наглядности будет показан исходный код следующих частей:
  • Шаблон основной программы.
  • Шаблон функции мониторинга работы демона.
  • Шаблон функции обработки ошибок.
  • Ряд вспомогательных функций.


Принцип работы демона.
По суди демон это обычная программа выполняющаяся в фоновом режиме. Но так как наш демон будет запускаться из init.d, то на него накладываются определенные ограничения:
  • Демон должен сохранить свой PID в файл, для того чтобы потом можно было его корректно остановить.
  • Необходимо выполнить ряд подготовительных операций для начала работы в фоновом режиме.

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


Шаблона программы.
Данный код будет осуществлять все действия, которые необходимы для удачного запуска демона.
int main(int argc, char** argv)
{
    int status;
    int pid;
    
    // если параметров командной строки меньше двух, то покажем как использовать демона
    if (argc != 2)
    {
        printf("Usage: ./my_daemon filename.cfg\n");
        return -1;
    }

    // загружаем файл конфигурации
    status = LoadConfig(argv[1]);
    
    if (!status) // если произошла ошибка загрузки конфига
    {
        printf("Error: Load config failed\n");
        return -1;
    }
    
    // создаем потомка
    pid = fork();

    if (pid == -1) // если не удалось запустить потомка
    {
        // выведем на экран ошибку и её описание
        printf("Error: Start Daemon failed (%s)\n", strerror(errno));
        
        return -1;
    }
    else if (!pid) // если это потомок
    {
        // данный код уже выполняется в процессе потомка
        // разрешаем выставлять все биты прав на создаваемые файлы,
        // иначе у нас могут быть проблемы с правами доступа
        umask(0);
        
        // создаём новый сеанс, чтобы не зависеть от родителя
        setsid();
        
        // переходим в корень диска, если мы этого не сделаем, то могут быть проблемы.
        // к примеру с размантированием дисков
        chdir("/");
        
        // закрываем дискрипторы ввода/вывода/ошибок, так как нам они больше не понадобятся
        close(STDIN_FILENO);
        close(STDOUT_FILENO);
        close(STDERR_FILENO);
        
        // Данная функция будет осуществлять слежение за процессом
        status = MonitorProc();
        
        return status;
    }
    else // если это родитель
    {
        // завершим процес, т.к. основную свою задачу (запуск демона) мы выполнили
        return 0;
    }
}


* This source code was highlighted with Source Code Highlighter.

Логика работы проста и не должна вызывать проблем с пониманием. Единственное что необходимо уточнить:
  • LoadConfig – данная функция загружает конфиг из указанного файла, её код будет зависеть от формата конфига, который вы используете, и в рамках данной статьи не будет рассматриваться.
  • Закрытие дескрипторов необходимо по той причине, что мы не будем использовать printf и scanf прочие функции работы с консольным вводом/выводом. Данное действие не обязательно и используется для экономии ресурсов.
  • Переход в корень диска, необходим для того, чтобы впоследствии не было проблем связанных с размонтированием дисков. Если текущая папка демона будет находиться на диске, который необходимо будет отмонтировать, то система не даст этого, до тех пор, пока демон не будет остановлен.
  • MonitorProc – данная функция будет выполнять основные действия, связанные с мониторингом состояния программы.

Основы разработки мониторинга состояния демона.
Основная цель мониторинг — отслеживание состояния процесса демона. Нам будут важны только два момента:
  1. Уведомление о завершении процесса демона.
  2. Получение кода завершения демона.

Весь мониторинг работы демона будет заключен в функцию MonitorProc. Весь смысл мониторинга заключается в том, чтобы запустить дочерний процесс и следить за ним, и в зависимости от кода его завершения, перезапускать его или завершать свою работу.
Исходный код функции мониторинга:
int MonitorProc()
{
    int      pid;
    int      status;
    int      need_start = 1;
    sigset_t sigset;
    siginfo_t siginfo;

    // настраиваем сигналы которые будем обрабатывать
    sigemptyset(&sigset);
    
    // сигнал остановки процесса пользователем
    sigaddset(&sigset, SIGQUIT);
    
    // сигнал для остановки процесса пользователем с терминала
    sigaddset(&sigset, SIGINT);
    
    // сигнал запроса завершения процесса
    sigaddset(&sigset, SIGTERM);
    
    // сигнал посылаемый при изменении статуса дочернего процесса
    sigaddset(&sigset, SIGCHLD);
    
    // пользовательский сигнал который мы будем использовать для обновления конфига
    sigaddset(&sigset, SIGUSR1);
    sigprocmask(SIG_BLOCK, &sigset, NULL);

    // данная функция создаст файл с нашим PID'ом
    SetPidFile(PID_FILE);

    // бесконечный цикл работы
    for (;;)
    {
        // если необходимо создать потомка
        if (need_start)
        {
            // создаём потомка
            pid = fork();
        }
        
        need_start = 1;
        
        if (pid == -1) // если произошла ошибка
        {
            // запишем в лог сообщение об этом
            WriteLog("[MONITOR] Fork failed (%s)\n", strerror(errno));
        }
        else if (!pid) // если мы потомок
        {
            // данный код выполняется в потомке
            
            // запустим функцию отвечающую за работу демона
            status = WorkProc();
            
            // завершим процесс
            exit(status);
        }
        else // если мы родитель
        {
            // данный код выполняется в родителе
            
            // ожидаем поступление сигнала
            sigwaitinfo(&sigset, &siginfo);
            
            // если пришел сигнал от потомка
            if (siginfo.si_signo == SIGCHLD)
            {
                // получаем статус завершение
                wait(&status);
                
                // преобразуем статус в нормальный вид
                status = WEXITSTATUS(status);

                 // если потомок завершил работу с кодом говорящем о том, что нет нужды дальше работать
                if (status == CHILD_NEED_TERMINATE)
                {
                    // запишем в лог сообщени об этом        
                    WriteLog("[MONITOR] Child stopped\n");
                    
                    // прервем цикл
                    break;
                }
                else if (status == CHILD_NEED_WORK) // если требуется перезапустить потомка
                {
                    // запишем в лог данное событие
                    WriteLog("[MONITOR] Child restart\n");
                }
            }
            else if (siginfo.si_signo == SIGUSR1) // если пришел сигнал что необходимо перезагрузить конфиг
            {
                kill(pid, SIGUSR1); // перешлем его потомку
                need_start = 0; // установим флаг что нам не надо запускать потомка заново
            }
            else // если пришел какой-либо другой ожидаемый сигнал
            {
                // запишем в лог информацию о пришедшем сигнале
                WriteLog("[MONITOR] Signal %s\n", strsignal(siginfo.si_signo));
                
                // убьем потомка
                kill(pid, SIGTERM);
                status = 0;
                break;
            }
        }
    }

    // запишем в лог, что мы остановились
    WriteLog("[MONITOR] Stop\n");
    
    // удалим файл с PID'ом
    unlink(PID_FILE);
    
    return status;
}


* This source code was highlighted with Source Code Highlighter.
По коду необходимо уточнить следующее:
  • PID_FILE – константа, которая будет хранить имя файла для сохранения PID’a. В нашем случае это /var/run/my_daemon.pid
  • WriteLog – функция осуществляющая запись в лог. В ней вы можете придумать то, что душе угодно и писать лог куда угодно или вообще передавать его куда-нибудь
  • WorkProc – функция, которая реализует непосредственно функционал демона

Для работы требуется вспомогательная функция для создания PID файла.
Код:
void SetPidFile(char* Filename)
{
    FILE* f;

    f = fopen(Filename, "w+");
    if (f)
    {
        fprintf(f, "%u", getpid());
        fclose(f);
    }
}


* This source code was highlighted with Source Code Highlighter.

На данный момент наш демон уже умеет запускаться, следить за своим потомком, который выполняет основные функции и при необходимости перезапускать его или посылать ему сигнал об изменение конфигурации. Далее рассмотрим шаблон кода потомка:
int WorkProc()
{
    struct sigaction sigact;
    sigset_t         sigset;
    int             signo;
    int             status;

    // сигналы об ошибках в программе будут обрататывать более тщательно
    // указываем что хотим получать расширенную информацию об ошибках
    sigact.sa_flags = SA_SIGINFO;
    // задаем функцию обработчик сигналов
    sigact.sa_sigaction = signal_error;

    sigemptyset(&sigact.sa_mask);

    // установим наш обработчик на сигналы
    
    sigaction(SIGFPE, &sigact, 0); // ошибка FPU
    sigaction(SIGILL, &sigact, 0); // ошибочная инструкция
    sigaction(SIGSEGV, &sigact, 0); // ошибка доступа к памяти
    sigaction(SIGBUS, &sigact, 0); // ошибка шины, при обращении к физической памяти

    sigemptyset(&sigset);
    
    // блокируем сигналы которые будем ожидать
    // сигнал остановки процесса пользователем
    sigaddset(&sigset, SIGQUIT);
    
    // сигнал для остановки процесса пользователем с терминала
    sigaddset(&sigset, SIGINT);
    
    // сигнал запроса завершения процесса
    sigaddset(&sigset, SIGTERM);
    
    // пользовательский сигнал который мы будем использовать для обновления конфига
    sigaddset(&sigset, SIGUSR1);
    sigprocmask(SIG_BLOCK, &sigset, NULL);

    // Установим максимальное кол-во дискрипторов которое можно открыть
    SetFdLimit(FD_LIMIT);
    
    // запишем в лог, что наш демон стартовал
    WriteLog("[DAEMON] Started\n");
    
    // запускаем все рабочие потоки
    status = InitWorkThread();
    if (!status)
    {
        // цикл ожижания сообщений
        for (;;)
        {
            // ждем указанных сообщений
            sigwait(&sigset, &signo);
        
            // если то сообщение обновления конфига
            if (signo == SIGUSR1)
            {
                // обновим конфиг
                status = ReloadConfig();
                if (status == 0)
                {
                    WriteLog("[DAEMON] Reload config failed\n");
                }
                else
                {
                    WriteLog("[DAEMON] Reload config OK\n");
                }
            }
            else // если какой-либо другой сигнал, то выйдим из цикла
            {
                break;
            }
        }
        
        // остановим все рабочеи потоки и корректно закроем всё что надо
        DestroyWorkThread();
    }
    else
    {
        WriteLog("[DAEMON] Create work thread failed\n");
    }

    WriteLog("[DAEMON] Stopped\n");
    
    // вернем код не требующим перезапуска
    return CHILD_NEED_TERMINATE;
}


* This source code was highlighted with Source Code Highlighter.


По коду требуется сказать:
  • InitWorkThread — функция которая создаёт все рабочие потоки демона и инициализирует всю работу.
  • DestroyWorkThread — функция которая останавливает рабочие потоки демона и корректно освобождает ресурсы.
  • ReloadConfig — функция осуществляющая обновление конфига (заново считать файл и внести необходимые изменения в свою работу). Имя файла можно также взять из параметров командной строки.

Данные функции зависят уже от вашей реализации демона.

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

Обработка ошибок при работе, с подробным отчетом в лог.

Конечно же демоны должны работать идеально и не вызывать всякого рода ошибок, но ошибаться может каждый, да и порой встречаются ошибки, которые довольно тяжело обнаружить на стадии тестирования. Особенно это актуально для ошибок которые проявляются при большой загруженности. По этому важным моментом в разработке демона является правильная обработка ошибок, а также выжимание из ошибки как можно большей информации. В данном случае рассмотрим обработку ошибок с сохранением стека вызовов (backtrace). Это даст нам информацию о том, где именно произошла ошибка (в какой функции), а также мы сможем узнать то, кто вызвал эту функцию.

Код функции обработчика ошибок:
static void signal_error(int sig, siginfo_t *si, void *ptr)
{
    void* ErrorAddr;
    void* Trace[16];
    int    x;
    int    TraceSize;
    char** Messages;

    // запишем в лог что за сигнал пришел
    WriteLog("[DAEMON] Signal: %s, Addr: 0x%0.16X\n", strsignal(sig), si->si_addr);

    
    #if __WORDSIZE == 64 // если дело имеем с 64 битной ОС
        // получим адрес инструкции которая вызвала ошибку
        ErrorAddr = (void*)((ucontext_t*)ptr)->uc_mcontext.gregs[REG_RIP];
    #else
        // получим адрес инструкции которая вызвала ошибку
        ErrorAddr = (void*)((ucontext_t*)ptr)->uc_mcontext.gregs[REG_EIP];
    #endif

    // произведем backtrace чтобы получить весь стек вызовов
    TraceSize = backtrace(Trace, 16);
    Trace[1] = ErrorAddr;

    // получим расшифровку трасировки
    Messages = backtrace_symbols(Trace, TraceSize);
    if (Messages)
    {
        WriteLog("== Backtrace ==\n");
        
        // запишем в лог
        for (x = 1; x < TraceSize; x++)
        {
            WriteLog("%s\n", Messages[x]);
        }
        
        WriteLog("== End Backtrace ==\n");
        free(Messages);
    }

    WriteLog("[DAEMON] Stopped\n");
    
    // остановим все рабочие потоки и корректно закроем всё что надо
    DestroyWorkThread();
    
    // завершим процесс с кодом требующим перезапуска
    exit(CHILD_NEED_WORK);
}


* This source code was highlighted with Source Code Highlighter.

При использовании backtrace можно получить данные примерно такого вида:
[DAEMON] Signal: Segmentation fault, Addr: 0x0000000000000000
== Backtrace ==
/usr/sbin/my_daemon(GetParamStr+0x34) [0x8049e44]
/usr/sbin/my_daemon(GetParamInt+0x3a) [0x8049efa]
/usr/sbin/my_daemon(main+0x140) [0x804b170]
/lib/tls/i686/cmov/libc.so.6(__libc_start_main+0xe6) [0x126bd6]
/usr/sbin/my_daemon() [0x8049ba1]
== End Backtrace ==

Из этих данных видно, что функция main вызвала функцию GetParamInt. Функция GetParamInt вызвала GetParamStr. В функции GetParamStr по смещению 0x34 произошло обращение к памяти по нулевому адресу.

Также помимо стека вызовов можно сохранить и значение регистров (массив uc_mcontext.gregs).
Необходимо заметить, что наибольшую информативность от backtrace можно получить только при компилировании без вырезания отладочной информации, а также с использованием опции -rdynamic.

Как можно было заметить, в коде используются константы CHILD_NEED_WORK и CHILD_NEED_TERMINATE. Значение этих констант вы можете назначать сами, главное чтобы они были не одинаковые.

Некоторые вопросы связанные с ресурсами системы.

Важным моментом является установка максимального кол-ва дескрипторов. Любой открытый файл, сокет, пайп и прочие тратят дескрипторы, при исчерпании которых невозможно будет открыть файл или создать сокет или принять входящее подключение. Это может сказаться на производительности демона. По умолчанию максимальное кол-во открытых дескрипторов равно 1024. Такого кол-ва очень мало для высоконагруженных сетевых демонов. Поэтому мы будем ставить это значение больше в соответствии со своими требованиями. Для этого используем следующую функцию:
int SetFdLimit(int MaxFd)
{
    struct rlimit lim;
    int          status;

    // зададим текущий лимит на кол-во открытых дискриптеров
    lim.rlim_cur = MaxFd;
    // зададим максимальный лимит на кол-во открытых дискриптеров
    lim.rlim_max = MaxFd;

    // установим указанное кол-во
    status = setrlimit(RLIMIT_NOFILE, &lim);
    
    return status;
}


* This source code was highlighted with Source Code Highlighter.

Вместо заключения.
Вот мы и рассмотрели как создать основу для демона. Конечно же код не претендует на идеальный, но со своими задачами справляется отлично.
В следующей статье будут рассмотрены моменты, связанные с установкой/удалением демона, управления им, написанием скриптов автозагрузки для init.d и непосредственно добавлением в автозагрузку.

Ссылка на исходный код: http://pastebin.com/jdX5wn0E
В исходном коде собраны все используемые функции в один файл. При разработке проекта желательно раскидать их в разные файлы в соответствии с их функциональным назначением.

Комментарии 40

    +1
    Хотелось бы добавить еще небольшое уточнение по коду.
    TraceSize = backtrace(Trace, 16);
    этим мы получаем не весь стек вызовов, а последние 16, чего вполне достаточно чтобы локализовать проблему, если конечно речь идет не о рекурсивных функциях
      0
      что то не вижу где можно скачать исходный код…
        0
        если надо, то могу выложить на pastebin
      –4
      Недавно писал демона для приложения на Erlang. Вышло все гораздо проще и быстрее.
      Стандартные gen_server + gen_tcp (для управления) + application решают. Можно управлять и через веб-интерфейс с помощью web machine. К тому же решение будет кросс-платформенным.
        +1
        Ну это чуть разные тематики выходят. Одни используют Erlang, другие .net, третьи java, а кто-то чистый Си. Кому что по вкусу, то и используют. К тому же данный код очень легко расширятся для работы с BSD.
        И к тому же демон может быть не сетевой и использоваться для других целей (к примеру конвертация чего-либо и прочие)
          0
          да, тематики разные) просто не удержался – так меня поразил эрланг))
            +3
            основная беда — стоит кому-то поразиться очередной интересной штуке, как он начинает использовать эту штуку везде где только можно. Причём не всегда там где она и правда требуется. Ладно хоть шелл-скрипты на Эрланге вроде как не пишут, хотя не удивлюсь если и такой изврат кто-то практикует.
          +3
          Недавно имел дело с сервером, написанным на эрланге. Расплевался из-за того, что эрланговцы считают, что весь мир должен вокруг их VM крутиться. Долго и нудно закрывал лишние порты от всяких beam.smp, боролся с уродливыми конфигами на самом эрланге… Общее впечатление — поменьше программ на подобном.
            +1
            Ну, для определённых задач Эрланг и впрям хорош. Но вот когда я небольшой и ненагруженый сокет-сервер сделал на Node.js, эрланговцы меня заплевали. Такое ощущение что все делают только чаты с многосоттысячными коннектами и видеосерверы с дикой посещаемостью.
          +2
          Спасибо огромное за статью. Она как никогда кстати. Как раз подвернулась работка на эту тематику
            +8
            man daemon.

            Всю задачу «корректно форкнуться» сделали уже за вас.

            Кроме того, вовсе не обязтельно самим хранить pid — масса приложений использует обвязку из init.d для работы с pid'ом.
              +1
              Можно и daemon использовать, но хотелось показать именно как всё это работает.
                0
                Обвязка из init.d не всегда есть.
                В Debian, например, есть start-stop-daemon, но в том же Arch — нету вообще ничего подобного.
                  +2
                  скрипт в init.d поставляется мейнтейнером и является конфигурационным файлом. В нормальном режиме любой уважающий себя дистриубив о подобных проблемах должен думать и должен их решать нормально.

                  Как в арче не знаю, мне вполне хватает того, что большая тройка (RHEL/Centos, Debian и SUSE) эту задачу решила успешно.
                    –1
                    А какой смысл надеятся, на встроенные механизмы, которые не факт, что будут работать корректно во всех дистрибутивах, когда кода тут кот наплакал.
                      +1
                      Кода кот наплакал если писать его как попало. В статье PID файл записывается без обработки ошибок, например.
                        +1
                        Никакого смысла нет. Весь чужой код может оказаться нерабочим или неправильным. Именно этим принципом руководствуются те, кто пишут свои карманные ОС.
                        0
                        Не знаю что она там решила, но в CentOS 6.9 start-stop-daemon так и нету.
                      0
                      А это уже на любителя — как реализовать.
                      Я, например, вообще «тройную проверку» делаю: flock на бинарник, проверку pidfile и проверку дерева процессов в /proc. Хотя, возможно, это и излишне…
                    • НЛО прилетело и опубликовало эту надпись здесь
                        0
                        как вариант если нужен дамп процесса, то в обработке сигнала можно запустить gdb с указанием своего pid'a и пусть он сгенерирует дамп. Конечно костыль, но всё же идея имеет право на жизнь
                          0
                          Или еще проще — использовать gcore. Если надо тащить в себе весь функционал, то достаточно взять его из того же gcore
                            0
                            И что мы получим? Корку когда мы находимся в обработчике сигнала? Она же практически бесполезна. Разве не так? Нам нужна корка в месте, где сигнал произошел.
                          +1
                          Самое простое — делать abort() и, если ядро настроено соответсвующим образом, SIGALRM вызовет coredump.
                          А вообще есть еще вот такое code.google.com/p/google-coredumper/
                            +1
                            сорри, имел в виду SIGABRT, my bad
                            +1
                            Я обычно делаю в конфиге параметр коркаться всегда, 1 раз или никогда. Выглядит примерно так.
                            После 1 форка, но до запуска процесса мониторинга.

                            if (prctl(PR_SET_DUMPABLE, config.coredump != COREDUMP_OFF) < 0) {
                            err = -errno;
                            log_sys_err(«prctl(PR_SET_DUMPABLE, ...)»);
                            return err;
                            }

                            if(config.respawn) { // как раз и означает запуск этого процесса
                            while(true) {
                            pid_t pid = fork();
                            if (pid < 0) {
                            err = -errno;
                            log_sys_err(«error in fork»);
                            return err;
                            }

                            if (pid == 0)
                            break;

                            int status = 0;
                            if (waitpid(pid, &status, 0) < 0) {
                            err = -errno;
                            log_sys_err(«error in waitpid»);
                            return err;
                            }
                            int sig = WTERMSIG(status);
                            if (!sig)
                            _exit(0);

                            int core = WCOREDUMP(status);

                            log_err(«daemon killed: %s (core %sdumped)»,
                            strsignal(sig), (core? "": «not „));
                            if(core && config.coredump == COREDUMP_ONCE)
                            if(prctl(PR_SET_DUMPABLE, 0) < 0)
                            log_sys_err(“prctl(PR_SET_DUMPABLE, 0)»);
                            }
                            +1
                            Огромное спасибо за статью.

                            Я до сих пор оказывается в разработке под *nix дуб дубом был. Таких элементарных вещей не знал.

                            Единственный вопрос возник — как решить проблему крашащегося при запуске основной рабочей части демона? Он в приведенном коде будет циклически перезапускаться. Я думаю, это не очень хорошо. Можно ли как то это решить?
                              +2
                              Можно перед стартом потомка запомнить время, после завершения потомка проверить если он умер слишком рано (либо последние n раз он умерал слишком рано), ругнуться в лог и подождать к примеру минуту.
                                0
                                В принципе логично. Как я сам не догадался) Спасибо.
                              +1
                              Спасибо за статью. Я новичек в разработке под *nix, но этот код уже нагуглил)
                              А вот за программный разбор бектрейса — огромнейшее спасибо. Недавно искал причину рандомного SIGSEGV в своем демоне… нашел… но сразу выведенный бектрейс сэкономил бы мне пару дней)
                                +1
                                Если мы закрыли stdin/stdout/stderr, то нужно открыть их хотя бы в /dev/null, иначе при следующем open() мы попадем на стандартный дескриптор, а это вроде как не есть хорошо.
                                  0
                                  В данном случае я закрывал, чтобы показать что они не нужны, т.к. в коде нигде не используются printf и прочие функции работы с терминалом. Зато 3 дескриптора освобождается, и даже если open вернет их, то проблем не будет, если сразу предусмотреть это.

                                  А вообще в таком случае в больших проектах лучше действовать по такой схеме:
                                  1) если это дебаг версия, то ничего не закрывать и для логирования использовать printf и получать на экран весь лог
                                  2) если это релиз версия, то закрывать всё, а потом открывать файл лога и связывать его с STDOUT_FILENO чтобы также прозрачно (через printf) писать лог.
                                  +1
                                  apt-get install runit
                                  man runsv

                                  подробнее…
                                    0
                                    Лет 10 нечто подобное встраиваю во все свои резидентные демоны.
                                    А идею подглядел в книге Робачевский А.М. «Операционная система UNIX», где коротко описано, как открепить демона от родителя-стартера.
                                      0
                                      У вас плохая обработка ошибок. Часть сисколов может вернуть EINTR. Нет проверки wait() и т.п., chdir("/"), если свалились chroot() или close(0/1/2) и ошибка не обработана — ай-ай-ай.

                                      Что будет при ошибках в SetPidFile()? Должен быть аналог abort(). Внутри должны проверяться fprintf(), close(), иначе пид файл недозаписан. Также если вы сами решили обрабатывать пид файл, то вы должны обрабатывать ситуации с параллельной попыткой записи в пид файл (т.е. одновременно был сделан /etc/init.d/your_daemon start) и fopen() vs. unlink() race.
                                        0
                                        Такой вопрос:
                                        Существует ли утилита, демонизирующая любой процесс, даже не демонизирущая его, а отвязывающая от текущей сессии (чтобы можно было удаленно запустить, например, cloud-mail.ru, затем закрыть сессию)?

                                        Подсказка — есть такая утилита, называется spawn-fcgi, но она делает еще плюс ко всем другие действия, а мне надо просто отвязать процесс от сессии.
                                          0
                                          Если говорить по факту, то отвязка от сессии — это и есть демон. Такая утилита есть и называется start-stop-daemon.
                                          Обычно ее используют в управляющих скриптах, чтобы запускать программу как сервис при загрузке, и которая сама не становится демоном.
                                          У демона в целом есть еще ряд действий, но в целом для задачи, я думаю, неважно.
                                            0
                                            Оо, ништяк, спасибо, попробую сча.

                                        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                        Самое читаемое