Letysite.ru

IT Новости с интернет пространства
0 просмотров
Рейтинг статьи
1 звезда2 звезды3 звезды4 звезды5 звезд
Загрузка...

Linux сигналы потоки

Часть 1. Основы работы с сигналами

Серия контента:

Этот контент является частью # из серии # статей: Работа с сигналами в Linux

Этот контент является частью серии: Работа с сигналами в Linux

Следите за выходом новых статей этой серии.

В современных операционных системах существует понятие межпроцессного взаимодействия (Inter-Process Communication – IPC) – это набор способов обмена данными между процессами и/или потоками. Одним из таких способов обмена служат сигналы. Концепцию сигналов поддерживает большинство операционных систем, но, например, Windows, не имеет их полноценной поддержки для использования в качестве одного из способов IPC – в подобных операционных системах сигналы лишь реализованы в стандартной библиотеке языка C.

Концепция сигналов

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

Сигналы имеют определенный жизненный цикл. Вначале сигнал создается – высылается процессом или генерируется ядром. Затем сигнал ожидает доставки в процесс-приемник. Принято считать, что период ожидания сигнала равен промежутку времени между созданием сигнала и действием, которое этот сигнал высылает. В конце жизненного цикла сигнала происходит его перехват (прием) процессом и выполнение связанных с сигналом действий.

Простые сигналы и надежные сигналы

Существует деление сигналов на простые и надежные.

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

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

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

В процессе решения проблемы были разработаны надежные сигналы, которые стандартизованы в POSIX и используются по сей день. Далее в статье рассматриваются именно надежные сигналы.

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

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

На сегодняшний момент существует реализация сигналов, которая свободна от этих недостатков. Решение состоит в том, что в обработчике сигнала следует лишь установить флаг, обозначающий, что сигнал получен, а затем нужно обеспечить возвращение из системного вызова с кодом ошибки, обозначающим прерывание вызова сигналом. Далее программа должна проверить флаг, установленный обработчиком сигнала, и выполнить соответствующие действия, например, очистить терминал и завершиться.

Современная реализация сигналов заставляет медленные системные вызовы возвращать код ошибки EINTR, когда они прерываются приходящим сигналом. Быстрые системные вызовы должны завершаться перед тем, как сигнал будет доставлен. Медленный системный вызов – системный вызов, требующий неопределенного количества времени для своего завершения, например read(), wait(), write(). Все системные вызовы, зависящие от непредсказуемых ресурсов, таких как действия человека, сетевые данные и т.п., являются медленными. Естественно, все остальные системные вызовы – быстрые.

Обычно программа обрабатывает код ошибки EINTR и, если не произошло ничего фатального, перезапускает системный вызов. Большинство Unix-like операционных систем в настоящее время делает это по умолчанию – нужно лишь обработать сигнал в обработчике, а системный вызов будет перезапущен автоматически. В Linux по умолчанию системные вызовы не перезапускаются, но для каждого сигнала процесс может установить флаг, указывающий системе о необходимости перезапуска медленных системных вызовов, прерванных этим сигналом.

Посылка сигналов

Посылка сигналов от одного процесса к другому обычно осуществляется при помощи системного вызова kill(). Его первый параметр – PID процесса, которому посылается сигнал; второй параметр – номер сигнала. Если мы хотим послать сигнал SIGTERM процессу с PID 6666, то используем системный вызов kill() так:

Вместо положительного значения PID можно передать вызову равное по модулю, но отрицательное значение. Тогда сигнал будет послан всем процессам из группы с номером, равным модулю переданного PID’а. Если PID равен 0, то сигнал посылается всем процессам из группы, к которой относится и текущий процесс. Эти возможности используются в основном оболочками для управления заданиями.

Если в качестве PID передать в системный вызов kill() значение -1, то сигнал будет послан всем процессам за исключением init’а. Такая возможность применяется для завершения работы системы.

Более подробная информация по системному вызову kill() приведена в man 2 kill.

Послать сигнал самому себе процесс может при помощи системного вызова raise(), который принимает один параметр – номер сигнала. Пример:

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

Перехват сигналов

Все программы, подчиняющиеся стандарту POSIX, регистрируют свои обработчики сигналов при помощи системного вызова sigaction(). Этот системный вызов имеет три параметра: первый – int signum – номер перехватываемого сигнала. Второй – struct sigaction * act – указатель на структуру, описывающую правила установки обработчика. Третий параметр – struct sigaction * oact – принимает уже установленные правила обработчика сигнала. Либо второй, либо третий (но не оба сразу!) параметр можно установить в NULL при необходимости.

Структура struct sigaction имеет следующее описание:

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

где единственный параметр – номер сигнала, который попал в обработчик. sa_handler также может быть равным SIG_IGN – сигнал игнорируется процессом, и SIG_DFL – сигнал вызывает действие по умолчанию, например прерывание процесса.

sa_mask – набор сигналов, которые должны блокироваться при вызове заданного в этой же структуре обработчика сигнала. Как их устанавливать, рассматривается далее.

Параметр sa_flags позволяет процессу модифицировать поведение сигнала. Параметр может принимать всего четыре значения, которые, впрочем, можно объединять при помощи битовой операции «ИЛИ»:

  1. SA_NOCLDSTOP – отсылать сигнал SIGCHLD только в случае прерывания дочернего процесса. Приостановка дочернего процесса не вызывает посылки сигнала.
  2. SA_NODEFER – эмуляция простых (ненадежных) сигналов.
  3. SA_RESTHAND – после прихода сигнала его обработчик сбрасывается в SIG_DFL.
  4. SA_RESTART – перезапуск системного вызова после возврата из обработчика сигнала. Если флаг не установлен, то системный вызов возвращает ошибку EINTR.

Как обычно, при успешном выполнении sigaction() возвращается 0, а в случае ошибки – отрицательное значение.

Маска сигналов процесса

Добавление сигналов в структуру sigset_t sa_mask, ее очистка и т.п. осуществляются при помощи набора функций sigemptyset(), sigfillset(), sigaddset(), sigdelset(). Первые две функции принимают один параметр – указатель на структуру sigset_t. Эти функции очищают и заполняют всеми возможными сигналами структуру sigset_t соответственно.

Последние две функции, соответственно, добавляют и удаляют один определенный сигнал из структуры и имеют по два параметра. Их первый параметр – указатель на структуру sigset_t, а второй – номер сигнала.

Все рассмотренные выше функции возвращают 0 при успешном завершении и число, не равное нулю, – при ошибке.

Кроме того, существует еще одна функция, проверяющая, находится ли указанный сигнал в указанном наборе – sigismember(). Ее параметры совпадают с параметрами sigaddset(). Функция возвращает 1, если сигнал находится в наборе, 0 – если не находится, и отрицательное число – при возникшей ошибке.

Помимо всего прочего, мы можем задать список сигналов, доставка которых процессу будет заблокирована. Это выполняется при помощи функции sigprocmask(int how, const sigset_t * set, sigset_t * oldset).

Первый ее параметр описывает то, что должно выполняться:

  1. SIG_BLOCK – сигналы из набора set блокируются;
  2. SIG_UNBLOCK – сигналы из набора set разблокируются;
  3. SIG_SETMASK – сигналы из набора set блокируются, остальные разблокируются.

Второй параметр является указателем на тот самый набор, сигналы из которого блокируются/разблокируются. Если он равен NULL, то значение первого параметра игнорируется системным вызовом.

Третий параметр – указатель на уже используемую маску сигналов; его можно поставить в NULL, если эти данные не нужны.

Для получения списка ожидающих сигналов можно использовать функцию sigpending(), которая принимает единственный параметр – указатель на структуру sigset_t, куда будет записан набор ожидающих сигналов.

Принципы написания обработчиков сигналов

Одно из самых главных правил написания обработчиков сигналов – обработчик должен быть реентерабельным, т.е. он должен допускать свой повторный вызов, когда процесс уже находится в обработчике. Нужно заботиться о том, чтобы обработчик сигналов не использовал глобальные структуры данных или медленные системные вызовы. Если избежать этого, увы, невозможно, то стоит позаботиться о защите от повторного вызова обработчика во время работы со структурой данных или с системным вызовом. Добиться этого можно, заблокировав на время доставку сигнала, обработчик которого сейчас работает, при помощи системного вызова sigprocmask(). Например, мы имеем обработчик сигнала SIGCHLD, выполняем в нем блокировку так:

Читать еще:  Удаленный доступ к рабочему столу linux

Кроме всего перечисленного выше, считается, что обработчик должен быть максимально простым – в идеале он должен выставлять некий флаг и завершаться, а все остальное должна выполнять основная часть программы.

Заключение

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

В завершение цикла будет рассказано о том, как получить (и отправить) дополнительные данные о сигнале, если вам не хватает обычной информации о том, что сигнал откуда-то пришел в ваш процесс.

Сигналы, группы, сеансы

Сигналы в Unix указывают ядру, что надо прервать нормальное планирование процесса и завершить/остановить его, или, вместо продолжения выполнения процесса с места остановки, выполнить функцию — обработчик сигнала, и лишь затем продолжить выполнение основного кода.

Сигналы, предназначенные процессу, создаются (отправляются) в нескольких ситуациях: при аппаратных сбоях, при срабатывании особого таймера, при обработке спецсимволов (Ctrl C, Ctrl Z) драйвером управляющего терминала, с помощью системного вызова kill(). В зависимости от причины, отправляются сигналы разных типов. Тип сигнала обозначается целым числом (номером). В Linux сигналы нумеруются от 1 до 64. Сигнал может быть отправлен отдельной нити процесса, процессу в целом или группе процессов.

Для каждого номера сигнала в процессе (нити) предусмотрено некоторое действие (действие по умолчанию, игнорирование или вызов пользовательской функции). Доставка сигнала заключается в выполнении этого действия.

Между отправкой сигнала и его доставкой проходит некоторое непредсказуемое время, поскольку, обычно, сигналы обрабатываются (доставляются) при выходе из системных вызовов или в тот момент, когда планировщик назначает процесс на выполнение. Исключением является доставка сигнала SIGKILL остановленным процессам. Для большинства сигналов можно явно приостановить доставку с помощью установки маски блокирования доставки sigprocmask() , и, в случае необходимости, удалить сигнал без обработки с помощью sigwait() .

Сигналы SIGKILL и SIGSTOP не могут быть заблокированы или проигнорированы и на них нельзя установить свой обработчик.

Действия по умолчанию:

  • SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOU — остановка процесса
  • SIGCONT — запуск остановленного процесса
  • SIGCHLD — игнорируется
  • SIGQUIT, SIGILL, SIGTRAP, SIGABRT, SIGFPE, SIGSEGV — сохранение дампа памяти и завершение (Linux)
  • остальные — завершение процесса

Сигнал, маска, обработчик

В упрощенном виде структуру в ядре, обеспечивающую доставку сигналов процессу, можно представить как таблицу:

  • Есть сигнал? — битовое поле, указывающее наличие недоставленного сигнала
  • Маска — битовое поле, указывающее временный запрет на доставку сигнала
  • Обработчик — указатель на действие, выполняемое при доставке сигнала. Может принимать значения: SIG_DFL — действие по умолчанию, SIG_IGN — игнорирование сигнала или указатель на функцию — обработчика сигнала.

Традиционно, при отправке процессу нескольких однотипных обычных сигналов, обработчик будет вызван лишь раз. Начиная с POSIX 1003.1, кроме обычных сигналов, поддерживаются сигналы реального времени, для которых создаётся очередь недоставленных сигналов, которая кроме номера сигнала, содержит значение (целое или адрес), которое уникально для каждого экземпляра сигнала.

Примеры использования сигналов

SIGKILL, SIGTERM, SIGINT, SIGHUP — завершение процесса. SIGKILL — не может быть проигнорирован, остальные могут. SIGTERM — оповещение служб о завершении работы ОС, SIGINT — завершение программы по нажатию Ctrl C, SIGHUP — оповещение программ, запущенных через модемное соединение, об обрыве связи (в настоящее время практически не используется).

SIGILL, SIGFPE, SIGBUS, SIGSEGV — аппаратный сбой. SIGILL — недопустимая инструкция CPU, SIGFPE — ошибка вычислений с плавающей точкой (деление на ноль), SIGBUS — физический сбой памяти, SIGSEGV — попытка доступа к несуществующим (защищенным) адресам памяти.

SIGSTOP, SIGCONT — приостановка и продолжение выполнения процесса

SIGPIPE — попытка записи в канал или сокет, у которого нет читателя

SIGCHLD — оповещение о завершении дочернего процесса.

Список сигналов

Особый сигнал с номером 0 фактически не доставляется, а используется в вызове kill() для проверки возможности доставки сигнала определённому процессу.

В Linux используется 64 сигнала. Список можно посмотреть в терминале командой kill -l

Posix.1-1990

Источник — man 7 signal

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

int kill(pid_t pid, int signum);

Для отправка сигнала необходимо, чтобы uid или euid текущего процесса был равен 0 или совпадал с uid процесса получателя.

Получатель сигнала зависит от величины и знака параметра pid :

  • pid > 0 — сигнал отправляется конкретному процессу
  • pid == 0 — сигнал отправляется всем членам группы
  • pid == -1 — сигнал отправляется всем процессам кроме init (в Linux’е еще кроме себя)
  • pid int sigqueue(pid_t pid, int sig, const union sigval value);

Аналогично kill() , но выполняется по правилам сигналов реального времени. Сигнал и связанная с ним дополнительная информация (целое или адрес) value помещаются в очередь сигналов (FIFO). Таким образом процессу можно отправить несколько однотипных сигналов с разными значениями value .

Отправка сигнала текущему процессу. Эквивалент kill(getpid(),signum);

Убирает из маски сигналов сигнала SIGABRT и отправляет его текущему процессу . Если сигнал перехватывается или игнорируется, то после возвращения из обработчика abort() завершает программу. Выхода из функции abort() не предусмотрено. Единственный способ продолжить выполнение — не выходить из обработчика сигнала.

Отправка сигнала SIGALRM себе через time секунд. Возвращает 0 если ранее alarm не был установлен или число секунд остававшихся до срабатывания предыдущего alarma. Таймер управляющий alarm’ами один. Соответственно установка нового alarm’а отменяет старый. Параметр time==0 позволяет получить оставшееся до alarm’а время без установки нового.

Исторический способ обработки сигнала — signal

Установка реакции на сигнал через функцию signal() не до конца стандартизована и сохраняется для совместимости с историческими версиями Unix. Не рекомендуется к использованию. В стандарте POSIX signal() заменен на вызов sigaction(), сохраняющий для совместимости эмуляцию поведения signal().

sighandler — адрес функции обработчика void sighandler(int) или один из двух макросов: SIG_DFL (обработчик по умолчанию) или SIG_IGN (игнорирование сигнала).

signal(. ) возвращает предыдущее значение обработчика или SIG_ERR в случае ошибки.

В Linux и SysV при вызове sighandler обработчик сбрасывается в SIG_DFL и возможна доставка нового сигнала во время работы sighandler . Такое поведение заставляет первой строкой в sighandler восстанавливать себя в качестве обработчика сигнала, и, даже в этом случае, не гарантирует от вызова обработчика по умолчанию. В BSD системах сброс не происходит, доставка новых сигнала блокируется до выхода из sighandler .

Реакция на сигнал — sigaction

Параметры для установки обработчика сигнала через sigaction()

Данные, передаваемые в обработчик сигнала sa_sigaction()

В Linux структура siginfo_t содержит больше полей, но они служат для совместимости и не заполняются.

Блокирование сигналов

Все функции блокирования сигналов работают с маской сигналов типа sigset_t. В Linux это 64 бита, т.е. два слова в 32х разрядной архитектуре или одно в 64х разрядной. Выставленный бит в маске сигналов означает, что доставка сигнала с соответствующим номером будет заблокирована. Сигналы SIGKILL и SIGSTOP заблокировать нельзя.

Манипулирование маской

Параметр how определяет операцию над текущей маской. Значения SIG_BLOCK, SIG_UNBLOCK, SIG_SETMASK.

Проверка наличия заблокированных сигналов

Очистка заблокированных сигналов

sigwait ожидает блокировки какого-либо из сигналов, указанных в маске, удаляет этот сигнал и возвращает его номер в параметр sig . Если реализация сигналов предусматривает очередь сигналов, то удаляется только один элемент из очереди.

Управляющий терминал, сеанс, группы

Управляющий терминал, сеанс, группы

Для организации диалоговой работы пользователей в Unix вводится понятие терминальной сессии. С точки зрения пользователя — это процесс работы с текстовым терминалом с момента ввода имени и пароля и до выхода из системы командой logout ( exit , нажатие ^D в пустой строке). Во время терминальной сессии может быть запущено несколько программ, которые будут параллельно выполнятся в фоновом режиме и между которыми можно переключаться в диалоговом режиме. После завершения терминальной сессии возможно принудительное завершение всех запущенных в ней фоновых процессов.

С точки зрения ядра — терминальная сессия — это группа процессов, имеющих один идентификатор сеанса sid. С идентификатором sid связан драйвер управляющего терминала, доступный всем членам сеанса как файл символьного устройства /dev/tty. Для каждого сеанса существует свой /dev/tty. Управляющий терминал взаимодействует с процессами сеанса с помощью отправки сигналов.

В рамках одного сеанса могут существовать несколько групп процессов. С каждым процессом связан идентификатор группы pgid. Одна из групп в сеансе может быть зарегистрирована в драйвере управляющего терминала как группа фоновых процессов. Процессы могут переходить из группы в группу самостоятельно или переводить из группы в группу другие процессы сеанса. Перейти в группу другого сеанса нельзя, но можно создать свой собственный сеанс из одного процесса со своей группой в этом сеансе. Вернуться в предыдущий сеанс уже не получится.

Группа процессов

Группа процессов — инструмент для доставки сигнала нескольким процессам, а также способ арбитража при доступе к терминалу. Идентификатор группы pgid равен pid создавшего её процесса — лидера группы. Процесс может переходить из группы в группу внутри одного сеанса.

Сеанс

Сеанс — средство для контроля путем посылки сигналов над несколькими группами процессов со стороны терминального драйвера. Как правило соответствует диалоговой пользовательской сессии. Идентификатор сеанса sid равняется идентификатору pid, создавшего его процесса — лидера сеанса. Одновременно с сеансом создаётся новая группа с pgid равным pid лидера сеанса. Поскольку переход группы из сеанса в сеанс невозможен, то создающий сеанс процесс не может быть лидером группы.

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

Фоновая группа сеанса

Процессы в фоновой группе выполняются до тех пор, пока не попытаются осуществить чтение или запись через файловый дескриптор управляющего терминала. В этот момент они получают сигнал SIGTTIN или SIGTTOU соответственно. Действие по умолчанию для данного сигнала — приостановка выполнения процесса.

Назначение фоновой группы:

Управляющий терминал

Некоторые сочетания клавиш позволяют посылать сигналы процессам сеанса:

  • ^C — SIGINT — завершение работы
  • ^Z — SIGTSTP — приостановка выполнения. bash отслеживает остановку дочерних процессов и вносит их в списки своих фоновых процессов. Остановленный процесс может быть продолжен командой fg n , где n — порядковый нрмер процесса в списке фоновых процессов

Простая обработка сигналов в Linux

У меня есть программа, которая создает много потоков и работает до тех пор, пока либо питание не будет отключено на встроенном компьютере, либо пользователь не использует kill или же Ctrl с прекратить процесс.

Вот некоторый код и как выглядит main ().

Мне интересно несколько вещей:

Нужна ли обработка сигналов?
Я читаю в этой теме «Linux C перехватывает сигнал уничтожения для корректного завершения» , что, видимо, ОС будет обрабатывать очистку для меня.
Следовательно, могу ли я просто заменить обработчик сигнала просто бесконечным циклом и позволить ОС корректно выходить из потоков, освобождать память и т. Д.?

Есть ли какие-либо другие сигналы, которые мне нужно беспокоить относительно чистого завершения? Эта тема «Как SIGINT относится к другим сигналам завершения?» , Было бы полезно перечислить все сигналы, которые могут меня интересовать, но сколько на самом деле требуется обработки?

Должна ли переменная завершения в моем примере быть изменчивой? Я видел много примеров, когда эта переменная изменчива, а другие — нет.

Я прочитал это signal() сейчас устарела, и использовать sigaction() , Есть ли действительно хорошие примеры, чтобы показать, как конвертировать из предыдущего signal() вызов? У меня возникли проблемы с новой структурой, которую я должен создать / передать, и как все это сочетается.

Является ли второй звонок signal() необходимо?
Есть ли что-то подобное, что мне нужно беспокоиться о sigaction() ?

Чтобы было ясно, все, что я пытаюсь сделать, чтобы мой: основной цикл работал, пока либо Ctrl с или питание отключено или происходит что-то действительно плохое.

Решение

[Q-3] Ли terminate переменная в моем примере должна быть volatile ? Я
видел много примеров, когда эта переменная изменчива, а другие где
это не.

Флаг terminate должно быть volatile sig_atomic_t :

Потому что функции-обработчики могут вызываться асинхронно. То есть обработчик может быть вызван в любой точке программы непредсказуемо. Если два сигнала поступают в течение очень короткого интервала, один обработчик может работать в другом. И считается лучшей практикой объявлять volatile sig_atomic_t к этому типу всегда обращаются атомарно, избегая неопределенности по поводу прерывания доступа к переменной. volatile говорит компилятору не оптимизировать и вносить его в реестр. (читать: Доступ к атомарным данным и обработка сигналов для уточнения детализации).
Еще одна ссылка: 24.4.7 Доступ к атомарным данным и обработка сигналов .
Кроме того, стандарт C11 в 7.14.1.1-5 указывает, что только объекты volatile sig_atomic_t можно получить доступ из обработчика сигнала (доступ к другим имеет неопределенное поведение).

[Q-4] Я прочитал это signal() сейчас устарела, и использовать sigaction() , Являются
Есть ли действительно хорошие примеры, чтобы показать, как конвертировать из
предыдущий signal() вызов? У меня проблемы с новой структурой, которая
Я должен создать / передать и как все это сочетается.

Пример ниже (и ссылка в комментариях) может быть полезным:

Рекомендации:

  1. Начало программирования на Linux, 4-е издание : в этой книге именно ваш код объясняется sigaction() красиво в «Глава 11: Процессы и сигналы».
  2. документация , включая пример (быстрое обучение).
  3. Библиотека GNU C: Обработка сигналов
    * Я начал с 1 , В настоящее время я читаю 3 GNU-библиотека

[Q-5] Является ли второй звонок signal() необходимо? Есть ли что-то подобное, что мне нужно беспокоиться о sigaction() ?

Почему вы установили default-action до завершения программы, мне неясно. Я думаю, что следующий параграф даст вам ответ:

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

Призыв к сигналу устанавливает обработку сигнала только для один появление сигнала. Перед вызовом функции обработки сигнала, библиотека сбрасывает сигнал так, чтобы действие по умолчанию было выполнено, если тот же сигнал повторяется. Сброс обработки сигнала помогает предотвратить бесконечный цикл, если, например, действие, выполненное в обработчике сигнала, снова вызывает тот же сигнал. Если вы хотите, чтобы ваш обработчик использовался для сигнала каждый раз, когда он происходит, вы должны вызвать сигнал в обработчике, чтобы восстановить его. Вы должны быть осторожны при восстановлении обработки сигналов. Например, если вы постоянно восстанавливаете SIGINT обработки, вы можете потерять способность прерывать и завершать вашу программу.

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

[Q-1a] Нужна ли обработка сигналов?

Да, Linux сделает очистку для вас. Например, если вы не закроете файл или сокет, Linux выполнит очистку после завершения процесса. Но Linux может не обязательно выполнять очистку немедленно, и это может занять некоторое время (может быть, для поддержания высокой производительности системы или некоторых других проблем). Например, если вы не закроете tcp-сокет и программа завершит работу, ядро ​​не закроет сокет немедленно, чтобы убедиться, что все данные были переданы, TCP гарантирует доставку, если это возможно.

[Q-1b] Следовательно, могу ли я просто заменить обработчик сигнала просто бесконечным циклом и позволить ОС корректно выходить из потоков, освобождать память и т. Д.?

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

[Q] Все, что я пытаюсь сделать, чтобы мой: основной цикл работал, пока либо Ctrl с или питание отключено или происходит что-то действительно плохое.

Нет, есть ограничение! Вы не можете поймать все сигналы. Некоторые сигналы не улавливаются, например, SIGKILL а также SIGSTOP и оба являются сигналами завершения. Цитируя одно:

— Макрос: int SIGKILL

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


Я не уверен, но, возможно, вы можете сделать что-то подобное в системах Windows: написав TSR (своего рода перехват режима ядра). Из моего тезиса я помню, что некоторые вирусы не могли быть уничтожены даже из диспетчера задач, но я также считаю, что они обманывают пользователя с помощью прав администратора.

Я надеюсь, что этот ответ поможет вам.

Другие решения

Для использования sigaction вместо этого вы можете использовать такую ​​функцию:

1. Нужна ли какая-либо обработка сигналов?

  • В конкретном случае ссылки, которую вы разместили, да. Запуски программного обеспечения, которые касаются сети, нуждаются в особых операциях, например, для предупреждения клиента о закрытии сокета, чтобы не мешать его работе.
  • В вашем конкретном случае вам не нужно обрабатывать какие-либо сигналы для того, чтобы процесс был элегантным и понятным: ваша ОС сделает это за вас.

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

Сначала взгляните на его страницу: Сигналы библиотеки GNU
Сигналы завершения — это то, что вы присматриваете. Но взгляните на SIGUSR1 и SIGUSR2, даже если вы никогда не найдете их ни в одном программном обеспечении, кроме как для целей отладки.

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

3. Должна ли переменная завершения в моем примере быть изменчивой?

4. Я прочитал это signal() сейчас устарела, и использовать sigaction()

Sigaction() это POSIX, а сигнал является стандартом C.

Signal() у меня отлично работает, но если вы хотите любой пример: Пример IBM

Для этого не обязательно использовать сигналы. Стандартный термин не необходимость быть пойманным У вас могут быть причины его поймать, но это будет зависеть от вашего приложения, а не от того, что требуется O / S.

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

Обработчики сигналов должны быть написаны для повторного входа. Это не требует, чтобы ваша переменная завершения была изменчивой, но это может зависеть от того, как вы ее используете!

Книга В. Ричарда Стивенса «Расширенное программирование в среде UNIX» имеет хороший пример того, почему и как обрабатывать сигналы.

И нет, вам не нужно возвращать обработчик по умолчанию до завершения работы вашего приложения, ваш обработчик действителен только для вашего приложения, поэтому, если вы просто убиваете приложение, оно не нужно.

Прежде всего — если вы не знаете, следует ли вам обрабатывать какие-либо сигналы, то вы, вероятно, не знаете. Сигналы требуют обработки в некоторых конкретных случаях, таких как закрытие сокетов, отправка некоторого сообщения другим подключенным процессам перед выходом или обработка сигнала SIGPIPE от write () (и, возможно, многих других).

Во вторых — это while (!terminate) sleep(2); не очень хорошо — в худшем случае это может привести к тому, что пользователь (или даже система) будет вынужден ждать 2 секунды и посылать в вашу программу SIGKILL, с которой вы не можете справиться.

ИМХО, лучшее решение здесь будет используя signalfd и выберите , так что вы можете завершить свою программу, не дожидаясь 2 с.

Linux сигналы потоки

В четвертом издании популярного руководства даны основы программирования в операционной системе Linux. Рассмотрены: использование библиотек C/C++ и стан­дартных средств разработки, организация системных вызовов, файловый ввод/вывод, взаимодействие процессов, программирование средствами командной оболочки, создание графических пользовательских интерфейсов с помощью инструментальных средств GTK+ или Qt, применение сокетов и др. Описана компиляция программ, их компоновка c библиотеками и работа с терминальным вводом/выводом. Даны приемы написания приложений в средах GNOME® и KDE®, хранения данных с использованием СУБД MySQL® и отладки программ. Книга хорошо структурирована, что делает обучение легким и быстрым.

Для начинающих Linux-программистов

Книга: Основы программирования в Linux

Сигналы

Разделы на этой странице:

Сигнал — это событие, генерируемое системами UNIX и Linux в ответ на некоторую ситуацию, получив сообщение о котором процесс, в свою очередь, может предпринять какое-то действие. Мы применяем термин «возбуждать» (raise) для обозначения генерации сигнала и термин «захватывать» (catch) для обозначения получения или приема сигнала. Сигналы возбуждаются некоторыми ошибочными ситуациями, например нарушениями сегментации памяти, ошибками процессора при выполнении операций с плавающей точкой или некорректными командами. Они генерируются командной оболочкой и обработчиками терминалов для вызова прерываний и могут явно пересылаться от одного процесса к другому как способ передачи информации или коррекции поведения. Во всех этих случаях программный интерфейс один и тот же. Сигналы могут возбуждаться, улавливаться и соответственно обрабатываться или (по крайней мере, некоторые) игнорироваться.

Имена сигналов задаются с помощью включенного заголовочного файла signal.h. Они начинаются с префикса SIG и включают приведенные в табл. 11.3 сигналы.

Таблица 11.3

*Могут быть также предприняты действия, зависящие от конкретной реализации.

Если процесс получает один из этих сигналов без предварительной подготовки к его перехвату, процесс будет немедленно завершен. Обычно при этом создается файл с дампом ядра. Этот файл в текущем каталоге, названный core, представляет собой образ процесса, который может оказаться полезным при отладке.

К дополнительным относятся сигналы, приведенные в табл. 11.4.

Таблица 11.4

Сигнал SIGCHLD может быть полезен для управления дочерними процессами. По умолчанию он игнорируется. Остальные сигналы заставляют процессы, получившие их, остановиться, за исключением сигнала SIGCONT , который вызывает возобновление процесса. Они применяются программами командной оболочки для контроля работы и редко используются в пользовательских программах.

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

Если вы хотите отправить сигнал не текущей приоритетной задаче, а другому процессу, используйте команду kill . Она принимает для отправки процессу в качестве необязательного параметра имя сигнала или его номер и PID (который, как правило, можно определить с помощью команды ps). Например, для отправки сигнала «останов или разъединение» командной оболочке, выполняющейся на другом терминале с PID 512, вы должны применить следующую команду:

$ kill -HUP 512

Удобный вариант команды kill — команда killall , которая позволяет отправить сигнал всем процессам, выполняющим конкретную команду. Не все системы UNIX поддерживают ее, но ОС Linux, как правило, поддерживает. Этот вариант полезен, когда вы не знаете PID процесса или хотите отправить сигнал нескольким разным процессам, выполняющим одну и ту же команду. Обычное применение — заставить программу inetd перечитать параметры настройки. Для этого можно воспользоваться следующей командой:

$ killall -HUP inetd

Программы могут обрабатывать сигналы с помощью библиотечной функции signal .

#include
void (*signal(int sig, void (*func)(int)))(int);

Это довольно сложное объявление говорит о том, что signal — это функция, принимающая два параметра, sig и func . Сигнал, который нужно перехватить или игнорировать, задается аргументом sig . Функция, которую следует вызвать при получении заданного сигнала, содержится в аргументе func . Эта функция должна принимать единственный аргумент типа int (принятый сигнал) и иметь тип void . Функция сигнала возвращает функцию того же типа, которая является предыдущим значением функции, заданной для обработки сигнала, или одно из двух специальных значений:

? SIG_IGN — игнорировать сигнал;

? SIG_DFL — восстановить поведение по умолчанию.

Пример сделает все понятным. В упражнении 11.7 вы напишете программу ctrlc.c, которая реагирует на нажатие комбинации клавиш + вместо обычного завершения выводом соответствующего сообщения. Повторное нажатие + завершает программу.

Упражнение 11.7. Обработка сигнала

Функция ouch реагирует на сигнал, передаваемый в параметре sig . Эта функция будет вызываться, когда возникнет сигнал. Она выводит сообщение и затем восстанавливает обработку сигнала по умолчанию для сигнала SIGINT (генерируется при нажатии комбинации клавиш + ).

#include
#include
#include
void ouch(int sig) <
printf(«OUCH! — I got signal %dn», sig);
(void)signal(SIGINT, SIG_DFL);
>

Функция main должна взаимодействовать с сигналом SIGINT , генерируемым при нажатии комбинации клавиш + . В остальное время она находится в бесконечном цикле, выводя один раз в секунду сообщение.

int main() <
(void)signal(SIGINT, ouch);
while(1) <
printf(«Hello World!n»);
sleep(1);
>
>

Сигналы в linux. Примеры

Сигналы — общие сведения

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

В операционной системе предусмотрено большое число типов сигналов, но большинство из этих типов зарезервировано для системных целей — это сигналы, которые операционная система посылает процессу. Однако есть и сигналы, которыми процессы могут обмениваться между собой.

По умолчанию реакция на большинство сигналов — прекращение процесса, получившего сигнал, то есть, если процесс получает сигнал, обработка которого в нем не предусмотрена, то процесс-получатель сигнала завершается. Однако для большинства типов сигналов процесс может установить обработчик данного сигнала или установить игнорирование данного сигнала.

Если процесс находится в состоянии «добровольного» приостанова (вызванного, например, выполнением системного вызова sleep ), то получение сигнала «пробуждает процесс от сна», независимо от того, в чем состояла обработка сигнала, системный вызов sleep заканчивается немедленно.

Обработчик сигнала в процессе имеет вид функции с прототипом:

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

Для установки своего обработчика сигнала, для его отмены или для установки игнорирования сигнала используется системный вызов signal

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

Ссылка на основную публикацию
Adblock
detector