реентерабельной.
• Неправильное блокирование остальных сигналов. Большинство обработчиков сигналов не являются реентерабельными, однако, довольно часто в обработчиках, управляющих несколькими сигналами, не блокируются автоматически остальные сигналы. Применение sigaction()
помогает исправить ситуацию (если программист старательный).
• Неблокирование сигналов в тех областях кода, модифицирующих переменные, к которым обработчик сигналов также имеет доступ. (Такие зоны часто называются критическими областями.)
В то время как режимы состязаний, порожденные сигналами, могут показаться не опасными, сетевые коды, setuid- и setgid-программы могут использовать сигналы, посылаемые ненадежными пользователями. Передача экстренных данных в программу может стать причиной отправки SIGURG
, в то время как setuid- и setgid-программы могут принимать сигналы от пользователя, который запускает их, при этом фактические универсальные имена данных процессов не изменяются. Даже если эти программы изменяют свои действительные имена для предупреждения передачи сигналов, при закрытии пользователем терминала все программы, использующие этот терминал, посылают SIGHUP
.
22.3.7. Закрытие файловых дескрипторов
В системах Linux и Unix файловые дескрипторы, как правило, наследуются через системные вызовы exec()
(и всегда наследуются через fork()
и vfork()
). В большинстве случаев такое поведение нежелательно, поскольку только разделяться должны только stdin, stdout и stderr. Программы, запускаемые привилегированным процессом, не должны иметь доступа к файлам через унаследованный файловый дескриптор. Поэтому очень важно, чтобы программы внимательно закрывали все файловые дескрипторы, к которым не должна получить доступ новая программа. Это может стать проблемой, если ваша программа вызывает библиотечные функции, которые открывают файлы и не закрывают их. Одним из методов закрытия файловых дескрипторов является закрытие всех файловых дескрипторов вслепую из дескриптора номер 3 (тот, который следует сразу за stderr) произвольным большим значением (скажем, 100 или 1024)[165]. В большинстве программ это обеспечивает закрытие всех надлежащих файловых дескрипторов[166].
Наиболее удобным способом является установка флага закрытия после выполнения для каждого файла, который программа оставляет открытым на длительный период времени (включая сокеты и файловые устройства), что предотвращает получение доступа к данным файлам новыми запускаемыми программами. Описание флага закрытия после выполнения можно найти в главе 11.
22.4. Запуск в качестве демона
При разработке программ, создаваемых для работы в качестве системных демонов, нужно очень внимательно проводить их становление как демонов для правильного определения всех деталей. Ниже приведен перечень тех обстоятельств, на которые необходимо обратить внимание.
1. Большинство действий по инициализации должны быть произведены до того, как программа становится фактическим демоном. Это гарантирует, что пользователь при запуске не будет получать сообщения об ошибках, и будет возвращаться значащий код завершения. Этот вид деятельности включает в себя анализ конфигурационных файлов и открытие сокетов.
2. Текущий каталог должен быть изменен на какой-либо подходящий. Это может быть корневой каталог, но никогда не может быть тот каталог, из которого была запущена программа. Если демон этого не сделает, то, возможно, он будет работать соответствующим образом, но это не позволяет удалить тот каталог, из которого он был активизирован, поскольку он остается текущим каталогом программы. Если это возможно, то неплохо применить chroot()
на какой-то каталог. Причины обсуждались ранее в этой главе.
3. Все ненужные файловые дескрипторы должны быть закрыты. Это может показаться очевидным, однако вы легко можете упустить закрытие тех дескрипторов, которые были унаследованы, а не открыты самой программой. Об этом речь шла в предыдущем разделе.
4. Затем программа должна вызвать fork()
, а родительский процесс должен вызвать exit()
, позволяя программе, запустившей демон (чаще всего командному процессору), продолжить работу.
5. Дочерний процесс, продолжающий работу, должен закрыть stdin, stdout и stderr, поскольку он не будет больше использовать терминал. Вместо повторного применения файловых дескрипторов 0, 1 и 2 лучше открывать эти файлы как /dev/null
. Это гарантирует, что ни одна библиотечная функция, передающая отчеты о состоянии ошибок в stdout или stderr, не запишет эти ошибки в другие файлы, открытые демоном. При этом демон сможет запускать внешние программы, не беспокоясь об их выходных данных.
6. Для полного разъединения с терминалом, из которого был запущен демон, он должен вызвать setsid()
, чтобы разместить его в собственной группе процесса. Это предотвращает получение сигналов при закрытии терминала, а также сигналов управления заданиями.
Библиотека С предлагает функцию daemon()
, которая обрабатывает некоторые из перечисленных задач.
int daemon(int nochdir, in tnoclose);
Данная функция сразу осуществляет ветвление, и если оно прошло успешно, родительский процесс вызывает _exit()
с кодом завершения 0. Затем дочерний процесс переходит в корневой каталог, если nochdir
не является нулем, и перенаправляет stdin, stdout и stderr в /dev/null
, если noclose
не равен нулю. Перед возвратом в дочерний процесс она также вызывает setsid()
. При этом унаследованные файловые дескрипторы все равно могут оставаться открытыми, поэтому в программах, использующих daemon()
, необходимо следить за ними. Если возможно, в программе также нужно использовать chroot()
.
Часть IV
Библиотеки для разработки
Глава 23
Сопоставление строк
Осуществлять сравнение строк можно не только с помощью функции strcmp()
или даже strncmp()
. Linux предлагает несколько общих функций сопоставления строк, использование которых позволяет упростить решение задач программирования. Мы рассмотрим сначала самые простые примеры, а затем перейдем к более сложным.