Страницы

Поиск по вопросам

пятница, 15 февраля 2019 г.

Недопонимание с Linux на примере демона.

Чтобы создать демона (по крайней мере в Linux), то нужно предпринять ряд событий, которые я сейчас опишу. В конце вопросы, если кто-то разбирается, то просьба овтетить, буду признателен.
1) Нужно вызвать функцию fork() для создания дочернего процесса и завершить родительский процесс. Это делается для того, чтобы наш дочерний процесс запутился в фоновом режиме (если не прав, то поправляйте)
if( (pid = fork()) < 0) return -1; else if(pid) exit(0);
2) Затем мы вызываем функцию setsid(), которая создает новый сеанс. К тому же, вызывающий процесс становится ведущим в группе, ведущим процессом нового сеанса и не имеет контролирующего терминала.
if(setsid() < 0) return -1;
В книге "Разработка сетевых приложений" Стивенс У.Р, откуда я и беру этот пример, автор вызывает еще один раз функцию fork(), для того, чтобы гарантировать, что демон не сможет автоматически получить управляющий терминал, когда будет открывать устройство терминала. Заметьте, что при завершении родительского процесса (в нашем случае первого дочернего процесса), посылается всем процессам в сеансе (в том числе и нашему второму дочернему процессу) сигнал SIGHUP (when someone kills the terminal, without killing app running inside of termiтal window, OS sends the signal to the program), который нам нужно игнорировать.
3) Производим смену текущего каталога командой chdir("/");
4) Закрываем дескрипторы файлов. Автор делает это таким образом
for(i = 0; i < 64; i++) close(i);
И дальше больше... Остальной код приводить не буду, так как он не относится к сути вопросов, но в интеренете есть книга, которую я указал ранее, где вы можете найти пример полностью.
Собственно говоря, мои вопросы
1) Вопрос может показаться глупым, но я не совсем понимаю, что означает управляющий терминал. Это что-то вроде терминала из под root? Объясните, пожалуйста, что это и что он делает.
2) Зачем нужно менять текущий каталог? В книге об этом сказано мало и мне не совсем понятно
3) За что отвечают дескрипторы, которые мы закрываем? И нужно ли их вообще закрывать?


Ответ

Вопрос может показаться глупым, но я не совсем понимаю, что означает управляющий терминал. Это что-то вроде терминала из под root? Объясните, пожалуйста, что это и что он делает.
Если кратко, то это терминал связанный с текущей консольным сеансом (session). Указан в частности в столбце TTY ps'а. По факту он эквивалентен одному из устройств /dev/tty* для «настоящей» консоли или /dev/pts* для псевдотерминалов (unix98). Также есть специальное устройство /dev/tty, ссылающееся на управляющий терминал (controlling terminal) текущего процесса.
Основное назначение этой абстракции — переключение между группами процессов — только одна из которых может быть на переднем плане (foreground), а остальные в фоне (background). Также УТ (находясь в каноническом режиме) отвечает за посылку сигналов процессам находящимся на переднем плане при появлении на входе специальных символов (например ^C или ^Z) . Кроме того с помощью УТ программы могут запрашивать непосредственно у пользователя какие-то данные, например пароль, когда ввод/вывод перенаправлен, так в частности поступают ssh или sudo
Вообще говоря, в UNIX управление заданиями, да и всей подсистемы связанной с терминалами, — довольно запутанная вещь со множеством мелких и крупных кочек, большинство из которых навеяно историей и необходимостью сделать всё гибко, но в то же время чисто, так что описание назначения и деталей УТ, а также таких абстракций, как «группа процессов», «сессия», «лидер сессии» итд, заслуживают отдельной главы в книге или хотя бы статьи
2) Зачем нужно менять текущий каталог? В книге об этом сказано мало и мне не совсем понятно
В принципе это не обязательно, но является хорошей практикой освобождать неиспользуемые ресурсы. Например, если это не сделать, то нельзя будет отмонтировать ФС с каталогом откуда пользователь запустил демона.
3) За что отвечают дескрипторы, которые мы закрываем? И нужно ли их вообще закрывать?
То же что и предыдущее, не обязательно, но обычно является хорошей практикой. Первые три — это стандартные потоки ввода/вывода/ошибок, а остальные — другие дескрипторы, которые могут быть открыты — различные сокеты, файлы итп.
К сожалению, в POSIX нет простого и переносимого способа, который позволяет просто «закрыть все открытые дескрипторы» или хотя бы «запросить все открытые д.», поэтому обычной практикой является перебирать их все подряд, от нулевого до последнего и закрывать. Да, более переносимым вариантом, не полагающийся на магическую константу «64», будет:
for(int fd=0, maxfd=sysconf(_SC_OPEN_MAX); fdВ BSD-мире есть довольно удобный вызов closefrom (), который закрывает все дескрипторы выше указанного. В некоторых системах есть аналогичный вызов fcntl — F_CLOSEM
На Linux и некоторых других системах вместо этого можно посмотреть содержимое /proc/self/fd (или /dev/fd/*) и закрыть только действительно открытые.
Другие замечания по этому зоопарку расписаны здесь. Реальный пример поддержки различных систем с тоннами условной компиляции можно посмотреть, например, в openssh
Кроме того первые три дескриптора (stdin, stdout, stderr) стоит переоткрыть, например, на /dev/null, дабы избежать ошибок в дальнейшем.
Другие замечания
Алгоритм «демонизации», опустив обработку ошибок и прочее, можно записать в 4 строчки. Причём непосредственно к демонизации относится только первая, и частично вторая, а остальные — лишь желательная подчистка.
if( fork() ) exit (0); // Собственно демонизация setsid (); // отвязка от управляющего терминала if( fork() ) exit (0); // самозащита, чтобы случайно не подцепить новый УТ во время работы cleanup_everything (); // различная очистка и освобождение всех не нужных ресурсов

Комментариев нет:

Отправить комментарий