Перейти к содержимому

Какие сигналы нельзя перехватить в linux

  • автор:

Процессы и сигналы — Администрирование Linux

Единица работы в Linux – процесс. Каждый раз, когда мы что-то запускаем, то стартует процесс и, возможно, не один. Нормально, когда одна программа состоит из множества процессов и порождает их во время работы. Зачем это сделано? Что вообще такое процесс? Операционная система сама по себе является программой, и она управляет тем, что мы запускаем внутри неё. Процесс – это представление запущенной программы внутри операционной системы. Благодаря вытесняющей многозадачности , современные операционные системы способны запускать и исполнять сотни и больше процессов вместе. Мы привыкли слушать музыку, серфить в браузере и рядом чатиться практически одновременно, и у нас не вызывает вопросов, как это вообще возможно.

Хороший пример – браузер. У современных браузеров одна вкладка – один процесс. Такой подход позволяет переложить обеспечение одновременной работы разных вкладок на саму операционную систему. Кроме того, процессы в операционной системе изолированы друг от друга. Сбой в одном процессе, как правило, не влияет на другие процессы. Поэтому мы видим, как одна зависшая вкладка не мешает работать с другими. Раньше было не так, и зависшая вкладка приводила к полной блокировке браузера.

Процессы внутри себя могут делиться на потоки для обеспечения более высокой производительности или для параллельного запуска. Но это значительно усложняет сам код

Каждому процессу внутри операционной системы соответствует структура данных, внутри которой находится вся информация по процессу. Главный параметр – PID (Process IDentifier), то есть идентификатор процесса. Кроме этого там хранится информация о том, какой файл был запущен, от какого пользователя, из какой рабочей директории и так далее. Много данных, которые целиком описывают окружение запуска.

# Выводит информацию о процессах. Вывод ниже обрезан для удобства ps aux # Второй параметр это PID root 606827 0:00 nginx: master process nginx -g daemon off; systemd+ 606952 0:01 nginx: worker process root 607582 0:21 /usr/bin/node /root/.config/coc/extensions/node_modules/coc- root 612998 0:00 /usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port root 613006 0:11 containerd-shim -namespace moby -workdir /var/lib/containerd root 613024 0:00 make webpacker root 613091 0:00 /bin/sh -c WEBPACKER_DEV_SERVER_PUBLIC=hexletdev4.com bin/we root 613092 1:46 webpack 

С процессами связано понятие сигналов, позволяющее управлять процессами или взаимодействовать с ними снаружи. Простой пример. Мы хотим завершить какую-то программу. Как это сделать? Существует сигнал SIGTERM, который говорит процессу, что его хотят завершить. Код процесса должен поймать этот сигнал и завершить своё выполнение. Отправка сигнала в Linux выглядит так:

# kill – несмотря на название не убивает, а посылает процессу сигналы # -15 – кодовое обозначение сигнала SIGTERM # 606827 – pid какого-то процесса kill -15 606827 

Сигнал SIGTERM не обрабатывается сам по себе. Нужно прямо написать код, который его ловит и выполняет остановку сервиса. Вот пример на JS:

// Где-нибудь во время инициализации process.on('SIGTERM', () =>  // например, останавливаем запущенный сервер server.close(() =>  process.exit(0); >); >); 

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов

Наши выпускники работают в компаниях:

Какие сигналы нельзя перехватить в linux

Next: Посылка сигналов с помощью Up: Сигналы Previous: Сигналы Contents

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

  • системные сигналы (ошибка аппаратуры, системная ошибка и т.д.);
  • сигналы от устройств;
  • сигналы, определенные пользователем.

Известно три варианта реакции на сигналы:

  • вызов собственной функции обработки;
  • игнорирование сигнала (не работает для SIGKILL );
  • использование предварительно установленной функции обработки по умолчанию.

void(*signal(int signr, void(*sighandler)(int)))(int); Такой прототип очень сложен для понимания. Следует упростить его, определив тип для функции обработки:

typedef void signalfunction(int); После этого прототип функции примет вид: signalfunction *signal(int signr,

signalfunction *sighandler); signr устанавливает номер сигнала, для которого устанавливается обработчик. В заголовочном файле определены следующие сигналы (табл. 1).

Таблица 1. Сигналы ОС Linux.

Номер Значение Реакция программы по умолчанию
SIGABRT Ненормальное завершение (abort()) Завершение
SIGALRM Окончание кванта времени Завершение
SIGBUS Аппаратная ошибка Завершение
SIGCHLD Изменение состояния потомка Игнорирование
SIGCONT Продолжение прерванной программы Продолжение / игнорирование
SIGEMT Аппаратная ошибка Завершение
SIGFPE Ошибка вычислений с плавающей запятой Завершение
SIGILL Неразрешенная аппаратная команда Завершение
SIGINT Прерывание с терминала Завершение
SIGIO Асинхронный ввод/вывод Игнорирование
SIGKILL Завершение программы Завершение
SIGPIPE Запись в канал без чтения Завершение
SIGPWR Сбой питания Игнорирование
SIGQUIT Прерывание с клавиатуры Завершение
SIGSEGV Ошибка адресации Завершение
SIGSTOP Остановка процесса Остановка
SIGTTIN Попытка чтения из фонового процесса Остановка
SIGTTOU Попытка записи в фоновый процесс Остановка
SIGUSR1 Пользовательский сигнал Завершение
SIGUSR2 Пользовательский сигнал Завершение
SIGXCPU Превышение лимита времени CPU Завершение
SIGXFSZ Превышение пространства памяти (4GB) Завершение
SIGURG Срочное событие Игнорирование
SIGWINCH Изменение размера окна Игнорирование

Переменная sighandler определяет функцию обработки сигнала. В заголовочном файле определены две константы SIG_DFL и SIG_IGN . SIG_DFL означает выполнение действий по умолчанию — в большинстве случаев — окончание процесса. Например, определение signal(SIGINT, SIG_DFL); приведет к тому, что при нажатии на комбинацию клавиш CTRL+C во время выполнения сработает реакция по умолчанию на сигнал SIGINT и программа завершится. С другой стороны, можно определить
signal(SIGINT, SIG_IGN);

Если теперь нажать на комбинацию клавиш CTRL+C , ничего не произойдет, так как сигнал SIGINT игнорируется. Третьим способом является перехват сигнала SIGINT и передача управления на адрес собственной функции, которая должна выполнять действия, если была нажата комбинация клавиш CTRL+C , например
signal(SIGINT, function);

Пример использования обработчика сигнала приведен ниже: #include

void sigfunc(int sig)

printf(«\nХотите завершить программу (y/n) : «);

Какие сигналы нельзя перехватить в linux

Главное отличие сигналов от других средств взаимодействия между процессами заключается в том, что их обработка программой обычно происходит сразу же после поступления сигнала (или не происходит вообще), независимо от того, что программа делает в данный момент. Сигнал прерывает нормальный порядок выполнения инструкций в программе и передает управление специальной функции – обработчику сигнала. Если обработка сигнала не приводит к завершению процесса, то по выходе из функции-обработчика выполнение процесса возобновляется с той точки, в которой оно было прервано. У программ также есть возможность приостановить обработку поступающих сигналов временно, на период выполнения какой-либо важной операции. В традиционной терминологии приостановка получения определенных сигналов называется блокированием. Если для поступившего сигнала было установлено блокирование, сигнал будет передан программе, как только она разблокирует данный тип сигналов. Этим блокирование отличается от игнорирования сигнала, при котором сигналы соответствующего типа никогда не передаются программе. Следует помнить, что не все сигналы могут быть проигнорированы. Например, при получении программой сигнала принудительного завершения SIGKILL система ничего не сообщает программе, а просто прекращает ее работу. Таким образом, преимущество сигналов перед другими средствами взаимодействия с программой заключается в том, что посылать программе сигналы можно в любой момент ее работы, не дожидаясь наступления каких-то особых условий. Источником сигналов может быть как сам операционная система, так и другие программы пользователя. Если вам показалось, что сигналы похожи на прерывания, то вы совершенно правы. Для реализации сигналов действительно используются программные прерывания.

Нужно ли обрабатывать сигналы в вашей программе? Большинство программ не делают этого. В случае программирования для графических оболочек многие функции сигналов берут на себя механизмы сообщений графической оболочки. Тем не менее, есть целый ряд программ (например, демоны и консольные многопоточные приложения), в которых обработка сигналов необходима. Большинству сигналов системы присвоена конкретная роль и, хотя у программиста существует возможность использовать сигналы для передачи произвольной информации, не соответствующей их стандартному назначению, делать этого не рекомендуется. Собственно говоря, с помощью сигналов можно передать не так уж и много информации – только номер сигнала (хотя на платформе x86, например, можно было бы организовать и передачу дополнительных параметров). Скудость данных, передаваемых сигналами, не удивительна, если учесть, что по умолчанию большинство сигналов просто завершают работу программы. При этом в некоторых случаях на диске сохраняется образ памяти выгруженной программы (знаменитый файл core dump). Соответственно и программа-источник сигнала обычно не ждет никакого ответа от программы-приемника.

Номерам сигналов соответствуют константы, определенные в файле . Имена всех этих констант начинаются с префикса SIG, за которыми следует сокращенное название сигнала. Стандарт POSIX определяет две группы сигналов – «классические» сигналы Unix и сигналы реального времени. В отличие от классических сигналов сигналы реального времени всегда буферизуются, так что программа получит все посланные ей сигналы. В этой статье мы рассмотрим только классические сигналы Unix, каковых в Linux насчитывается 31. Этим сигналам назначены номера с 1 до 31 (номер 0, так называемый null-сигнал имеет особый смысл). Полный список сигналов можно получить из заголовочного файла signal.h. Мы рассмотрим несколько наиболее интересных сигналов.

Сигнал SIGHUP (номер 1) изначально был предназначен для того, чтобы информировать программу о потере связи с управляющим терминалом (терминалы часто подключались к системе с помощью модемов, так что название сигнала происходит от hung up – повесить трубку). Сигнал SIGHUP посылается приложению так же и в том случае, если процесс-лидер сессии завершил свою работу. Многие программы-демоны, у которых нет лидера сессии, так же обрабатывают этот сигнал. В ответ на получение SIGHUP демон обычно перезапускается (или просто повторно читает файл конфигурации). По умолчанию программа, получившая этот сигнал, завершается.

Сигнал SIGINT (номер 2) обычно посылается процессу, если пользователь терминала дал команду прервать процесс (обычно эта команда – сочетание клавиш Ctrl-C) .

Сигнал SIGABRT (номер 6) посылается программе в результате вызова функции abort(3). В результате программа завершается с сохранением на диске образа памяти.

Сигнал SIGKILL (номер 9) завершает работу программы. Программа не может ни обработать, ни игнорировать этот сигнал.

Сигнал SIGSEGV (номер 11) посылается процессу, который пытается обратиться к не принадлежащей ему области памяти. Если обработчик сигнала не установлен, программа завершается с сохранением на диске образа памяти.

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

Сигнал SIGCHLD (номер 17) посылается процессу в том случае, если его дочерний процесс завершился или был приостановлен. Родительский процесс также получит этот сигнал, если он установил режим отслеживания сигналов дочернего процесса и дочерний процесс получил какой-либо сигнал. По умолчанию сигнал SIGCHLD игнорируется.

Сигнал SIGCONT (номер 18) возобновляет выполнение процесса, остановленного сигналом SIGSTOP.

Сигнал SIGSTOP (номер 19) приостанавливает выполнение процесса. Как и SIGKILL, этот сигнал не возможно перехватить или игнорировать.

Сигнал SIGTSTP (номер 20) приостанавливает процесс по команде пользователя (обычно эта команда – сочетание клавиш Ctrl-Z).

Сигнал SIGIO/SIGPOLL (в Linux обе константы обозначают один сигнал – номер 29) сообщает процессу, что на одном из дескрипторов, открытых асинхронно, появились данные. По умолчанию этот сигнал, как ни странно, завершает работу программы.

В стандартной системе Unix определены два сигнала, SIGUSR1 (в Linux – номер 10) и SIGUSR2 (номер 12), предназначенные для передачи произвольной информации, но использование этих сигналов не приветствуется. Одной из причин негативного отношения программистов Unix к пользовательским сигналам является то, что сигналы, вообще говоря, представляют собой ограниченный ресурс, совместное использование которого может вызвать конфликты (например, если программист задействовал эти сигналы в своей программе и при этом использует стороннюю библиотеку, в которой эти сигналы также задействованы). Если вы не знали, то вам, возможно, будет интересно узнать, что обработка сигналов является частью стандарта языка Си и, как таковая, поддерживается даже на платформе Microsoft Windows. Однако, стандартный интерфейс сигналов Си, основанный на функции signal(), довольно неуклюж (недостатки интерфейса сигналов Си подробно описаны в книге [2]), так что мы воспользуемся более совершенным вариантом интерфейса, основанным на функции sigaction(2). Для демонстрации работы обработки сигналов мы напишем небольшую программу (файл sigdemo.c в исходниках).

#include #include #include void term_handler(int i) < printf ("Terminating\n"); exit(EXIT_SUCCESS); >int main(int argc, char ** argv)

Наша программа делает две вещи: обрабатывает сигнал SIGTERM (при получении этого сигнала программа выводит диагностическое сообщение и завершает свою работу) и блокирует сигнал SIGHUP, так что этот сигнал не может завершить ее работу. В тексте программы мы первым делом определяем функцию- обработчик сигнала SIGTERM term_handler(). Функции-обработчики сигналов – это обычные функции Си, они имеют доступ ко всем глобально видимым переменным и функциям. Однако, поскольку мы не знаем, в какой момент выполнения программы будет вызвана функция-обработчик, мы должны проявлять особую осторожность при обращении к глобальным структурам данных из этой функции.

Для функций, обрабатывающих потоки, существует и еще одно важное требование – реентерабильность. Поскольку обработчик сигнала может быть вызван в любой точке выполнения программы (а при не кототорых условиях во время обработки одного сигнала может быть вызван другой обработчик сигнала) в обработчиках додлжны использоваться функции, которые удовлетворяют требованию реентерабельности, то есть, могут быть вызваны в то время, когда они уже вызваны где-то в другой точке программы. Фактически, требование реентерабельности сводится к тому, чтобы функция не использовала никаких глобальных ресурсов, не позаботившись о синхронизации доступа к этим ресурсам. Некоторые функции ввода-вывода, в том числе, функция printf(), которую мы (и не только мы) используем в примерах обработчиков сигналов, реентерабельными не являются. Это значит, что выводу одной функции printf() может помешать вывод другой функции. В приложении приводится список реентерабельных функций, которые безопасно вызвать из обработчиков сигналов.

Единственным параметром нашего варианта функции-обработчика сигнала (в Unix-системах существует и другой вариант) является переменная типа int, в которой передается номер сигнала, вызвавшего обработчик. Нам этот номер не нужен, поскольку мы знаем, что только один сигнал, — SIGTERM, может вызвать нашу функцию, однако, в принципе, ничто не мешает нам использовать одну функцию для обработки нескольких разных сигналов, и тогда параметр функции- обработчика будет иметь для нас смысл. Функция-обработчик не возвращает никакого значения, что вполне логично, так как она вызывается не нашей программой, а неким системным компонентом. Особый интерес представляет завершение программы из обработчика сигнала. Назначение обработчика сигналу SIGTERM означает, что умалчиваемое действие сигнала, – завершение программы, не будет выполняться автоматически, и нам необходимо (если, конечно, мы хотим, чтобы этот сигнал завершал программу) позаботиться об этом явным образом. Если вы закомментируете вызов exit() в нашем примере, то увидите, что программа не будет завершать по получении сигнала SIGTERM. В принципе, вы можете придать сигналу SIGTERM совершенно иной смысл, например, оповещать программу о наступлении времени вашей любимой телепередачи (или о выходе нового номера журнала Linux Format), однако назначать стандартным сигналам нестандартные действия категорически не рекомендуется. Обработчик SIGTERM предназначен для того, чтобы, по требованию системы или пользователя, программа могла быстро и элегантно закончить текущую задачу и завершить свое выполнение. Именно этим обработчик и должен заниматься.

Перейдем теперь к тексту главной функции программы. Установка и удаление обработчиков сигналов осуществляются функцией sigaction(2). Первым параметром этой функции является номер сигнала, а в качестве второго и третьего параметров следует передать указатели на структуру sigaction. Эта структура содержит данные об операции, выполняемой над обработчиком сигнала. Второй параметр sigaction() служит для передачи новых значений для обработки сигнала, а третий – возвращает ранее установленные значения. В таблице 1 приводится краткое описание полей структуры sigaction.

Поле Значение
sa_handler Указатель на функцию обработчик сигнала или константа.
sa_mask Маска сигналов, блокируемых на время вызова обработчика.
sa_flags Дополнительные флаги.

Таблица 1. Поля структуры sigaction.

Поле sa_handler должно содержать либо адрес функции-обработчика, либо специальную константу, указывающую, что нужно делать с сигналом. Константа SIG_IGN указывает, что сигнал следует игнорировать, а константа SIG_DFL – что нужно восстановить обработку сигнала, заданную системой по умолчанию. Поле sa_mask позволяет заблокировать некоторое множество сигналов на время выполнения обработчика данного сигнала. Делается это для того, чтобы обработка других сигналов не могла прервать обработку данного (это может быть необходимо, особенно, если один обработчик обрабатывает несколько разных сигналов). Параметр sa_flags позволяет задать ряд флагов для выполнения более тонкой настройки обработчика сигналов. Например, флаг SA_RESETHAND указывает, что после завершения обработки сигнала заданным обработчиком должен быть восстановлен обработчик, заданный по умолчанию, так что все последующие сигналы будут обрабатываться умалчиваемым обработчиком.

В результате вызова функции sigaction() мы устанавливаем обработчик сигнала SIGTERM. Затем наша программа распечатывает значение PID (это значение понадобится нам для вызова команды kill) и входит в бесконечный цикл, из которого она может быть выведена одним из сигналов. Следует отметить, что функция sleep() возвращает управление (возобновляет выполнение программы раньше срока) если обработчик какого-либо сигнала возвращает управление в этот момент. Иначе говоря, любой обрабатываемый сигнал прерывает выполнение sleep(). Впрочем, в нашем примере с бесконечным циклом это не помогло бы программе завершиться. Сигнал SIGTERM приведет к тому, что программа выдаст диагностическое сообщение и завершит работу, а сигналы SIGINT и SIGABRT – к тому, что программа завершится без всякого сообщения. Скомпилируйте и запустите программу в окне терминала. В другом окне скомандуйте kill где PID – идентификатор процесса программы. Вы увидите, что перед тем как завершиться, программа выдает диагностическое сообщение, тогда как при завершении с помощью Ctrl-C никакого сообщения не выводится.

Рассмотрим теперь блокировку сигналов. Поскольку игнорирование сигнала устанавливается функцией sigaction(), можно было бы ожидать, что и блокировка устанавливается этой же функцией, но это не так. Так как зачастую программисту приходится блокировать несколько сигналов сразу, для блокировки существует специальная функция sigprocmask(2), которая оперирует наборами сигналов (signal sets). Разделение интерфейса между несколькими функциями вызвано еще и требованиями многопоточности. Параметры, устанавливаемые sigaction(), действительны для всей программы в целом, тогда как блокировку сигналов потоки осуществляют независимо друг от друга. Наборы сигналов хранятся в переменных специального типа — sigset_t, а операции над ними осуществляются с помощью специальных функций. Функция sigemptyset() инициализирует набор сигналов пустыми значениями, а функция sigfillset() устанавливает все возможные значения в наборе. Используемая нами функция sigaddset() добавляет значение сигнала в набор, а функция sigdelset() удаляет сигнал из набора. После того как набор сигналов сформирован, мы передаем его функции sigprocmask(), которая выполняет блокирование и разблокирование сигналов.

Первым параметром этой функции должна быть одна из констант, определяющих операцию над заданными сигналами. Константа SIG_BLOCK указывает, что сигналы из нового набора должны быть добавлены к списку уже заблокированных сигналов. Константа SIG_SETMASK указывает, что новый набор блокируемых сигналов должен заменить уже существующий (при этом заблокированные ранее сигналы будут разблокированы, если они не заблокированы в новом наборе), а константа SIG_UNBLOCK указывает на необходимость разблокировать сигналы, переданные в наборе. В нашей программе мы блокируем сигнал SIGHUP и вы можете видеть, что программа не обрабатывает этот сигнал. Послать нашей программе сигнал SIGHUP вы можете с помощью консольной команды

kill –s 1
где PID – идентификатор процесса.

Сигналы прерывают нормальный порядок выполнения программы и могут завершить работу программы, не способной завершиться иным образом. Но иногда бывает так, что программе просто нечего делать до тех пор, пока она не получит какой-либо сигнал. Иначе говоря, программу нужно заставить ждать появления сигнала, по возможности не нагружая процессор. Такая ситуация может возникнуть, например, в многопоточном программировании, когда нужно синхронизировать завершение нескольких потоков. Ожидание сигнала можно реализовать с помощью цикла, проверяющего значение флажка, который может сбросить обработчик сигнала. В некоторых случаях (таких как рассмотренный выше пример) можно реализовать ожидание и с помощью бесконечного цикла. Очевидно, однако, что эти методы не эффективны и не элегантны. В POSIX- системах существует специальная функция sigwait(3), которая «усыпляет» процесс до тех пор, пока процессу не будет передан один из заданного набора сигналов.

Модифицируем нашу программу так, чтобы вместо бесконечного цикла она входила в цикл ожидания сигнала SIGHUP (файл swdemo.c на компакт-диске):

sigprocmask(SIG_BLOCK, &newset, 0); while(!sigwait(&newset, &sig)) printf("SIGHUP recieved\n");

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

Функцию sigwait() можно использовать и для исследования сигналов. На компакт-диске вы найдете программку siglog.c, которая распечатывает информацию о каждом поступившем сигнале (естественно, исследуются только те сигналы, которые могут быть заблокированы). Рассмотрим здесь фрагмент этой программы:

sigset_t sset; int sig; . sigfillset(&sset); sigdelset(&sset, SIGTERM); sigprocmask(SIG_SETMASK, &sset, 0); while(!sigwait(&sset, &sig)) printf("Signal %i - %s\n", sig, sys_siglist[sig]);

С помощью вызовов sigfillset()и sigdelset()мы создаем набор из всех сигналов, за исключением сигнала SIGTERM (этот сигнал понадобится нам для того, чтобы мы могли завершить работу программы). Далее мы блокируем сигналы набора sset и вызываем для них функцию sigwait(). Функция вернет управление при получении любого сигнала, кроме SIGTERM (для которого назначен отдельный обработчик). Получив новый сигнал, мы распечатываем информацию о нем. Массив char * sys_siglist[] определен в стандартной библиотеке glibc. Этот массив содержит наименования сигналов на «человеческом» языке (эти наименования можно использовать при выводе диагностических и отладочных сообщений). Наименования расположены так, чтобы их индексы в массиве соответствовали номерам сигналов. Те же данные возвращает и функция strsignal(), единственным параметром которой является номер сигнала.

На протяжении всей этой статьи мы занимались обработкой сигналов, но не их генерацией. Поскольку основным источником сигналов является операционная система, нам и в «реальной жизни» чаще приходится заниматься именно обработкой. Однако, в заключение статьи следует рассмотреть и функции генерации сигналов. Для генерации сигналов в Unix предусмотрены две функции – kill(2) и raise(3). Первая функция предназначена для передачи сигналов любым процессам, к которым владелец данного процесса имеет доступ, а с помощью второй функции процесс может передать сигнал самому себе. Как это обычно принято в мире Unix, семантика вызова функции kill()совпадает с семантикой одноименной команды ОС. У функции kill()два аргумента –PID процесса-приемника и номер передаваемого сигнала. С помощью функции kill()как и с помощью одноименной команды можно передавать сообщения не только конкретному процессу, но и группе процессов.

Таблица 2 демонстрирует поведение функции kill()в зависимости от значения PID:

PID > 1 Сигнал посылается процессу с соответствующим PID.
PID == 0 Сигнал посылается всем процессам из той же группы что и процесс-источник.
PID < 0 Сигнал посылается всем процессам, чей идентификатор группы равен абсолютному значению PID.
PID == 1 Сигнал посылается всем процессам системы.
raise(sig);

эквивалентен вызову

kill(getpid(), sig);

Так же как и для других примитивов IPC, для сигналов действует система прав доступа, основанная на правах доступа владельцев процессов. Процесс-приемник получит сигнал только в том случае, если у процесса-источника есть соответствующие права. С помощью функции kill()можно проверить, существует ли в системе процесс с заданным PID, не посылая процессу никаких сигналов. Для этого предназначен псевдо-сигнал с номером 0. Если соответствующего процесса не существует, функция kill()вернет значение 1, соответствующее об ошибке. В любом случае, сигнал не будет отправлен. Читателей, полюбивших обработку сигналов, я могу обрадовать тем, что мы рассмотрели далеко не все функции, связанные с сигналами. При изучении документации вас ждет еще много полезного и приятного, мы же закончим на этом наше знакомство с сигналами.

Приложение. Список реентерабельных функций
accept() access() aio_error() aio_return()
aio_suspend() alarm() bind() cfgetispeed()
cfgetospeed() cfsetispeed() cfsetospeed() chdir()
chmod() chown() clock_gettime() close()
connect() creat() dup() dup2()
execle() execve() _Exit() _exit()
fchmod() fchown() fcntl() fdatasync()
fork() fpathconf() fstat() fsync()
ftruncate() getegid() geteuid() getgid()
getgroups() getpeername() getpgrp() getpid()
getppid() getsockname() getsockopt() getuid()
kill() link() listen() lseek()
lstat() mkdir() mkfifo() open()
pathconf() pause() pipe() poll()
posix_trace_event() pselect() raise() read()
readlink() recv() recvfrom() recvmsg()
rename() sendto() setgid() setpgid()
setsid() setsockopt() setuid() shutdown()
sigaction() sigaddset() sigdelset() sigemptyset()
sigfillset() sigismember() signal() sigpause()
sigpending() sigprocmask() sigqueue() sigset()
sigsuspend() sleep() socket() socketpair()
stat() symlink() sysconf() tcdrain()
tcflow() tcflush() tcgetattr() tcgetpgrp()
tcsendbreak() tcsetattr() tcsetpgrp() time()
timer_getoverrun() timer_gettime() timer_settime() times()
umask() uname() unlink() utime()
wait() waitpid() write()
Литература:
1.обратно D. P. Bovet, M. Cesati, Understanding the Linux Kernel, 3rd Edition, O’Reilly, 2005
2.обратно W. R. Stevens, S. A. Rago, Advanced Programming in the UNIX® Environment: Second Edition, Addison Wesley Professional, 2005

Какие сигналы перехватывать обязательно?

Теперь задача — перехватывать сигналы, т.к. надо завершится мягко, что-то сохранить, а не падать трупом.

// SIGKILL - Не перехватывается, не игнорится, не обрабатывается, сразу труп // SIGSTOP - Не перехватывается, не игнорится, не обрабатывается, просто стоп, freeze чтоли? // SIGTSTP - Это rerise(?) от VSUSP (Ctrl+Z) может быть блокирован, перехвачен, игнорирован // SIGCONT - Продолжить после стопа, но нет смысла вешать обработчик // SIGTERM - Может быть блокирован, перехвачен, игнорирован // SIGINT - Это Ctrl+C // SIGQUIT - Ctrl+\ какой-то дамп, я так понял отладочный коредамп // SIGHUP - Отвалился терминал? а как потом вернуться в терминал если он отвалился? // SIGTTOU - Попытка писать в терминал из фона // SIGPIPE - Брокнутые FIFO или отвалился сокет // SIGLOST - Потеряно соединение с сервером(?) с любым ли? // SIGXCPU - Превышен лимит процессорного времени // SIGXFSZ - Превышен лимит величины размера файла (если к файлу то м.б. ограничение для fat32, а как насчет дисковой квоты?) // SIGINFO - Кагдила 

Какие еще сигналы надо учесть?

deep-purple ★★★★★
28.12.14 06:49:16 MSK

Обычно перехватывают SIGTERM (это выход) и SIGHUP. SIGHUP обычно служит для рестарта и перечитывания конфига.

niemand
( 28.12.14 07:55:56 MSK )
Ответ на: комментарий от niemand 28.12.14 07:55:56 MSK

Глянь на первый пост, я дописал еще ))

Как видишь — этого мало, кроме сигтерма есть еще несколько, и если ты будешь ловить только сигтерм, то профукаешь остальные.

deep-purple ★★★★★
( 28.12.14 08:28:15 MSK ) автор топика

E ★★★
( 28.12.14 09:08:11 MSK )
Ответ на: комментарий от E 28.12.14 09:08:11 MSK

Не забыл, предполагается что на ввод ничего не будет слушаться.

deep-purple ★★★★★
( 28.12.14 09:30:05 MSK ) автор топика
Ответ на: комментарий от deep-purple 28.12.14 08:28:15 MSK

Можешь взять пример из гугля.

Обработчики там только на 3 сигналах: 2 мной названных и SIGINT. Кое-что там ещё заблокированно (это тебе решать)

Ты можешь обрабатовать и больше (хоть SIGILL или SIGSEGV), но обычно этих 3 хватает (всё зависит от того, что делает твоя программа)

niemand
( 28.12.14 09:54:27 MSK )

SIGINT, SIGTERM, SIGPIPE, желательно SIGHUP.

mashina ★★★★★
( 28.12.14 10:47:28 MSK )

Отвалился терминал? а как потом вернуться в терминал если он отвалился?

Демон обычно сам отключается от терминала.

AptGet ★★★
( 28.12.14 12:11:01 MSK )
Ответ на: комментарий от AptGet 28.12.14 12:11:01 MSK

Это если демон. А если ты зашел по ssh, запустил прилагу, и ssh отвалился на твоей стороне?

$ ./prog $ Ctrl+Z $ bg

Как отловить в прилаге что её в фон кинули? Через этсамый SIGTTOU?
deep-purple ★★★★★
( 28.12.14 12:45:45 MSK ) автор топика

SIGINT/SIGTERM обязательно, остальное по вкусу.

post-factum ★★★★★
( 30.12.14 01:36:29 MSK )

Лет 15 назад забил болт на весь этот зоопарк с сигналами, терминалами и другими std(in/out/err) и демонизациями. Пишу все сервера как обыкновенное консольное приложение. Перехватываю только SIGHUP (по нему переоткрываю логи для ротейтов/перечитываю конфиги) и SIGINT по нему корректно завершаюсь. Проблем никогда небыло (главное ввод вывод делать так чтоб всяких sigpipe не возникало) — зато процесс отладки / управления упростился значительно (в любой момент запустил в консоле, виден весь stdout и stderr Ctrl+C — завершился). При переносе в продакшен деманизирую приложение по средством шела прямо из стартап скрипта — в нем же стоит и вачдог на баше (который просто делает waitpid и смотрит по какой причине сервер завершился — если что респавнит и шлет почту).

Конечно бывают спецефические случаи когда нужно чтото обработать дополнительно, или вообще перейти в отдельную терминальную группу — но у меня такие задачи возникали только в институте 🙂

PS. возможно на некоторых *bsd/unix системах могут быть проблемы, я в основном работаю с linux. Пару раз запускал свой софт на FreeBSD, Solaris, Open Solaris, AIX — там в принципе все тоже работало нормально, только приходилось немного править стартап/ватчдог скрипты.

PS.PS Подбешивают демоны которых нельзя запустить в фореграунде (по умолчанию демонизируются и нету ключика аля —foregraund). Если вдруг сломался и не запускается/не работает при этом в логах тишина — то ни подебажить нормально, ни stdin/stderr и если нужно на вачдога посадить — то тоже приходится костыли делать.

zaz ★★★★
( 30.12.14 02:45:48 MSK )
Ответ на: комментарий от zaz 30.12.14 02:45:48 MSK

Ды, я вот хочу вообще шоп сам не умел в демона. Если надо будет, то а-ля 1>> /dev/null 2>> /dev/null &

Хз насколько это нормально..

Но тут вопрос что если он в бг попадет при обычном запуске, то будет продолжать гадить в кансоль свои stdout/stderr мессаги. Как в ём определять что его отправили в бг (не суспенд) (чтоб перестал гадить) и как потом понять что подняли в фг (чтоб продолжил)?

deep-purple ★★★★★
( 30.12.14 03:20:33 MSK ) автор топика
Ответ на: комментарий от deep-purple 30.12.14 03:20:33 MSK

Если честно никогда таким не заморачивался. Но могу предположить что это может быть зависимо от конкретного шела (поскольку для ОС нет такого понятия как фореграунд и бекграунд, это больше шел всем этим заведует). Также могу предположить что смотреть нужно в сторону терминальных груп (tcgetpgrp) — шел должен держать фореграунд и бекграунд процессы в различных терминальных группах (тоесть bg/fg должны переводить процесс с одной группы в другую). Насщет сигналов не уверен но я бы посмотрел в сторону SIGTTIN/SIGTTOU

zaz ★★★★
( 30.12.14 03:36:58 MSK )
Ответ на: комментарий от deep-purple 30.12.14 03:20:33 MSK

Как в ём определять что его отправили в бг

не нужно так делать. Если нужно отправить в bg, и чтоб не срал, то перенаправь вывод и отправь в bg. Это не задача самого приложения.

К тому же, удобно отправлять в bg, а сообщения перенаправить в лог-файл.

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

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *