«Правильный» способ запуска сценария оболочки в качестве демона
Я пишу скрипт , который я хотел бы работать в качестве демона при запуске без использования внешних инструментов , таких как DaemonTools или демон .
Linux Daemon Writing HOWTO
Согласно Linux Daemon Writing HOWTO , правильный демон имеет следующие характеристики:
- вилки из родительского процесса
- закрывает все файловые дескрипторы (то есть stdin , stdout , stderr )
- открывает логи для записи (если настроено)
- изменяет рабочий каталог на постоянный (обычно / )
- сбрасывает маску режима файла (umask)
- создает уникальный идентификатор сеанса (SID)
Демонизация Введение
Демон Введение идет дальше, утверждая , что типичный демон также:
- отсоединяется от терминала управления (если таковой имеется) и игнорирует все сигналы терминала
- отделяется от своей группы процессов
- ручки SIGCLD
Как бы сделать все это в sh , dash или bash сценарий только с общими инструментами Linux?
Сценарий должен быть в состоянии работать на максимально возможном количестве дистрибутивов без дополнительного программного обеспечения, хотя Debian является нашей основной задачей.
ПРИМЕЧАНИЕ. Я знаю, что в сети StackExchange есть множество ответов, в которых рекомендуется использовать nohup или setsid , но ни один из этих методов не отвечает всем вышеперечисленным требованиям.
EDIT: демон (7) страница руководства также дает некоторые указатели, хотя , кажется, некоторые различия между старым стилем SysV демонами и новыми из systemd них. Поскольку совместимость с различными дистрибутивами важна, пожалуйста, убедитесь, что ответ проясняет любые различия.
Используя systemd, вы сможете запустить скрипт как демон, создав простой модуль. Вы можете добавить множество различных опций , но это настолько просто, насколько вы можете получить.
Скажем, у вас есть сценарий /usr/bin/mydaemon .
Вы создаете юнит /etc/systemd/system/mydaemon.service .
Чтобы запустить демона, ты бежишь
Для запуска при загрузке вы включаете его
Если в системе, основанной на systemd, которой сегодня является большинство дистрибутивов Linux, это на самом деле не внешний инструмент. Негатив будет то, что он не будет работать везде, хотя.
Я, наверное, что-то здесь упускаю; почему именно не nohup подойдет? Конечно , это не достаточно , в одиночку , но в дополнение к нему кажется простым.
Насколько я вижу:
- выходные данные соответствующим образом перенаправляются (при необходимости используйте / dev / null)
- Маска наследуется
- stdin умирает в конце родительского сценария, однако
- сценарий daemon.sh переставляется в init (или systemd )
У меня сильное чувство, что я упускаю очевидное. Downvote, но, пожалуйста, скажите мне, что это такое 🙂
Команда Linux, screen содержащаяся в большинстве дистрибутивов, может демонизировать скрипт оболочки. Я использую это часто. Вот быстрый пример, чтобы начать, перечислить и выйти из отдельного сеанса экрана .
Источник
Власть над демонами или автозапуск в Linux
Для реализации автозапуска в Linux написано уже немало и на разных языках, но приходится искать, потому постарался свести большую часть тут. Здесь не рассказывается полностью весь процесс с нуля, но предоставлено достаточно информации и ссылок, чтобы сделать атоматический запуск программ в Linux реальностью.
Стоит сразу заметить — чтобы программа была полноценным сервисом/демоном, она должна быть соответствующе написана (link1, link2). Впрочем такое делают не всегда, хотя возможно это и не совсем правильно.
Существуют несколько способов сделать автозапуск программ в Linux:
- записать вызов программы/скрипта запуска в /etc/rc.local в фоновом режиме (&) (в разных дистрибутивах может лежать в разных местах, например, /etc/rc.d/rc.local) с перенаправленными потоками ввода/вывода в /dev/null. Например, «/home/user/my_prog 1 > /dev/null 2 > /dev/null &». Также, дополнительно, можно воспользоваться командой nohup;
- внести вызов в /etc/inittab, согласно правилам его оформления. В отличие от первого способа тут можно указать уровень запуска для программы;
- написать скрипт, позволяющий запускать/останавливать/перезапускать программу как демона, а также получать информацию о её состоянии.
Первый способ самый лёгкий, но и самый проблемный. Файл rc.local есть не во всех дистрибутивах. В нём нельзя задать уровень запуска. Если там записано несколько программ, то сложно ими управлять как сервисами (разве что запустить или остановить все одновременно). И, под конец, запуск из него подрывает устойчивость системы от взлома (примеры можно легко найти в поисковике).
Второй метод довольно экзотичный, сам узнал о нём совсем недавно, хотя пишут, что им пользуются многие администраторы. Тем не менее, используя его, нельзя оперировать запущенными таким способом программами как демонами, что довольно неудобно. Да и загромождать inittab некрасиво.
Последний метод на текущий момент самый «кошерный», но немного сложнее предыдущих (возможно, на первый взгляд). Именно им представлены все системные демоны, что говорит само за себя. Потому его и рассмотрю ниже.
Также есть способ автозапуска графических программ, но его опишу в конце, отдельно от остальных, т.к. он имеет недемоническую сущность.
Сразу обмолвлюсь, что у меня стоит Debian 6 и в других дистрибутивах пути могут несколько различаться.
Автозапуск программы как демона
Обычно в системе уже есть много подсказок как это сделать, но всё-таки приходится лазить по разным файлам и искать в интеренете дополнительную информацию. Это не значит, что я опишу тут каждую букву, но искать придётся меньше, надеюсь.
Для начала стоит заглянуть в каталог /etc/init.d. Здесь содержатся запускные скрипты всех сервисов, а также два файла для желающих написать себе такой же:
README и skeleton
skeleton содержит в себе болванку скрипта запуска с довольно подробными комментариями, а README его неплохо дополняет, не смотря на его небольшой размер. Также можно посмотреть и другие файлы и попытаться найти там что-то, что прояснит непонятную ситуацию.
В 6-ом debian`е для запускных скриптов демонов используется LSB (Linux Script Base) Init Standart. Почитать о нём подробнее можно тут. Для систем, где LSB не используется стоит взглянуть сюда.
Рассмотрим поближе файл skeleton. Первое с чего он должен начинаться, конечно же «#!/bin/sh», т.к. init-скрипт — запускной файл. Далее идёт комментированный заголовок:
Может показаться, что это просто лишняя информация от автора, но это не так. То, что указано здесь используется при прописывании скрипта в систему. Тут как раз пригодится файл README, который показывает, что в заголовке skeleton перечислены не все возможные параметры. Как минимум есть ещё следующие:
Все параметры и их полное описание (на английском) можно увидеть тут, а на русском тут и тут (спасибо awzrno за новые ссылки ^_^). К русскому варианту добавлю, что в Required-Start: можно прописать $all, тогда текущий скрипт будет запускаться после всех остальных (иногда это бывает нужно). Также X-Interactive: true показывает, что этот скрипт может взаимодействовать с пользователем, запросом на ввод чего-нибудь, например пароля.
Далее в skeleton идёт инициализация переменных, используемых в самом скрипте. Часть из них нужно будет настроить под свои нужды. Потом проверки на то, что сам демон существует и попытка прочитать конфигурационный файл (их имена должны быть указаны в переменных выше), далее загрузка переменных rcS, а потом идёт одна из самых интересных частей init-файла:
. /lib/lsb/init-functions
это определение LSB функций работы с логами, LSB-статусом сервиса, работы с процессом. В некоторых дистрибутивах этот файл может находиться в каталоге /etc/init.d. Названия и часть подробностей можно узнать непосредственно из комментариев к функциям в этом файле, а также тут.
Следующая часть — непосредственно тело скрипта. Тело состоит из условных частей, которые являются командами для демона: start, stop, restart/reload/force-reload, status. Кто-то выделяет их в отдельные функции, кто-то нет. На мой взгляд, функциями они выглядят эстетичнее и код более понятен. Все эти команды объединяет оператор выбора case, который и выбирает для исполнения нужный кусок кода, в зависимости от команды (параметра) с которой был запущен init-скрипт.
Таким образом для создания обычного скрипта достаточно подставить в переменные в начале файла нужные значения и, возможно, немного добавить кода в функции start/stop (например загрузку/выгрузку драйвера).
После того как файл будет готов его нужно скопировать в /etc/init.d и добавить в автозагрузку:
update-rc.d defaults
(или insserv для debian 6 stable и выше)
Удалить из автозагрузки можно так:
update-rc.d -f remove
(или insserv -r для debian 6 stable и выше)
Далее также можно использовать команды sysv-rc-conf в debian или service в fedora core, чтобы включить/выключить автозагрузку сервиса.
Автозапуск графического ПО без ввода паролей
Сама по себе реализация такой возможности понижает уровень защищённости ОС, т.к. войти может любой. Но бывают ситуации, когда это необходимо. Рассмотрю тут варианты только для двух основных графических менеджеров, т.к. других установленных под рукой нет.
Убрать запрос пароля на вход можно в центре управления (kcontrol) -> системное администрирование -> менеджер входа в систему -> удобства. Там выбрать пользователя, под которым входить (кроме рута) и поставить нужные галочки (разрешить автовход и вход без ввода пароля).
Чтобы сделать автозапуск программы нужно в каталог /home/ /.kde/Autostart добавить ссылку на запускной файл/скрипт нужного ПО.
Тут убрать запрос пароля на вход можно также в центре управления (gnome-control-center) -> Login Screen. Там, под рутом (ткнуть на замок, ввести пароль) выбрать пользователя, под которым входить (кроме суперпользователя).
Для автозапуска программы опять же в центре управления выбрать Startup Applications -> Add и заполнить маленькую форму.
Для обоих графических менеджеров:
Если нужно запустить под обычным пользователем, но от рута, то ещё надо настроить правила в /etc/sudoers на запуск конкретной программы/набора программ от имени суперпользователя (манами рекомендуется для безопасности делать это с помощью visudo). Как это делать рассказывать не буду, т.к. в man sudoers всё хорошо расписано.
Источник
Пишем собственный 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 и непосредственно добавлением в автозагрузку.
Источник