Starting daemons in linux

Запуск приложения в режиме «daemon» в Linux

Часто случается ситуация, что нужно запустить приложение в Linux, которое не должно быть завершено при выходе пользователя, а режима демона у приложения нет.
На этот случай можно воспользоваться парой приемов.

Первый способ

Запустить в сессии программы screen, которая не завершается при выходе пользователя.

screen -d -m команда

использованные параметры:
-d -m — Запустить новый сеанс screen, но не подключаться к нему.

Так же можно использовать параметр:
-S имя — задать имя сессии screen для удобства поиска сессии по имени.

Пример:

screen -d -m -S backgroud_ping ping 127.0.0.1

Эта команда запустит пинг адреса 127.0.0.1 и присвоит сессии имя backgroud_ping.

Для возврата к приложению и получению управления нужно:

посмотреть список активный сессий screen:

в выводе консоли мы увидим имя и PID процесса:

There is a screen on:
1218.backgroud_ping (13.05.2016 15:43:34) (Detached)
1 Socket in /var/run/screen/S-root.

запущенная сессия будет иметь имя backgroud_ping, и в данном случае, PID будет 1218.

теперь остается подключиться к процессу screen, это делается командой:

screen -r номер_PID

в нашем случае это будет:

Мы получили обратно управление над приложением.

Второй способ

Использовать утилиту nohup, которая запустит приложение с игнорированием сигналов потери связи (SIGHUP), что позволит продолжить выполнение приложения после выхода пользователя из системы.

вывод будет перенаправлен вместо консоли в файл nohup.out, который будет находиться в папке из которой производился запуск. Посмотреть вывод консоли можно командой:

tail -f nohup.out

nohup ping 127.0.0.1 &

В данном случае вернуться к приложению и получить управление нельзя. его можно только завершить командой kill.

Третий способ

Использовать утилиту start-stop-daemon, которая создана для запуска в фоне приложений, которые сами не умеют переходить в фон.

start-stop-daemon -S -b -x путь_и_имя_исполняемого_файла -m -p путь_и_имя_pid_файла

использованные параметры:
-S — запустить;
-b — запустить в фоне;
-x — полный путь и имя исполняемого файла;
-m — создать PID-файл с номером процесса;
-p — путь и имя PID-файла.
Если требуется запустить программу с параметрами, то параметры указываются в конце, после двойного тире (подробнее — в примере).

start-stop-daemon -S -b -x /bin/ping -m -v -p /run/ping.pid — -I eth0 127.0.0.1

В данном примере мы запускаем утилиту ping с параметрами (-I eth0 127.0.0.1) и создаем PID-файл (/run/ping.pid).

Для остановки программы использутся другие параметры:

start-stop-daemon -K -x путь_и_имя_исполняемого_файла

start-stop-daemon -K -p путь_и_имя_pid_файла

использованные параметры:
-K — остановить;
-x — найти по имени запущенной программы;
-p — найти по PID-файлу .

start-stop-daemon -K -p /run/ping.pid

Находим номер процесса, записанный в файл /run/ping.pid и останавливаем его.

Более правильно всегда использовать PID-файлы, потому что имя запускаемой программы не всегда равно имени запущенного процесса.

Заказать создание и поддержку безопасной IT-инфраструктуры любой сложности

Быть уверенным в своей IT-инфраструктуре — это быть уверенным в завтрашнем дне.

Источник

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

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

  • Как добавить демона в автозагрузку?
  • Что делать, если в процессе работы произошла ошибка и демон рухнул?
  • Каким образом обновить конфигурацию демона без прерывания его работы?
Читайте также:  Finding hardware in 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 .

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();
>

if (pid == -1) // если произошла ошибка
<
// запишем в лог сообщение об этом
WriteLog( «[MONITOR] Fork failed (%s)\n» , strerror(errno));
>
else if (!pid) // если мы потомок
<
// данный код выполняется в потомке

// запустим функцию отвечающую за работу демона
status = WorkProc();

// завершим процесс
exit(status);
>
else // если мы родитель
<
// данный код выполняется в родителе

// ожидаем поступление сигнала
sigwaitinfo(&sigset, &siginfo);

Читайте также:  Olympus vn 4100pc драйвер windows

// если пришел сигнал от потомка
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);

* This source code was highlighted with Source Code Highlighter .

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;

// установим наш обработчик на сигналы

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

// блокируем сигналы которые будем ожидать
// сигнал остановки процесса пользователем
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 — функция осуществляющая обновление конфига (заново считать файл и внести необходимые изменения в свою работу). Имя файла можно также взять из параметров командной строки.

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

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

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

Читайте также:  Linux apt get help

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

* This source code was highlighted with Source Code Highlighter .

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

Источник

Оцените статью