Ответ Чарльза верен, но его предостережение («Обязательно вызывайте только входящие функции из обработчика») является крайним ограничением. Можно перенаправить обработку сигнала в более безопасную среду, используя kqueue
и CFFileDescriptor
.
Техническое примечание TN2050: наблюдение времени жизни процесса без опроса относится к другой теме, но иллюстрирует метод. Там Apple описывает предостережение Чарльза так:
Прослушивание сигнала может быть сложным из-за дурацкого исполнения
среда, связанная с обработчиками сигналов. В частности, если вы
установить обработчик сигнала (используя signal или sigaction ), вы должны быть очень
внимательно относитесь к тому, что вы делаете в этом обработчике. Очень немногие функции безопасны
позвонить из обработчика сигнала. Например, это не безопасно распределить
память, используя malloc
!
Функции, которые защищены от обработчика сигнала (асинхронный сигнал )
безопасные функции) перечислены на справочной странице sigaction .
В большинстве случаев вы должны предпринять дополнительные шаги для перенаправления входящих сигналов
в более разумную среду.
Я взял оттуда иллюстрацию кода и изменил ее для обработки SIGINT
. Извините, это Objective-C. Вот одноразовый установочный код:
// Ignore SIGINT so it doesn't terminate the process.
signal(SIGINT, SIG_IGN);
// Create the kqueue and set it up to watch for SIGINT. Use the
// EV_RECEIPT flag to ensure that we get what we expect.
int kq = kqueue();
struct kevent changes;
EV_SET(&changes, SIGINT, EVFILT_SIGNAL, EV_ADD | EV_RECEIPT, NOTE_EXIT, 0, NULL);
(void) kevent(kq, &changes, 1, &changes, 1, NULL);
// Wrap the kqueue in a CFFileDescriptor. Then create a run-loop source
// from the CFFileDescriptor and add that to the runloop.
CFFileDescriptorContext context = { 0, self, NULL, NULL, NULL };
CFFileDescriptorRef kqRef = CFFileDescriptorCreate(NULL, kq, true, sigint_handler, &context);
CFRunLoopSourceRef rls = CFFileDescriptorCreateRunLoopSource(NULL, kqRef, 0);
CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode);
CFRelease(rls);
CFFileDescriptorEnableCallBacks(kqRef, kCFFileDescriptorReadCallBack);
CFRelease(kqRef);
А вот как вы бы реализовали обратный вызов sigint_handler
, указанный выше:
static void sigint_handler(CFFileDescriptorRef f, CFOptionFlags callBackTypes, void *info)
{
struct kevent event;
(void) kevent(CFFileDescriptorGetNativeDescriptor(f), NULL, 0, &event, 1, NULL);
CFFileDescriptorEnableCallBacks(f, kCFFileDescriptorReadCallBack);
// You've been notified!
}
Обратите внимание, что этот метод требует, чтобы вы запускали установочный код в потоке, который будет существовать столько, сколько вы хотите обработать SIGINT
(возможно, время жизни приложения), и обслуживать / запускать его цикл выполнения. Потоки, созданные системой для ее собственных целей, например, те, которые обслуживают очереди Grand Central Dispatch, не подходят для этой цели.
Основной поток приложения будет работать, и вы можете использовать его. Однако , если основной поток блокируется или перестает отвечать на запросы, то он не обслуживает цикл выполнения и обработчик SIGINT
не будет вызываться. Поскольку SIGINT
часто используется для прерывания именно такого зависания процесса, основной поток может не подойти.
Итак, вы можете создать свою собственную нить, чтобы отслеживать этот сигнал. Он не должен делать ничего другого, потому что что-то еще может привести к его застреванию. Но даже там есть проблемы. Ваша функция-обработчик будет вызываться в этом фоновом потоке, а основной поток все еще может быть заблокирован. В системных библиотеках есть много вещей, предназначенных только для основных потоков, и вы не сможете ничего этого сделать. Но вы будете иметь гораздо большую гибкость, чем в обработчике сигналов в стиле POSIX.
Я должен добавить, что источники диспетчеризации GCD также могут отслеживать сигналы UNIX и с ними легче работать, особенно из Swift. Тем не менее, у них не будет предварительно созданного выделенного потока для запуска обработчика. Обработчик будет отправлен в очередь. Теперь вы можете назначить очередь с высоким приоритетом / высоким QOS, но я не совсем уверен, что обработчик запустится, если в процессе уже запущено множество потоков. То есть задача, которая на самом деле выполняется в очереди с высоким приоритетом, будет иметь приоритет над потоками или очередями с более низким приоритетом, но запуск новой задачи может и не быть. Я не уверен.