Сигналы в Unix указывают ядру, что надо прервать нормальное планирование процесса и завершить/остановить его, или, вместо продолжения выполнения процесса с места остановки, выполнить функцию - обработчик сигнала, и лишь затем продолжить выполнение основного кода.
Сигналы, предназначенные процессу, создаются (отправляются) в нескольких ситуациях: при аппаратных сбоях, при срабатывании особого таймера, при обработке спецсимволов (Ctrl C, Ctrl Z) драйвером управляющего терминала, с помощью системного вызова kill(). В зависимости от причины, отправляются сигналы разных типов. Тип сигнала обозначается целым числом (номером). В Linux сигналы нумеруются от 1 до 64. Сигнал может быть отправлен отдельной нити процесса, процессу в целом или группе процессов.
Для каждого номера сигнала в процессе (нити) предусмотрено некоторое действие (действие по умолчанию, игнорирование или вызов пользовательской функции). Доставка сигнала заключается в выполнении этого действия.
Между отправкой сигнала и его доставкой проходит некоторое непредсказуемое время, поскольку, обычно, сигналы обрабатываются (доставляются) при выходе из системных вызовов или в тот момент, когда планировщик назначает процесс на выполнение. Исключением является доставка сигнала SIGKILL остановленным процессам. Для большинства сигналов можно явно приостановить доставку с помощью установки маски блокирования доставки sigprocmask()
, и, в случае необходимости, удалить сигнал без обработки с помощью sigwait()
.
Сигналы SIGKILL и SIGSTOP не могут быть заблокированы или проигнорированы и на них нельзя установить свой обработчик.
Действия по умолчанию:
Ссылка: ПРАВИЛА ИГРЫ В СИГНАЛЫ UNIX
В упрощенном виде структуру в ядре, обеспечивающую доставку сигналов процессу, можно представить как таблицу:
Номер | Есть Сигнал? | Маска | Обработчик |
---|---|---|---|
1 | 1 | 1 | SIG_DFL |
2 | 0 | 0 | SIG_IGN |
3 | 0 | 0 | sig_func() |
... | ... | ... | ... |
Традиционно, при отправке процессу нескольких однотипных обычных сигналов, обработчик будет вызван лишь раз. Начиная с 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
Источник - man 7 signal
Signal Value Action Comment
-------------------------------------------------------------------------
SIGHUP 1 Term Hangup detected on controlling terminal
or death of controlling process
SIGINT 2 Term Interrupt from keyboard
SIGQUIT 3 Core Quit from keyboard
SIGILL 4 Core Illegal Instruction
SIGABRT 6 Core Abort signal from abort(3)
SIGFPE 8 Core Floating point exception
SIGKILL 9 Term Kill signal
SIGSEGV 11 Core Invalid memory reference
SIGPIPE 13 Term Broken pipe: write to pipe with no readers
SIGALRM 14 Term Timer signal from alarm(2)
SIGTERM 15 Term Termination signal
SIGUSR1 30,10,16 Term User-defined signal 1
SIGUSR2 31,12,17 Term User-defined signal 2
SIGCHLD 20,17,18 Ign Child stopped or terminated
SIGCONT 19,18,25 Cont Continue if stopped
SIGSTOP 17,19,23 Stop Stop process
SIGTSTP 18,20,24 Stop Stop typed at tty
SIGTTIN 21,21,26 Stop tty input for background process
SIGTTOU 22,22,27 Stop tty output for background process
int kill(pid_t pid, int signum);
Для отправка сигнала необходимо, чтобы uid или euid текущего процесса был равен 0 или совпадал с uid процесса получателя.
Получатель сигнала зависит от величины и знака параметра pid
:
Если signum == 0 - сигнал не посылается, но делается проверка прав на посылку сигнала и формируются код ответа и errno.
int sigqueue(pid_t pid, int sig, const union sigval value);
Аналогично kill()
, но выполняется по правилам сигналов реального времени. Сигнал и связанная с ним дополнительная информация (целое или адрес) value
помещаются в очередь сигналов (FIFO). Таким образом процессу можно отправить несколько однотипных сигналов с разными значениями value
.
raise(int signum);
Отправка сигнала текущему процессу. Эквивалент kill(getpid(),signum);
abort();
Убирает из маски сигналов сигнала SIGABRT и отправляет его текущему процессу . Если сигнал перехватывается или игнорируется, то после возвращения из обработчика abort()
завершает программу. Выхода из функции abort()
не предусмотрено. Единственный способ продолжить выполнение - не выходить из обработчика сигнала.
alarm(time);
Отправка сигнала SIGALRM себе через time секунд. Возвращает 0 если ранее alarm не был установлен или число секунд остававшихся до срабатывания предыдущего alarma. Таймер управляющий alarm'ами один. Соответственно установка нового alarm'а отменяет старый. Параметр time==0 позволяет получить оставшееся до alarm'а время без установки нового.
Установка реакции на сигнал через функцию signal() не до конца стандартизована и сохраняется для совместимости с историческими версиями Unix. Не рекомендуется к использованию. В стандарте POSIX signal() заменен на вызов sigaction(), сохраняющий для совместимости эмуляцию поведения signal().
signal(SIGINT,sighandler);
sighandler - адрес функции обработчика void sighandler(int)
или один из двух макросов: SIG_DFL (обработчик по умолчанию) или SIG_IGN (игнорирование сигнала).
signal(...)
возвращает предыдущее значение обработчика или SIG_ERR в случае ошибки.
В Linux и SysV при вызове sighandler обработчик сбрасывается в SIG_DFL и возможна доставка нового сигнала во время работы sighandler
. Такое поведение заставляет первой строкой в sighandler
восстанавливать себя в качестве обработчика сигнала, и, даже в этом случае, не гарантирует от вызова обработчика по умолчанию. В BSD системах сброс не происходит, доставка новых сигнала блокируется до выхода из sighandler
.
Пример кода:
#include <signal.h>
void sighandler(int signum) {
signal(signum,sighandler);
...
}
main() {
signal(SIGINT,sighandler);
signal(SIUSR1,SIG_IGN);
signal(SIUSR2,SIG_DFL);
}
sigaction()
struct sigaction {
void (*sa_handler)(int); // Обработчик сигнала старого стиля
void (*sa_sigaction)(int, siginfo_t *, void *); // Обработчик сигнала нового стиля
// Обработчик выбирается на основе флага SA_SIGINFO
// в поле sa_flags
sigset_t sa_mask; // Маска блокируемых сигналов
int sa_flags; // Набор флагов
// SA_RESETHAND - сброс обработчика на SIG_DFL после выхода из назначенного обработчика.
// SA_RESTART - восстановление прерванных системных вызовов после выхода из обработчика.
// SA_SIGINFO - вызов sa_sigaction вместо sa_handler
void (*sa_restorer)(void); // Устаревшее поле
}
sa_sigaction()
siginfo_t {
int si_signo; // Номер сигнала
int si_code; // Способ отправки сигнала или уточняющее значение
// SI_USER сигнал отправлен через вызов kill()
// SI_QUEUE сигнал отправлен через вызов sigqueue()
// FPE_FLTDIV - уточнение для сигнала SIGFPE - деление на ноль
// ILL_ILLOPC - уточнение для сигнала SIGILL - недопустимый опкод
// ...
pid_t si_pid; // PID процесса отправителя (дочернего процесса при SIGCHLD)
uid_t si_uid; // UID процесса отправителя
int si_status; // Статус завершения дочернего процесса при SIGCHLD
sigval_t si_value; // Значение, переданое через параметр value при вызове sigqueue()
void * si_addr; // Адрес в памяти
// SIGILL, SIGFPE - адрес сбойной инструкции
// SIGSEGV, SIGBUS - адрес сбойной памяти
}
В Linux структура siginfo_t
содержит больше полей, но они служат для совместимости и не заполняются.
#include <signal.h>
void myhandler(int sig) {
...
}
void myaction(int signum, siginfo_t * siginfo, void *code) {
...
}
main() {
struct sigaction act,oldact;
act.sa_sigaction=myaction;
act.sa_flags=SA_SIGINFO;
sigaction(signum, &act, &oldact);
}
Все функции блокирования сигналов работают с маской сигналов типа sigset_t. В Linux это 64 бита, т.е. два слова в 32х разрядной архитектуре или одно в 64х разрядной. Выставленный бит в маске сигналов означает, что доставка сигнала с соответствующим номером будет заблокирована. Сигналы SIGKILL и SIGSTOP заблокировать нельзя.
#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signum);
int sigdelset(sigset_t *set, int signum);
int sigismember(const sigset_t *set, int signum);
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset); Изменить маску сигналов
Параметр how определяет операцию над текущей маской. Значения SIG_BLOCK, SIG_UNBLOCK, SIG_SETMASK.
int sigpending(sigset_t *set); // Получить список недоставленных из-за блокировки сигналов.
int sigsuspend(const sigset_t *mask); //Установить новую маску и "уснуть" до получения и обработки разрешенного в ней сигнала.
sigwait
ожидает блокировки какого-либо из сигналов, указанных в маске, удаляет этот сигнал и возвращает его номер в параметр sig
. Если реализация сигналов предусматривает очередь сигналов, то удаляется только один элемент из очереди.
int sigwait(const sigset_t *set, int *sig);
Для организации диалоговой работы пользователей в Unix вводится понятие терминальной сессии. С точки зрения пользователя - это процесс работы с текстовым терминалом с момента ввода имени и пароля и до выхода из системы командой logout
(exit
, нажатие ^D в пустой строке). Во время терминальной сессии может быть запущено несколько программ, которые будут параллельно выполнятся в фоновом режиме и между которыми можно переключаться в диалоговом режиме. После завершения терминальной сессии возможно принудительное завершение всех запущенных в ней фоновых процессов.
С точки зрения ядра - терминальная сессия - это группа процессов, имеющих один идентификатор сеанса sid. С идентификатором sid связан драйвер управляющего терминала, доступный всем членам сеанса как файл символьного устройства /dev/tty. Для каждого сеанса существует свой /dev/tty. Управляющий терминал взаимодействует с процессами сеанса с помощью отправки сигналов.
В рамках одного сеанса могут существовать несколько групп процессов. С каждым процессом связан идентификатор группы pgid. Одна из групп в сеансе может быть зарегистрирована в драйвере управляющего терминала как группа фоновых процессов. Процессы могут переходить из группы в группу самостоятельно или переводить из группы в группу другие процессы сеанса. Перейти в группу другого сеанса нельзя, но можно создать свой собственный сеанс из одного процесса со своей группой в этом сеансе. Вернуться в предыдущий сеанс уже не получится.
Группа процессов - инструмент для доставки сигнала нескольким процессам, а также способ арбитража при доступе к терминалу. Идентификатор группы pgid равен pid создавшего её процесса - лидера группы. Процесс может переходить из группы в группу внутри одного сеанса.
#include <unistd.h>
int setpgid(pid_t pid, pid_t pgid); // включить процесс pid в группу pgid.
// pid=0 означает текущий процесс,
// pgid=0 означает pgid=pid текущего процесса
// pid=pgid=0 - создание новой группы с pgid=pid текущего процесса
// и переход в эту группу
pid_t getpgid(pid_t pid); // получить номер группы процесса pid.
// pid=0 - текущий процесс
int setpgrp(void); // создание группы, эквивалент setpgid(0.0);
pid_t getpgrp(void); // запрос текущей группы, эквивалент getpgid(0);
Сеанс - средство для контроля путем посылки сигналов над несколькими группами процессов со стороны терминального драйвера. Как правило соответствует диалоговой пользовательской сессии. Идентификатор сеанса sid равняется идентификатору pid, создавшего его процесса - лидера сеанса. Одновременно с сеансом создаётся новая группа с pgid равным pid лидера сеанса. Поскольку переход группы из сеанса в сеанс невозможен, то создающий сеанс процесс не может быть лидером группы.
#include <unistd.h>
pid_t setsid(void); //Создание новой группы и нового сеанса. Текущий процесс не должен быть лидером группы.
pid_t getsid(pid_t pid); //Возвращает номер сеанса для указанного процесса
Создание собственного сеанса рекомендуется начать с fork, чтобы гарантировать, что процесс не является лидером группы.
if( fork() ) exit(0);
setsid();
Процессы в фоновой группе выполняются до тех пор, пока не попытаются осуществить чтение или запись через файловый дескриптор управляющего терминала. В этот момент они получают сигнал SIGTTIN или SIGTTOU соответственно. Действие по умолчанию для данного сигнала - приостановка выполнения процесса.
Назначение фоновой группы:
#include <unistd.h>
pid_t tcgetpgrp(int fd); // получить pgid фоновой группы, связанной с управляющим терминалом,
// на который ссылается файловый дескриптор fd
int tcsetpgrp(int fd, pid_t pgrp); // назначить pgid фоновой группы терминалу,
// на который ссылается файловый дескриптор fd
Некоторые сочетания клавиш позволяют посылать сигналы процессам сеанса:
bash
отслеживает остановку дочерних процессов и вносит их в списки своих фоновых процессов. Остановленный процесс может быть продолжен командой fg n
, где n - порядковый нрмер процесса в списке фоновых процессовОткрыть управляющий терминал сеанса
#include <stdio.h>
char name[L_ctermid];
int fd;
ctermid(name); // если name=NULL, то используется внутренний буфер
// ctermid возвращает указатель на буфер
// L_ctermid - библиотечная константа
fd = open(name, O_RDWR, 0);