IgorKa — Информационный ресурс
Немного обо всем и все о немногом, или практический опыт системного администратора.

| Пн | Вт | Ср | Чт | Пт | Сб | Вс |
|---|---|---|---|---|---|---|
| « Окт | Дек » | |||||
| 1 | ||||||
| 2 | 3 | 4 | 5 | 6 | 7 | 8 |
| 9 | 10 | 11 | 12 | 13 | 14 | 15 |
| 16 | 17 | 18 | 19 | 20 | 21 | 22 |
| 23 | 24 | 25 | 26 | 27 | 28 | 29 |
| 30 | ||||||
Лекция №13 Сигналы в Linux
11 ноября 2009, 11:36
Прошлая лекция была посвящена процессам в Linux. Сегодня мы поговорим о взаимодействии процессом между собой, а также о том как мы можем воздействовать на процессы. Сначала посмотрим как процессы могу взаимодействовать между собой. Мы уже писали в командной строке конструкции подобные этой: less /etc/group | grep user . В этом примере процесс less взаимодействует с процессом grep посредством механизма, который называется неименований канал или пайп (pipe — канал). Мы не будем вдаваться в подробности, просто запомните, что посредством символа |, который можно в данном случае называть “пайпом” информация (результат выполнения) процесса less идет на вход другого процесса — grep. Таким образом один процесс передал информацию другому процессу.
Еще один способ общения процессов — это именованные каналы. Изучение именованный каналов не входит в этот курс, но на практическом примере расскажу, что это. Именованный канал можно создать командой mkfifo:
igor@adm-ubuntu:
/linux$ mkfifo my_pipe
igor@adm-ubuntu:
/linux$ ls -l | grep my_pipe
prw-r–r– 1 igor igor 0 2009-11-09 17:59 my_pipe
Теперь в одной консоли выполните команду:
/linux$ echo Hello > my_pipe
Как видите команда не завершает свою работу, а ждет. Зарегистрируйтесь еще в одной консоли и выполните команду:
/linux$ cat my_pipe
Hello
Если вернуться на первую консоль, то вы увидите, что команда echo завершила свою работу. Таким образом через именованный канал my_pipe команда (процесс) echo передала информацию (слово Hello) процессу cat, который ее принял и вывел на экран.
Давайте теперь рассмотрим основной способ “общения” процессов — сигналы. Один процесс при помощи ядра может передать другому процессу специальное числовое значение сигнала. Процесс вызывает функцию передачи сигнала и передает необходимую информацию (код сигнала, PID процесса) ядру. Ядро передает сигнал процессу получателю и отслеживает как этот сигнал обрабатывается. Сигналы обозначаются цифрами или мнемоническими обозначениями. Перечень сигналов можно вывести командой kill -l.
Мнемонические имена которые вы видите (SIGTERM, SIGINT, SIGKILL) начинаются с приставки SIG. Имена в этом виде используются в языках программирования таких как С. В интерпретаторе bash используются или числа или мнемонические имена, но без приставки SIG — TERM, INT, KILL.
Часть сигналов (INT, TERM) являются перехватываемыми. Это означает, что процесс при получении такого сигнала должен перейти на специальную подпрограмму, которая занимается обработкой сигнала. Если подпрограммы обработки нет (а ее написанием занимаются разработчики программы, которая выполняется в контексте процесса), то управление передается ядру, которое выполняет действия по умолчанию, описанные для каждого сигнала.
Часть сигналов являются такими которые можно заблокировать. Например, один процесс посылает сигнал TERM другому процессу, а он в свою очередь не закончил операции ввода/вывода. В таком случае второй процесс может заблокировать полученный сигнал (опять таки в обработчике сигнала) до момента выполнения необходимой операции ввода/вывода. Сигнал TERM — это сигнал корректного завершения работы процесса. Обработчик этого сигнала, должен выполнить все необходимые действия для правильного завершения работы.
И есть третья группа сигналов, которые не блокируются и для которых не нужны обработчики. Примером такого сигнала является сигнал KILL. Этот сигнал уничтожает процесс, так как для него нет обработчиков в процессах, то он будет обрабатываться ядром по умолчанию и так как он не блокируемый, то действия будут выполнятся немедленно.
Пока мы говорили о том как процессы “общаются” между собой с помощью сигналов. Но мы (пользователи) также можем посылать сигналы процессам. Например, комбинация клавиш Ctrl+C посылает процессу сигнал INT, который прерывает выполнение процесса. Если вы наберете в терминале команду sleep 100, то команда не вернет управление терминалу пока не завершится. Прервать выполнение этой команды можно нажав комбинацию клавиш Ctrl+C.
В чем же отличия между похожими сигналами INT, TERM, KILL (а также QUIT и HUP)? Несмотря на похожесть отличия есть:
Сигнал KILL не блокируется и не перехватывается и ведет к немедленному завершению процесса.
Сигнал INT в отличии от KILL является блокируемым сигналом и перехватываемым.
Сигнал TERM также является перехватываемым и блокируемым и предназначен для корректного (предпочтительного) завершения работы процесса.
Сигнал QUIT — похож на TERM, но позволяет сохранить дамп памяти.
Сигнал HUP — сейчас этот сигнал чаще всего интерпретируется процессами как “прочесть конфигурационные файлы”.
Рассмотрим два сигнала: STOP и CONT. Сигнал STOP останавливает процесс, то есть процесс переходит в состояние “остановленный“. В таком состоянии процесс будет до тех пор пока снова не получит сигнал. Если будет получен сигнал CONT, то процесс возобновит свою работу с того момента как он был остановлен. Практический пример:
Наберите в терминале команду sleep 1000 &.
Затем проверьте, что процесс находится в состоянии ожидания, о чем нам говорит буква S в столбце STAT:
$ ps x | grep [s]leep
PID TTY STAT TIME COMMAND
6301 pts/1 S 0:00 sleep 1000
Теперь пошлем процессу сигнал STOP. Для этого используем команду kill (kill -название процесса PID процесса):
$ kill -STOP 6301
[1]+ Stopped sleep 1000
Проверяем статус процесса:
igor@ubuntu:
$ ps x | grep [s]leep
PID TTY STAT TIME COMMAND
6301 pts/1 T 0:00 sleep 1000
Видим, что процесс действительно находится в состоянии “остановленный” (символ T в столбце STAT).
Теперь отправим процессу сигнал продолжения работы (CONT) и проверим состояние:
igor@ubuntu:
$ kill -CONT 6301
igor@ubuntu:
$ ps x | grep [s]leep
PID TTY STAT TIME COMMAND
6301 pts/1 S 0:00 sleep 1000
Если необходимо корректно завершить процесс, то необходимо послать ему сигнал TERM:
igor@ubuntu:
$ kill -TERM 6301
igor@ubuntu:
$ ps x | grep [s]leep
[1]+ Terminated sleep 1000
igor@ubuntu:
$ ps x | grep [s]leep
Если сразу же после посылки сигнала TERM выполнить команду ps x | grep [s]leep, то можно успеть увидеть сообщение о том, что процесс завершает работу, в при следующей попытке вывести информацию о нашем процессе sleep мы уже ничего не увидим — процесс прекратил существование. Команда kill без указания сигнала, по умолчанию передает процессу именно сигнал TERM. Поэтому можно было написать просто kill 6301.
Если необходимо срочно завершить процесс, или процесс не завершается по сигналу TERM, то тогда необходимо послать процессу сигнал KILL:
igor@ubuntu:
$ sleep 1000 &
[1] 6348
igor@ubuntu:
$ kill -KILL 6348
igor@ubuntu:
$ ps x | grep [s]leep
[1]+ Killed sleep 1000
Если необходимо послать один и тот же сигнал нескольким процессам, то можно перечислить их через пробел: kill -TERM 2345 3456 4567.
Команда kill довольно ограничена в возможностях и не позволяет выполнять более сложные действия. Поэтому рассмотрим еще одну команду — killall. Основное преимущество этой команды, то что она умеет посылать сигналы всем процессам с одинаковым именем или всем процессам одного пользователя. Запустите несколько раз подряд команду sleep 1000 &:
$ ps x | grep [s]leep
6460 pts/1 S 0:00 sleep 1000
6461 pts/1 S 0:00 sleep 1000
6462 pts/1 S 0:00 sleep 1000
6463 pts/1 S 0:00 sleep 1000
6464 pts/1 S 0:00 sleep 1000
6465 pts/1 S 0:00 sleep 1000
6466 pts/1 S 0:00 sleep 1000
Теперь, чтобы завершить все процессы с именем sleep, достаточно набрать команду killall sleep:
igor@ubuntu:
$ killall sleep
[1] Terminated sleep 1000
[2] Terminated sleep 1000
[3] Terminated sleep 1000
[4] Terminated sleep 1000
[6]- Terminated sleep 1000
[7]+ Terminated sleep 1000
[5]+ Terminated sleep 1000
Выполните команду sleep 1000 & еще несколько раз, а затем зарегистрируйтесь в другой консоли от имени другого пользователя (например, test) и также от его имени выполните команду sleep 1000 &. Теперь вернитесь в свою консоль и просмотрите процессы sleep всех пользователей:
$ ps aux | grep [s]leep
igor 6540 0.0 0.0 2952 628 pts/1 S 22:30 0:00 sleep 1000
igor 6541 0.0 0.0 2952 632 pts/1 S 22:30 0:00 sleep 1000
igor 6542 0.0 0.0 2952 628 pts/1 S 22:30 0:00 sleep 1000
test 6543 0.0 0.0 2952 632 pts/3 S 22:30 0:00 sleep 1000
test 6544 0.0 0.0 2952 628 pts/3 S 22:30 0:00 sleep 1000
test 6545 0.0 0.0 2952 628 pts/3 S 22:30 0:00 sleep 1000
test 6546 0.0 0.0 2952 632 pts/3 S 22:30 0:00 sleep 1000
Теперь для того, чтобы удалить процессы только пользователя test необходимо выполнить (от имени пользователя root) команду killall -u test:
igor@ubuntu:
$ sudo killall -u test
igor@ubuntu:
$ ps aux | grep [s]leep
igor 6540 0.0 0.0 2952 628 pts/1 S 22:30 0:00 sleep 1000
igor 6541 0.0 0.0 2952 632 pts/1 S 22:30 0:00 sleep 1000
igor 6542 0.0 0.0 2952 628 pts/1 S 22:30 0:00 sleep 1000
Команда выше удалит не только процессы sleep, но вообще все процессы пользователя test. Если необходимо удалить конкретно процессы sleep, то тогда команду нужно было записать так: killall -u test sleep.
Если запустить команду killall c ключом -i, то перед посылкой сигнала будет запрашиваться подтверждение:
$ killall -i sleep
Прибить sleep(6540) ? (y/N) n
Прибить sleep(6541) ? (y/N) n
Прибить sleep(6542) ? (y/N) n
Следующая лекция будет завершающей по теме процессов и сигналов Linux. Мы поговорим о заданиях (jobs), командах jobs, fg, bg, strong>nohup, и top.
Как чаще всего процессы обрабатывают сигнал term
Автор: Станислав Лапшанский, slapsh@slapsh.pp.ru
Опубликовано: 2.7.2002
2002, Издательский дом «КОМПЬЮТЕРРА» | http://www.computerra.ru/
Журнал «СОФТЕРРА» | http://www.softerra.ru/
Этот материал Вы всегда сможете найти по его постоянному адресу:═ http://www.softerra.ru/freeos/18735/
Статья является переводом текста Dru Lavigne, опубликованного по адресу: http://www.onlamp.com/pub/a/bsd/2000/11/22/FreeBSD_Basics.html .
В первой части статьи мы узнали что представляют из себя процессы, а так же как посмотреть список запущенных процессов на вашей FreeBSD. В этой части мы узнаем как процессы общаются между собой, как вы можете что-нибудь передать процессу и зачем вам это может понадобиться.
Ход общения процессов называется межпроцессным взаимодействием. Процессы не имеют права просто передавать что-нибудь кому-нибудь. В FreeBSD существует 31 предопределенное сообщение. Эти сообщения называются сигналами. Вы можете посмотреть список сигналов набрав:
Каждый сигнал имеет номер, и в этом списке они перечислены в порядку номеров. Таким образом HUP=1, INT=2 и т.д. Процесс может посылать эти сигналы другим процессам. Пользователи могут тоже самое.
Для того, что бы понять, что означает каждый из этих сигналов наберите man 2 sigaction или man 3 signal. Следующая ниже таблица кратко описывает каждый из 31 сигналов, а также результаты их применения. Поскольку пользователи так же могут посылать любые из этих сигналов, рядом с наиболее часто используемыми сигналами я поставил звездочку.
| Название сигнала | # | Действие по умолчанию | Описание |
|---|---|---|---|
| *HUP | 1 | уничтожить процесс | оборвалась связь с терминалом |
| *INT | 2 | уничтожить процесс | прерывание программы |
| *QUIT | 3 | создать дамп памяти | выход из программы |
| ILL | 4 | создать дамп памяти | запрещенная инструкция |
| TRAP | 5 | создать дамп памяти | отладочное прерывание |
| ABRT | 6 | создать дамп памяти | вызов функции abort |
| EMT | 7 | создать дамп памяти | была выполнена эмулируемая инструкция |
| FPE | 8 | создать дамп памяти | исключение при операциях с плавающей точкой |
| *KILL | 9 | уничтожить процесс | убить программу |
| BUS | 10 | создать дамп памяти | ошибка на шине |
| SEGV | 11 | создать дамп памяти | нарушение сегментации |
| SYS | 12 | создать дамп памяти | вызов несуществующей системной программы |
| PIPE | 13 | уничтожить процесс | запись в канал при отсутствии чтения |
| ALRM | 14 | уничтожить процесс | истек таймер реального времени |
| *TERM | 15 | уничтожить процесс | программный сигнал на уничтожение процесса |
| URG | 16 | игнорирование сигнала | неотложное условие |
| *STOP | 17 | остановить процесс | останов (не может игнорироваться) |
| *TSTP | 18 | остановить процесс | сигнал стоп с клавиатуры |
| CONT | 19 | игнорирование сигнала | продолжить после останова |
| CHLD | 20 | игнорирование сигнала | статус порожденного процесса изменился |
| TTIN | 21 | остановить процесс | попытка фонового чтения |
| TTOU | 22 | остановить процесс | попытка фоновой записи |
| IO | 23 | игнорирование сигнала | ввод/вывод возможен |
| XCPU | 24 | уничтожить процесс | исчерпан лимит процессорного времени |
| XFSZ | 25 | уничтожить процесс | исчерпан лимит на размер файла |
| VTALRM | 26 | уничтожить процесс | сигнал от виртуального таймера |
| PROF | 27 | уничтожить процесс | сигнал от таймера профайлера |
| WINCH | 28 | игнорирование сигнала | изменение размеров окна |
| INFO | 29 | игнорирование сигнала | запрос статуса с клавиатуры |
| USR1 | 30 | уничтожить процесс | Определенный пользователем сигнал 1 |
| USR2 | 31 | уничтожить процесс | Определенный пользователем сигнал 2 |
Некоторые из сигналов использовались пользователями столь часто, что получили свои клавиатурные сокращения. Для просмотра этих сокращений посмотрите четыре последние строки вывода команды stty -e:
Символ «^» означает что вы должны нажать клавишу «ctrl», а затем указанную за ним букву. Обратите внимание, что три сигнала были привязаны к управляющим последовательностям:
- ^C привязан к сигналу INT (сигнал 2)
- ^\ привязан к сигналу QUIT (сигнал 3)
- ^Z привязан к сигналу TSTP (сигнал 18, хотя здесь он называется susp)
Не путайте слово «kill» в выводе команды stty с сигналом KILL (сигнал 9). Комбинация ^U удаляет строку, а не шлет сигнал номер 9. Для того что бы в этом убедиться, напечатайте в командной оболочке длинную строку, а затем нажмите ^U.
Но как послать сигнал, который не имеет соответствующей комбинации клавиш? Используйте для этого команду kill.
Есть пара способов использования команды kill. Если вы просто напечатаете:
то по умолчанию процессу с идентификатором PID будет послан сигнал TERM. Если вы хотите послать какой-нибудь другой сигнал, то в команде укажите его название или номер:
Таким образом команды
эквивалентны. Не забывайте, что в UNIX имеет значение регистр набранных команд, если вы напечатаете:
то получите следующее сообщение об ошибке:
Итак теперь мы знаем о каждом из 31 возможных сообщений, а так же можем посылать их различным процессам. Давайте рассмотрим причины, по которым вам может потребоваться послать процессу сигнал. Когда вы прорабатываете какой-нибудь вопрос используя FreeBSD Handbook или другое руководство, их авторы часто обучают как и что менять в тех или иных конфигурационных файлах, а затем говорят вам о необходимости послать сигнал HUP. Дело в том, что большинство процессов прочитывают свои конфигурационные файлы только при первоначальном запуске. Сигнал HUP говорит процессу, что он должен прекратить выполнение. После того как процесс перезапустится, он перечитает конфигурационные файлы и внесенные в них изменения вступят в силу. Аналогичным образом, когда вы выходите командой logout из терминала, сигнал HUP рассылается всем процессам, которые были запущены на этом терминале. Это значит, что все процессы которые выполнялись на этом терминале будут остановлены.
Иногда вы можете запустить процесс и захотеть его остановки, до того, как он завершится в штатном режиме. Например в приливе вдохновения вы можете решить, что вам необходимо посмотреть имена всех файлов в вашей системе. Это можно сделать написав следующее:
Однако, вероятнее всего вы быстро утомитесь нажимать пробел и поймете, что на самом деле вам вовсе не хочется в данный момент просматривать список всех ваших файлов. Другими словами вам захочется подать прерывающий сигнал. Один из путей сделать это, нажать на терминале «Ctrl+C»:
То, что на вашем терминале появится приглашение интерпретатора команд, свидетельствует о том, что посланный вами сигнал INT сработал.
Опять выполните ту же самую команду find, но в этот раз пошлите сигнал 3, нажав на клавиатуре «Ctrl+\»:
Теперь, перед тем, как вы получите приглашение интерпретатора команд, вы увидите следующее сообщение:
Если вы воспользуйтесь комбинацией Alt+F1, что бы посмотреть сообщения системной консоли, там вы увидите сообщение примерно следующего содержания:
Если теперь вы вернетесь на предыдущий терминал и посмотрите список файлов в каталоге, среди прочего вы обязательно найдете файл more.core. Обычно вам никогда не потребуется посылать процессу сигнал номер 3, если конечно вы не программист, который знает как использовать отладчик ядра. Я включил этот пример в статью для того что бы показать разницу между сигналами 2 и 3. Удаляйте core-файлы без опаски.
Межпроцессное взаимодействие (в оригинале используется термин «межпроцессные коммуникации» — прим. переводчика) практически ни чем не отличается от любых других видов коммуникаций: вы или какой-нибудь процесс можете послать сигнал в надежде на определенный результат, однако процесс получающий этот сигнал распоряжается с ним «на свое усмотрение». Помните, что процессы — это всего лишь запущенные программы. Большинство программ используют процедуры «обработки сигналов», применяемые для того, что бы решать что и как надо делать с поступившими сигналами в данный момент времени. Обычно, если вы посылаете процессу какой-нибудь из сигналов останавливающих выполнение программы, процедура обработки сигналов этого процесса пытается корректно закрыть все используемые программой файлы для предотвращения потери данных после останова. В некоторых случаях обработчик сигналов может просто проигнорировать поступивший процессу сигнал и отклонить запрос на останов программы (обычно так поступают зависшие программы, которые на момент поступления сигнала уже не работают должным образом — прим. переводчика).
Однако (к счастью — прим. переводчика), некоторые сигналы не могут быть проигнорированы программой. Это например девятый и семнадцатый сигналы. Представим, что вы хотите остановить процесс который вы некоторое время назад запустили. Воспользовавшись связкой команды ps и grep, вы узнали PID процесса, а затем при помощи команды kill послали ему сигнал TERM, а затем решили проверить остановлен ли процесс повторив команду ps:
Однако при повторе команды ps вы опять обнаружили этот процесс в списке, а это значит, что по каким-то причинам сигнал TERM был проигнорирован. Любая из этих двух команд исправит ситуацию:
Если вы теперь повторите команду ps, то вы должны будете получить пустой список, что свидетельствует об успешном останове процесса.
Вы можете спросить: «Почему бы всегда не посылать процессам сигнал 9, если он не может быть игнорирован?». Дело в том, что сигнал 9 на самом деле просто «убивает» процесс, не давая ему времени на корректное сохранение всех обработанных данных, что означает, что при применении сигнала 9 могут быть потеряны данные (никогда не применяйте сигнал номер 9 без крайней на то необходимости — прим. переводчика). Намного лучше попробовать для начала послать процессу какой-нибудь другой сигнал останова, а сигнал номер 9 иметь «про запас» для процессов упрямо игнорирующих другие сигналы. Не забывайте так же, что если вы работаете от имени обычного пользователя, то вы сможете посылать сигналы только процессам, владельцем которых являетесь. Суперпользователь root может посылать сигналы любым процессам.
Может возникнуть ситуация, когда вам захочется остановить все принадлежащие вам процессы. Результаты этого действия будут отличаться в зависимости от того находитесь ли вы в системе как обычный или как суперпользователь.
Продемонстрируем это. Войдите в систему на другом терминале и введите команду ps:
В этом примере я вошел в систему с терминалов 0, 1, 2, 3. Я запустил команду ps с консоли (терминал 0 по совместительству выполняет роль системной консоли — прим. переводчика), на первом терминале запущена оболочка командного процессора, на втором — запущен браузер lynx и на третьем у меня запущен сеанс X Window. И так я являюсь владельцем 10 процессов. Если в команде kill я воспользуюсь идентификатором процесса (PID) равным -1, я отправлю указанный в команде сигнал всем принадлежащим мне процессам. так попробуем послать сигнал TERM таким образом:
А теперь проверим результаты, воспользовавшись командой ps:
Обратите внимание — мы остановили шесть процессов, однако четыре оставшиеся проигнорировали сигнал TERM. Давайте будем более агрессивными:
Если вы «пройдетесь» по тем четырем терминалам, на которых выполнялись ваши программы, то на трех из них вы увидите приглашение войти в систему. Последняя команда kill уничтожила все процессы, за исключением своего родительского процесса, т.е. командного интерпретатора C shell, в котором вы набрали команду kill (так произошло потому, что структура процессов в UNIX древовидна и каждый процесс должен иметь своего родителя — прим. переводчика).
Обратите внимание, что если вы допустите ошибку при наборе команды и напишете:
то вы получите сообщение об ошибке:
Дело в том, что -1 это специальный идентификатор процесса, который обозначает «все процессы», а 1 это идентификатор процесса с именем init. Только суперпользователь может останавливать процесс init. К тому же суперпользователь должен останавливать процесс init только при условии того, что он знает что делает.
Теперь давайте поглядим что произойдет, если мы повторим то же упражнение, но только от имени суперпользователя. Для начала на моем тестовом компьютере (где выполняются следующие программы: apache, mysql, squid, nfs и т.п.) я выполню команду ps:
Теперь я пошлю сигнал KILL специальному идентификатору -1 от имени суперпользователя:
Эта команда произвела на меня большее впечатление чем предыдущая, поскольку я был выкинут из командного интерпретатора в котором только что набрал команду kill. После того, как я опять вошел в систему, я определил масштаб разрушений следующим образом:
Когда суперпользователь посылает сигнал идентификатору -1, он рассылается всем процессам за исключением системных. Если этим сигналом будет KILL, то вы наслушаетесь жалоб от простых пользователей, у которых будут потеряны все открытые ими, но не сохраненные файлы данных.
Это является одной из причин, по которой только суперпользователь может выполнять команды reboot и halt. Когда одна из этих команд запускается на выполнение, то всем процессам рассылается сигнал TERM, для того что бы дать им шанс для сохранения данных, поскольку за сигналом TERM, через некоторое время, следует сигнал KILL, который посылается для того что бы гарантированно уничтожить все процессы.
В следующей статье я продолжу эту тему, заострив внимание на процессах init и getty.
Функция обработчик сигналов
Данная функция вызывается, когда процесс (или нить) получает неблокируемый сигнал. Дефолтный обработчик завершает наш процесс (нить). Но мы можем сами определить обработчики для интересующих нас сигналов. Следует очень осторожно относится к написанию обработчика сигналов, это не просто функция, выполняющаяся по коллбеку, происходит прерывание текущего потока выполнения без какой либо подготовительной работы, таким образом глобальные объекты могут находится в неконсистентном состоянии. Автор не берется приводить свод правил, так как сам их не знает, и призывает последовать совету Kobolog (надеюсь он не против, что я ссылаюсь на него) и изучить хотя бы вот этот материал FAQ.
Установить новый обработчик сигнала можно двумя функциями
- функция не блокирует получение других сигналов пока выполняется текущий обработчик, он будет прерван и начнет выполняться новый обработчик
- после первого получения сигнала (для которого мы установили свой обработчик), его обработчик будет сброшен на SIG_DFL
- sa_handler — аналогичен sighandler_t в функции signal
- sa_mask — маска сигналов который будут блокированы пока выполняется наш обработчик. + по дефолту блокируется и сам полученный сигнал
- sa_flags — позволяет задать дополнительные действия при обработке сигнала о которых лучше почитать тут
Здесь мы установили наш обработчик для сигналов SIGUSR1 и SUGUSR2, а также указали, что необходимо блокировать эти же сигналы пока выполняется обработчик.
С обработчиком сигналов есть один не очень удобный момент, он устанавливается на весь процесс и все порожденные нити сразу. Мы не имеет возможность для каждой нити установить свой обработчик сигналов.
Но при этом следует понимать что когда сигнал адресуется процессу, обработчик вызывается именно для главной нити (представляющей процесс). Если же сигнал адресуется для нити, то обработчик вызывается из контекста этой нити. См пример 1.
Блокирование сигналов
Для того, чтобы заблокировать некоторый сигналы для процесса, необходимо добавить их в маску сигналов данного процесса. Для этого используется функция
Мы можем к уже существующей маске сигналов добавить новые сигналы (SIG_BLOCK), можем из этой маски убрать часть сигналов (SIG_UNBLOCK), а так же установить полностью нашу маску сигналов (SIG_SETMASK).
Для работы с маской сигналов внутри нити используется функция
которая позволяет сделать все тоже, но уже для каждой нити в отдельности.
Невозможно заблокировать сигналы SIGKILL или SIGSTOP при помощи этих функций. Попытки это сделать будут игнорироваться.
sigwait
Данная функция позволяет приостановить выполнении процесса (или нити) до получения нужного сигнала (или одного из маски сигналов). Особенностью этой функции является то, что при получении сигнала не будет вызвана функции обработчик сигнала. См. пример 2.
Посыл сигнала
Для того, чтобы послать сигнал процессу можно использовать две функции
С первой все понятно. Вторая нужна для того, чтобы послать сигнал самому себе, и по сути равносильна kill(getpid(), signal). Функция getpid() возвращает PID текущего процесса.
Для того, чтобы послать сигнал отдельной нити, используется функция
Пример использования сигналов
Все, что я описал выше, не дает ответа на вопрос «Зачем мне использовать сигналы». Теперь я хотел бы привести реальный пример использования сигналов и где без них попросту не обойтись.
Представьте, что вы хотите читать или писать какие-то данные в какое то устройство, но это может привести к блокированию. Ну например, чтение в случае работы с сокетами. Или может быть запись в пайп. Вы можете вынести это в отдельный поток, чтобы не блокировать основную работу. Но что делать когда вам нужно завершить приложение? Как корректно прервать блокирующую операцию IO? Можно было бы задавать таймаут, но это не очень хорошее решение. Для этого есть более удобные средства: функции pselect и ppoll. Разница между ними исключительно в юзабельности, поведение у них одинаковое. В первую очередь эти функции нужны для мультиплексирования работы с IO (select/poll). Префикс ‘p’ в начале функции указывает на то, что данная функция может быть корректно прервана сигналом.
Итак, сформулируем требование:
Необходимо разработать приложение, открывающее сокет (для простоты UDP) и выполняющее в потоке операцию чтения. Данное приложение должно корректно без задержек завершаться по требованию пользователя.
Функция треда выглядит вот так
- проверяем, что пока стартовал тред его еще не пожелали завершить
- блокируем завершающий сигнал
- проверяем, что пока блокировали, нас не пожелали завершить
- вызываем ppoll передавая в качестве последнего параметра маску сигналов по которой ждется сигнал
- после выхода из ppoll проверяем что вышли не из за сигнала о завершении
Устанавливаем наш обработчик для SIGINT, и когда нужно завершить дочерний поток шлем ему этот сигнал.
Полный листинг см. пример 3.
На мой взгляд, недостатком данного способа является то, что в случае нескольких потоков мы можем завершить их только все сразу. Нет возможности устанавливать свой обработчик сигналов для каждого треда. Таким образом, нет возможности реализовать полноценное межпоточное взаимодействие через сигналы. Linux way это не предусматривает.
PS. Исходные коды разместил на сервисе PasteBin (ссылку не даю, а то еще за рекламу посчитают).
PPS. Прошу простить за обилие ошибок. Язык, слабая моя сторона. Спасибо, всем кто помог их исправить.
Данная статья не претендует на полное (и глубокое) описание работы с сигналами и нацелена в первую очередь на тех, кто до этого момента не сталкивались с понятием «сигнал». Для более глубоко понимания работы сигналов автор призывает обратиться в более компетентные источники и ознакомиться с конструктивной критикой в комментариях.
Как использовать сигналы Linux в сценариях Bash

Ядро Linux посылает процессам сигналы о событиях, на которые им нужно реагировать. Хорошо себя зарекомендовавшие скрипты элегантно и надежно обрабатывают сигналы и могут очистить себя, даже если вы нажмете Ctrl+C. Вот как.
Сигналы и процессы
Сигналы — это короткие, быстрые односторонние сообщения, отправляемые таким процессам, как сценарии, программы и демоны. Они сообщают процессу о том, что произошло. Возможно, пользователь нажал Ctrl+C, или приложение могло попытаться записать в память, к которой у него нет доступа.
Если автор процесса ожидал, что ему может быть отправлен определенный сигнал, он может написать в программе или сценарии подпрограмму для обработки этого сигнала. Такая процедура называется обработчиком сигнала. Он ловит или перехватывает сигнал и выполняет какое-то действие в ответ на него.
Как мы увидим, Linux использует множество сигналов, но с точки зрения сценариев существует лишь небольшое подмножество сигналов, которые могут вас заинтересовать. В частности, в нетривиальных сценариях сигналы, сообщающие сценарий для завершения работы должен быть перехвачен (где это возможно) и выполнено корректное завершение работы.
Например, сценариям, которые создают временные файлы или открывают порты брандмауэра, можно дать возможность удалить временные файлы или закрыть порты до их закрытия. Если сценарий просто умирает в момент получения сигнала, ваш компьютер может остаться в непредсказуемом состоянии.
Вот как вы можете обрабатывать сигналы в своих собственных скриптах.
Знакомьтесь с сигналами
Некоторые команды Linux имеют загадочные имена. Не так команда, которая перехватывает сигналы. Это называется ловушка . Мы также можем использовать trap с параметром -l (список), чтобы показать нам весь список сигналов, которые использует Linux.

Хотя наш пронумерованный список заканчивается на 64, на самом деле сигналов 62. Сигналы 32 и 33 отсутствуют. Они не реализованы в Linux. Они были заменены функциями компилятора gcc для обработки потоков в реальном времени. Все, от сигнала 34, SIGRTMIN , до сигнала 64, SIGRTMAX , являются сигналами реального времени.
Вы увидите разные списки в разных Unix-подобных операционных системах. Например, на OpenIndiana присутствуют сигналы 32 и 33, а также множество дополнительных сигналов, в результате чего общее количество сигналов достигает 73.

На сигналы можно ссылаться по имени, номеру или сокращенному имени. Их сокращенное имя — это просто их имя с удаленным начальным «SIG».
Сигналы поднимаются по разным причинам. Если вы можете их расшифровать, их назначение содержится в их названии. Воздействие сигнала относится к одной из нескольких категорий:
- Завершить: процесс прекращен.
- Игнорировать: сигнал не влияет на процесс. Это только информационный сигнал.
- Core: создается файл дампа ядра. Обычно это делается из-за нарушения процесса, например нарушения памяти.
- Остановить: процесс остановлен. То есть он приостанавливается, а не прекращается.
- Продолжить. Сообщает остановленному процессу продолжить выполнение.
Это сигналы, с которыми вы будете сталкиваться чаще всего.
- SIGHUP: сигнал 1. Соединение с удаленным хостом, например сервером SSH, неожиданно прервано или пользователь вышел из системы. Сценарий, получивший этот сигнал, может корректно завершить работу или попытаться повторно подключиться к удаленному хосту.
- SIGINT: сигнал 2. Пользователь нажал комбинацию клавиш Ctrl+C, чтобы принудительно закрыть процесс, или с сигналом 2 была использована команда kill . Технически , это сигнал прерывания, а не сигнал завершения, но прерванный скрипт без обработчика сигнала обычно завершается.
- SIGQUIT: сигнал 3. Пользователь нажал комбинацию клавиш Ctrl+D, чтобы принудительно завершить процесс, или была использована команда kill с сигналом 3.
- SIGFPE: сигнал 8. Процесс пытался выполнить недопустимую (невозможную) математическую операцию, например деление на ноль.
- SIGKILL: сигнал 9. Это эквивалент сигнала гильотины. Вы не можете поймать или проигнорировать это, и это происходит мгновенно. Процесс немедленно завершается.
- SIGTERM: сигнал 15. Это более продуманная версия SIGKILL . SIGTERM также сообщает процессу о завершении, но это может быть перехвачено, и процесс может запустить свои процессы очистки перед закрытием. Это обеспечивает плавное завершение работы. Это сигнал по умолчанию, вызываемый командой kill .
Сигналы в командной строке
Один из способов перехватить сигнал — использовать trap с номером или именем сигнала и ответом, который вы хотите получить, если сигнал получен. Мы можем продемонстрировать это в окне терминала.
Эта команда перехватывает сигнал SIGINT . Ответом является печать строки текста в окне терминала. Мы используем параметр -e (включить escape-последовательности) с echo , поэтому мы можем использовать спецификатор формата « \n ».

Наша строка текста печатается каждый раз, когда мы нажимаем комбинацию Ctrl+C.
Чтобы узнать, установлена ли ловушка для сигнала, используйте параметр -p (вывести ловушку).

Использование trap без параметров делает то же самое.
Чтобы сбросить сигнал в нормальное состояние без захвата, используйте дефис « — » и имя захваченного сигнала.

Отсутствие вывода команды trap -p означает, что для этого сигнала не установлено прерывание.
Перехват сигналов в скриптах
Мы можем использовать ту же команду общего формата trap внутри скрипта. Этот скрипт перехватывает три разных сигнала: SIGINT , SIGQUIT и SIGTERM .
Три оператора trap находятся в верхней части скрипта. Обратите внимание, что мы включили команду exit в ответ на каждый из сигналов. Это означает, что скрипт реагирует на сигнал и затем завершает работу.
Скопируйте текст в свой редактор и сохраните его в файле с именем «simple-loop.sh» и сделайте его исполняемым с помощью команды chmod . Вам нужно будет сделать это со всеми сценариями в этой статье, если вы хотите следовать на своем собственном компьютере. Просто используйте имя соответствующего скрипта в каждом случае.

Остальная часть скрипта очень проста. Нам нужно знать идентификатор процесса скрипта, поэтому у нас есть скрипт, который повторяет это для нас. Переменная $$ содержит идентификатор процесса скрипта.
Мы создаем переменную с именем counter и присваиваем ей нулевое значение.
Цикл while будет работать вечно, если его не остановить принудительно. Он увеличивает переменную counter , выводит ее на экран и на секунду засыпает.
Запустим скрипт и отправим ему разные сигналы.

Когда мы нажимаем «Ctrl + C», наше сообщение печатается в окне терминала, и скрипт завершается.
Давайте запустим его снова и отправим сигнал SIGQUIT с помощью команды kill . Нам нужно сделать это из другого окна терминала. Вам нужно будет использовать идентификатор процесса, который сообщил ваш собственный скрипт.

Как и ожидалось, скрипт сообщает о поступлении сигнала, а затем прекращает работу. И, наконец, чтобы доказать это, мы проделаем это снова с сигналом SIGTERM .

Мы убедились, что можем перехватывать несколько сигналов в сценарии и реагировать на каждый из них независимо. Шаг, который превращает все это из интересного в полезное, — это добавление обработчиков сигналов.
Обработка сигналов в скриптах
Мы можем заменить строку ответа на имя функции в вашем скрипте. Затем команда trap вызывает эту функцию при обнаружении сигнала.
Скопируйте этот текст в редактор и сохраните его как файл с именем «grace.sh» и сделайте его исполняемым с помощью chmod .
Сценарий устанавливает ловушку для трех разных сигналов — SIGHUP , SIGINT и SIGTERM — с помощью одного оператора trap . . Ответ представляет собой имя функции graceful_shutdown() . Функция вызывается всякий раз, когда принимается один из трех захваченных сигналов.
Сценарий создает временный файл в каталоге «/tmp», используя mktemp . Шаблон имени файла — «tmp.XXXXXXXXXX», поэтому имя файла будет «tmp». за которыми следуют десять случайных буквенно-цифровых символов. Имя файла эхом отобразится на экране.
Остальная часть скрипта такая же, как и в предыдущем, с переменной counter и бесконечным циклом while .

Когда файлу отправляется сигнал, вызывающий его закрытие, вызывается функция graceful_shutdown() . Это удаляет наш единственный временный файл. В реальной ситуации он может выполнить любую очистку, которую требует ваш скрипт.
Кроме того, мы объединили все наши захваченные сигналы вместе и обработали их с помощью одной функции. Вы можете перехватывать сигналы по отдельности и отправлять их в собственные выделенные функции обработчика.
Скопируйте этот текст и сохраните его в файле с именем «triple.sh» и сделайте его исполняемым с помощью команды chmod .
Мы определяем три ловушки в верхней части скрипта.
- Один перехватывает SIGINT и имеет обработчик с именем sigint_handler() .
- Второй перехватывает сигнал с именем SIGUSR1 и использует обработчик с именем sigusr1_handler() .
- Ловушка номер три перехватывает сигнал EXIT . Этот сигнал поднимается самим скриптом при его закрытии. Установка обработчика сигнала для EXIT означает, что вы можете установить функцию, которая всегда будет вызываться при завершении скрипта (если только он не будет уничтожен сигналом SIGKILL ). Наш обработчик называется exit_handler() .
SIGUSR1 и SIGUSR2 — это сигналы, которые вы можете отправлять в свои скрипты. То, как вы интерпретируете и реагируете на них, полностью зависит от вас.
Оставив пока в стороне обработчики сигналов, тело скрипта должно быть вам знакомо. Он выводит идентификатор процесса в окно терминала и создает некоторые переменные. Переменная sigusr1_count записывает, сколько раз обрабатывался SIGUSR1 , а sigint_count записывает, сколько раз обрабатывался SIGINT . Переменная loop_flag устанавливается равной нулю.
Цикл while не является бесконечным циклом. Цикл прекратится, если для переменной loop_flag установлено любое ненулевое значение. Каждое вращение цикла while использует kill для отправки сигнала SIGUSR1 этому скрипту, отправляя его идентификатору процесса скрипта. Скрипты могут посылать сигналы сами себе!
Функция sigusr1_handler() увеличивает переменную sigusr1_count и отправляет сообщение в окно терминала.
Каждый раз, когда принимается сигнал SIGINT , функция siguint_handler() увеличивает переменную sigint_count и выводит ее значение в окно терминала.
Если переменная sigint_count равна трем, переменная loop_flag устанавливается равной единице, и в окно терминала отправляется сообщение, информирующее пользователя о начале процесса завершения работы.
Поскольку loop_flag больше не равен нулю, цикл while завершается, и сценарий завершается. Но это действие автоматически вызывает сигнал EXIT и вызывается функция exit_handler() .

После трех нажатий Ctrl+C скрипт завершает работу и автоматически вызывает функцию exit_handler() .
Читайте сигналы
Перехватывая сигналы и обрабатывая их в простых функциях-обработчиках, вы можете сделать так, чтобы ваши скрипты Bash убирались за собой, даже если они были неожиданно завершены. Это дает вам более чистую файловую систему. Это также предотвращает нестабильность при следующем запуске сценария и — в зависимости от цели вашего сценария — может даже предотвратить дыры в безопасности.