Запуск приложения в режиме «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 демон с возможностью автовосстановления работы
Уважаемые хабрапользователи, хотелось бы поделиться с вами опытом написания серверных демонов. В Рунете очень много статей по этому поводу, но большинство из них не даёт ответы на такие важные вопросы как:
- Как добавить демона в автозагрузку?
- Что делать, если в процессе работы произошла ошибка и демон рухнул?
- Каким образом обновить конфигурацию демона без прерывания его работы?
В рамках данной части рассмотрим следующие моменты:
- Принцип работы демона.
- Основы разработки мониторинга состояния демона.
- Обработка ошибок при работе, с подробным отчетом в лог.
- Некоторые вопросы связанные с ресурсами системы.
Для наглядности будет показан исходный код следующих частей:
- Шаблон основной программы.
- Шаблон функции мониторинга работы демона.
- Шаблон функции обработки ошибок.
- Ряд вспомогательных функций.
Принцип работы демона.
По суди демон это обычная программа выполняющаяся в фоновом режиме. Но так как наш демон будет запускаться из 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);
// если пришел сигнал от потомка
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 — функция осуществляющая обновление конфига (заново считать файл и внести необходимые изменения в свою работу). Имя файла можно также взять из параметров командной строки.
Данные функции зависят уже от вашей реализации демона.
Принципе работы следующий: устанавливаем свой обработчик на сигналы ошибок, затем запускаем все рабочие потоки и ждем сигналов завершения или обновления конфига.
Обработка ошибок при работе, с подробным отчетом в лог.
Конечно же демоны должны работать идеально и не вызывать всякого рода ошибок, но ошибаться может каждый, да и порой встречаются ошибки, которые довольно тяжело обнаружить на стадии тестирования. Особенно это актуально для ошибок которые проявляются при большой загруженности. По этому важным моментом в разработке демона является правильная обработка ошибок, а также выжимание из ошибки как можно большей информации. В данном случае рассмотрим обработку ошибок с сохранением стека вызовов (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 и непосредственно добавлением в автозагрузку.
Источник