В настоящее время у меня возникли проблемы с жалобами пользователей на завершение работы моего приложения. При некоторых (на первый взгляд произвольных) условиях и средах рабочего стола приложение не завершается, а настройки не сохраняются при перезагрузке. Я спрашивал в соответствующих каналах IRC, и в большинстве случаев мне говорят правильно обрабатывать сигналы. Я знаю о SIGINT для Ctrl-C в терминале и SIGTERM для «нормального» завершения. Но мне сказали, что SIGHUP тоже важен. Поэтому мой вопрос:
Какие сигналы мне необходимо обработать, чтобы создать хорошо работающее приложение?
решение1
https://en.wikipedia.org/wiki/Unix_signal
имеет список стандартных сигналов и их действий по умолчанию. Любой достаточно привилегированный процесс всегда может отправить вам любой сигнал, но процессы (или вы, через процессы) не должны этого делать.
Вы должны только kill
с этим и ожидать, что ваши процессы будут kill
совершеныСРОКи набор сигналов, которые может генерировать ваша среда (оболочка, драйвер терминала), если только вы не знаете, обрабатывает ли цель специальный сигнал, который вы хотите отправить.
Вы можете отсортировать основные сигналы по их действию.
Сигналы дампинга ядра
ABRT TRAP SYS BUS FPE ILL SEGV XFSZ XCPU QUIT
генерируются либо вами (TRAP, ABRT, SYS) с помощью некоторых специализированных функций, либо при переходе в состояния серьезных ошибок (BUS, FPE,ILL, SEGV).
QUIT
генерируется пользователем на терминале, желающим получить дамп ядра ( C+\
).
Возможно, вы захотите оставить их в их исходном состоянии.
Внезавершающие сигналы:
HUP INT PIPE TERM ALRM POLL PROF USR1 USR2
вы должны ожидать
HUP INT PIPE TERM
из вашего окружения в различных обстоятельствах:
HUP -- when the terminal hungs up or you become a stopped orphaned process
INT -- when the user at the terminal interrupts you with C-c
PIPE -- when a PIPE or socket you write into closes, e.g. by
exiting before you finished writing to it
(`yourprogram | (exit)` will give you a `PIPE`
if yourprogram attempts to write to its STDOUT)
TERM -- when a process ends you a normal termination request
Остальные сигналы завершения вы должны получать только в том случае, если настроили их самостоятельно.
УБИЙСТВОиОСТАНАВЛИВАТЬСЯс этим ничего не поделаешь.
Вы можете захотеть перехватить терминал, сгенерированныйостанавливающие сигналы:
TTIN -- you read from the terminal and you're a backgrounded process
TTOU -- you write the terminal when you're a backgrounded process
and the terminal is set to put you to sleep when you do
TSTP -- you're being put to sleep with `C-Z`
но в худшем случае они просто остановят ваш процесс, не нарушив вашего состояния.
Если вы не знаете, что вам нужно справиться с CHLD
, CONT
, или URG
, вы этого не сделаете.
TL;DR:
В принципе, я думаю, что HUP, INT, PIPE и TERM следует обрабатывать (или игнорировать), если вы обычно хотите сделать какую-то предвыходную очистку. Остальное можно оставить в покое, если только ваша программа не использует эти сигналы, или если вам не нужна очистка при любых обстоятельствах.
В последнем случае вы можете просто заблокировать все необработанные сигналы, но помните, что маски блокировки сигналов наследуются между fork и вызовами execve, а игнорирование или блокировка сигналов, таких как ILL, если они возникли в результате выполнения вашего процесса и не были отправлены вам kill или sigqueue, приведет к неопределенному поведению.
Если вы хотите узнать больше, изучите man-страницы и стандарты. Сигналы — довольно большая тема в Unix, и их обработка может оказаться очень сложной.
решение2
Если вы пойдете этим путем, вы, вероятно, в конечном итоге проигнорируете большое количество сигналов, просто потому, что один из них завершает вашу программу. Как предложил siblenx в комментарии, лучшим способом действий будет проанализировать эти сигналы и понять, откуда они исходят. В конце концов, может быть веская причина для того, чтобы ваша программа была убита.
Сказав это, вы можете взглянуть наsignals(7)
чтобы получить список сигналов, которые могут вызвать завершение процесса:
SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGABRT, SIGFPE, SIGKILL, SIGSEGV, SIGPIPE, SIGALRM, SIGTERM, SIGUSR1 и SIGUSR2.
Если ваше приложение проводит некоторое время подключенным к терминалу, обработка SIGINT
— хорошая идея ( в конце концов, Ctrl+ Cдовольно известен). SIGTERM
также хорошая идея, потому что он остается основным сигналом завершения и используется ядром (например, при завершении работы системы). SIGQUIT
вероятно, является «более жесткой» версией двух предыдущих, поскольку она вызывает дамп ядра. Когда вы получаете этот сигнал, ваша программа, вероятно, должна немедленно аварийно завершить работу. При получении любого из других сигналов ваша программа должна «убрать беспорядок и уйти» (сохранить настройки, завершить работу должным образом).
SIGILL
, SIGFPE
, SIGSEGV
и SIGPIPE
— сигналы, связанные с неправильным поведением программы.Ошибка сегментацииэто, вероятно, наиболее повторяющаяся ситуация, но в конечном итоге все эти случаи можно рассматривать как ошибки, и я не верю, что перехват сигнала — лучший способ исправить эти ситуации.
- В большинстве случаев компиляторы не позволят
SIGILL
этому случиться так просто (больше здесь), так что я думаю, вы можете отложить это в сторону. - Никогда не делите на ноль, и вы избежите большинства
SIGFPE
s. Кроме этого, просто убедитесь, что ваша программа хороша в математике и не сходит с ума от чисел. SIGSEGV
может быть вызвано несколькими причинами (незаконный доступ к памяти, незаконное разыменование), и в этом случае я бы рекомендовал использовать функциюgdb
для наблюдения за вашей программой во время ее выполнения и отслеживания неприятной инструкции с помощьюgdb
стека.- Наконец,
SIGPIPE
в основном относится к потокам данных (чтение файлов, соединения, ...). Если какой-либо из потоков ввода-вывода вашей программы, вероятно, будет завершен во время выполнения, вам следует убедиться, что ваша программа проверяет свои дескрипторы перед их использованием и обрабатывает все события ввода-вывода, происходящие в ней (при использованииselect
илиpoll
например).gdb
может быть полезным и здесь.
SIGABRT
, SIGALRM
, SIGUSR1
и SIGUSR2
по сути являются пользовательскими сигналами. Если вы их получаете, скорее всего, вы действительно это сделали.
Это оставляет нас с SIGHUP
и SIGKILL
, которые могут быть не пойманы.
SIGHUP
произойдет, например, если вы запустите процесс из терминала и закроете этот терминал после этого, не отсоединив процесс (disown
или используяnohup
). Если ваша программа запускается из терминала и работает как демон, не забудьте отсоединить ее от оболочки, например, с помощью двойного разветвления. Этот конкретный сигнал менее важен, если ваша программа может запускаться из графического интерфейса или во время загрузки, когда управляющий терминал с меньшей вероятностью будет «закрыт».SIGKILL
как вы знаете, является всемогущим из сигналов. Это способ ядра сказать "дерьмо!". Если вы получаете это, то вам следует сосредоточить свое внимание на том, что испускает сигнал, а не на том, как его обработать в вашей программе.
В конце концов, анализ сигнала и использование отладочных инструментов, вероятно, является самым простым подходом к решению этих проблем. Если сигнал срабатывает от ядра, когда ваша программа ведет себя неправильно, это gdb
должно помочь вам локализовать проблему. В этом случае забудьте о сигнале и сосредоточьтесь на ошибке, о которой он вам сообщает.
Если сигнал поступает «извне», то, вероятно, лучшим решением будет найти его источник.(если вы действительно не можете его найти,трассировка the kill(2)
системный вызов может быть хорошим последним средством). Затем вы можете решить, хотите ли вы игнорировать его ( SIG_IGN
), или принять его во внимание. Отключение программы-излучателя также может быть достойной альтернативой...