Журнал LinuxFormat - перейти на главную

LXF131:DrBrown3

Материал из Linuxformat
Перейти к: навигация, поиск

Содержание

Шлем верные сигналы

Сигналы Подробное руководство о том, что это, откуда они взялись, куда идут и что происходит при отправке.

Сигнал – событие, отправляемое выполняющейся программе ядром или пользовательским процессом. Сигналы асинхронны, то есть могут приходить когда угодно, и их появление в большинстве случаев никак не связано с тем, что собиралась делать программа. Сигналы обычно означают некое внешнее событие, способное повлиять на выполнение программы. Программа может выбрать, что должно произойти по приходу сигнала определенного типа. Она может игнорировать его (за одним важным исключением); запустить специальный обработчик сигнала и продолжить с того места, где остановилась; или просто выполнить «действие по умолчанию» для этого сигнала, что в большинстве случаев означает завершение процесса.

За исключением сигналов «реального времени», существует 31 тип сигнала. Каждый тип определяется именем (например, SIGTERM) и целым числом (например, 15). Получить список всех типов можно командой:

$ kill -l

Из этих 31 администратор должен знать, полагаю, шесть. Я свел их в таблицу.

Имя Описание Действие по умолчанию
SIGHUP 1 Инициирует перенастройку фонового сервиса Завершить процесс
SIGINT 2 Ctrl+C с клавиатуры Завершить процесс
SIGQUIT 3 Ctrl+\ с клавиатуры Завершить процесс с дампом памяти
SIGKILL 9 Суперважный сигнал, который нельзя перехватить или игнорировать Завершить процесс
SIGSEGV 11 Программа пыталась обратиться к памяти по неверному адресу Завершить процесс с дампом памяти
SIGTERM 15 «Вежливая» просьба закончить работу Завершить процесс

Сигнал SIGHUP обычно используется для предупреждения демона (это системный сервис, работающий в фоне), что конфигурационный файл изменен и его нужно перечитать. Многие демоны так и реагируют на SIGHUP. Примеры включают «суперсервер Интернета» xinetd и демон системного журнала syslogd. Демонам, поддерживающим SIGHUP, лучше отправить этот сигнал, чем перезапустить их – тогда существующие соединения с клиентами сохранятся, а при останове и перезапуске демона они будут разорваны.

«HUP» – сокращение от «hang up» (повесить трубку). Изначально этот сигнал генерировался драйвером терминала последовательного порта с подключенным на прием звонков модемом. Если телефонное соединение обрывалось, драйвер терминала замечал, что несущая исчезла, и отправлял SIGH-UP оболочке, завершая ее. Идея состояла в том, чтобы гарантировать, что новый абонент не начнет случайно работать в оболочке предыдущего.

Сигнал SIGINT посылается активному процессу при нажатии Ctrl+C в его терминале. Его действие по умолчанию – завершить (убить) процесс. Поэтому «долгоиграющие» задания часто можно прервать, нажав Ctrl+C. Однако многие программы игнорируют или перехватывают SIGINT. Например, less при получении SIGINT не завершается, а запрашивает ввод команды.

Сигнал SIGQUIT посылается активному процессу при нажатии Ctrl+\ на клавиатуре. Он используется гораздо реже и менее известен, чем SIGINT. По умолчанию он также завершает процесс, но заодно заставляет оболочку создать файл core, содержащий образ памяти программы, для «посмертной» отладки. В большинстве дистрибутивов файлы core запрещены, потому что в настройке процесса ulimit (которая ограничивает использование ресурсов) максимальный размер файла core установлен в ноль. Чтобы проверить свои настройки, выполните команду ulimit -a и поищите встроенную команду ulimit на man-странице bash.

Смертоносные сигналы

Сигнал SIGTERM отправляется командой kill по умолчанию. Это вежливая просьба «пожалуйста, приберите за собой и завершитесь». При выключении системы процесс init отправляет сигналы SIGTERM всем выполняющимся процессам с просьбой завершиться. Если это не срабатывает, init применяет более суровые меры...

Сигнал SIGKILL – самый подлый из всех. Его нельзя перехватить или игнорировать, и его действие – прикончить процесс. Отправка SIGKILL должна быть крайней мерой, только в том случае, если более благородные SIGTERM и SIGINT не сработали. Разница между SIGTERM и SIGKILL – как между вежливой просьбой завершиться и выстрелом в голову. SIGKILL не дает процессу возможности прибраться. Перед перезапуском «убитых» программ, которые временно хранили данные на диске или находились посреди двухфазной фиксации транзакции, может потребоваться ручная расчистка.

Сигнал SIGSEGV (нарушение сегментации) отличается от прочих описанных здесь тем, что не приходит извне, а обусловлен самой программой. Он подается, когда программа пытается обратиться к адресу памяти вне отведенного ей диапазона. (Это легко сделать в C и C++, сославшись на неинициализированный указатель). Блок управления памятью видит эту попытку и тихо говорит ядру, которое отправляет SIGSEGV программе. Действие по умолчанию при получении этого сигнала – завершить программу. Если в программе нет ошибок, такого произойти не должно, но безупречных программ мало, и вы, наверное, видели, как программы падают из-за ошибки нарушения сегментации.

Сигналы SIGILL (неверная команда), SIGBUS (ошибка шины) и SIGFPE (исключение с плавающей точкой) генерируются подобным образом, когда программа пытается сделать такое, чего не разрешает аппаратная часть; но распространены они гораздо меньше. SIGSEGV легко продемонстрировать. Следующая программа на С из двух строк генерирует этот сигнал в ответ на попытку записать число по адресу памяти 0:

 main()
 { *((int*)0) = 0;}

История о SIGSEGV

В первый раз я общался с Unix в так называемой "Edition6". Я принес на работу копию, которую мне дал коллега в Открытом университете, и установил ее на наш PDP-11/60. На другой день я печатал некоторые man-страницы (другой печатной документации не было) и получил сообщение об «ошибке памяти». Решив, что компьютер неисправен, я выключил Unix и провел остаток дня за диагностикой оборудования, но никаких проблем не обнаружил. В конце концов я узнал, что «ошибка памяти» – это сообщение оболочки о том, что процесс завершился с SIGSEGV. Со временем я выяснил, что многие сообщения об ошибках в Unix были скорее причудами программиста, а не попыткой объяснить, что же на самом деле происходит. Моим любимым было «Это не пишущая машинка».

Отправка сигналов

Все сигналы в конечном счете доставляются ядром, но исходят из различных источников, как показано на рисунке. Для отправки сигналов из командной строки используется команда kill, обычно таким образом:

$ kill -SIGINT 11434 11559

11434 и 11559 – идентификаторы процессов-приемников.

Отправить сигналы можно только тем процессам, владельцем которых являетесь вы (если вы не root). Вместо имени сигнала допускается его номер; можно также не набирать SIG в начале имени, поэтому три следующих команды эквивалентны:

$ kill -SIGKILL 11434
$ kill -KILL 11434
$ kill -9 11434

Команда называется «kill», потому что во многих случаях ее результатом становится завершение процесса; но это не самое лучшее имя. «Throw» [выбросить] или «send-signal» [послать сигнал] было бы лучше.

Неудобство kill в том, что нужно знать числовой идентификатор процесса. Команда pkill позволяет задать процессы, которым отправляются сигналы, другими способами. Вот три примера:

$ pkill -SIGHUP syslogd
$ pkill -SIGTERM -U fred
$ pkill -SIGKILL -P 13579

Первый сигнал отправляется всем процессам, выполняющим syslogd. Второй – всем процессам, владельцем которых является fred, а третий – всем процессам, чей родительский PID 13579.

В оболочке есть встроенная команда trap, упрощающая обработку сигнала в сценариях без необходимости сочинять нудный код на C, как делают записные хакеры. Trap принимает два аргумента: команду (или набор команд) и имя сигнала, и обеспечивает выполнение указанных команд при получении сигнала с заданным номером. Попробуйте создать такой скрипт:

 trapecho I got a SIGHUP signal’ SIGHUP
 trapecho Terminating on SIGTERM; exit 1’ SIGTERM
 while true
 do
 	 sleep 1;
 	 date
 done

Сделайте его исполняемым и запустите в фоне – так вы легко сможете узнать идентификатор процесса:

$ chmod u+x trapdemo
$ ./trapdemo &
[1] 18729

Скрипт печатает дату и ждет секунду. Поэкспериментируйте с командой kill, отправляя сигналы различных типов с другого терминала. При отправке SIGHUP скрипт сообщит, что получил сигнал, и продолжит работу. В ответ на SIGTERM он выведет сообщение и завершится. При получении любого другого сигнала выполнится действие по умолчанию.

Советы программистам

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

Вот еще несколько вещей, для которых программисту могут пригодиться сигналы:

  • обработать SIGHUP для перечитывания конфигурационных файлов, обычно для демонов;
  • обработать SIGTERM, чтобы убрать за собой и корректно завершить работу;
  • обработать SIGCHLD, чтобы асинхронно собрать результаты завершения дочерних процессов;
  • обработать SIGALRM для реализации таймаута при блокирующих операциях;
  • обработать SIGUSR1 (или другой сигнал по вашему выбору), чтобы сообщить текущий статус процесса;
  • использовать сигнал, чтобы динамически включать и выключать отладочные сообщения.

Если вы серьезно заинтересовались сигналами с точки зрения программиста, раздобудьте книгу Ричарда Стивенса «UNIX. Профессиональное программирование» и погрузитесь в ее десятую главу.

Сигналы и статус выхода

Если программа запускается из командной строки и завершается сигналом, в оболочке появится сообщение об этом. Например, при завершении программы сигналом SIGTERM оболочка выводит сообщение “Terminated”. Для программы, убитой сигналом SIGKILL, выводится сообщение “Killed”, а для программы, совершившей самоубийство попыткой обратиться по неверному адресу памяти – сообщение “Segmentation fault”. По статусу выхода оболочка знает, что процесс завершен сигналом.

У программы, завершившейся нормально, статус выхода равен нулю. Программа, которая (добровольно) завершается с ошибкой, возвратит статус выхода в диапазоне 1–127. А программа, завершенная сигналом, возвратит статус выхода, равный 128 плюс номер соответствующего сигнала. Мы можем увидеть это, запустив заведомо кривую программу на C и просмотрев статус выхода путем вывода значения специальной переменной оболочки $?

$ ./forcesignal
Segmentation fault
$ echo $?
139

Здесь статус выхода 139 (128+11) сообщает нам о том, что программа завершилась по сигналу 11 (SIGSEGV), что мы преднамеренно и пытались спровоцировать.

Персональные инструменты
купить
подписаться
Яндекс.Метрика