Операционная система UNIX - Андрей Робачевский 23 стр.


Для UNIX System V реализована следующая возможность получения более полной информации о сигнале. Если установлен флаг SA_SIGINFO, то при получении сигнала sig будет вызван обработчик, адресованный полем sa_sigaction. Помимо номера сигнала, обычно передаваемого обработчику сигнала, ему будет переданы указатель на структуру siginfo_t, содержащую информацию о причинах получения сигнала, а также указатель на структуру ucontext_t, содержащую контекст процесса.

Структура siginfo_t определена в файле <siginfo.h> и включает следующие поля:

int si_signoНомер сигнала
int si_errnoНомер ошибки
int si_codeПричина отправления сигнала

В поле si_signo хранится номер сигнала. Поле si_code имеет следующий смысл: если его значение меньше или равно нулю, значит сигнал был отправлен прикладным процессом, в этом случае структура siginfo_t содержит также следующие поля:

pid_t si_pidИдентификатор процесса PID
uid_t si_uidИдентификатор пользователя UID

которые адресуют процесс, пославший сигнал; если значение si_code больше нуля, то оно указывает на причину отправления сигнала. Список возможных значений si_code для некоторых сигналов, соответствующих полю si_signo, приведен в табл. 2.19

Таблица 2.19. Значения поля si_code структуры siginfo_t для некоторых сигналов

Значение поля si_signoЗначение поля si_codeОписание
SIGILLПопытка выполнения недопустимой инструкции
ILL_ILLOPCНедопустимый код операции (opcode)
ILL_ILLOPNНедопустимый операнд
ILL_ADRНедопустимый режим адресации
ILL_ILLTRPНедопустимая ловушка (trap)
ILL_PRVOPCПривилегированный код операции
ILL_PRVREGПривилегированный регистр
ILL_COPROCОшибка сопроцессора
ILL_BADSTKОшибка внутреннего стека
SIGFPEОсобая ситуация операции с плавающей точкой
FPE_INTDIVЦелочисленное деление на ноль
FPE_INTOVFЦелочисленное переполнение
FPE_FLTDIVДеление на ноль с плавающей точкой
FPE_FLTOVFПереполнение с плавающей точкой
FPE_FLTUNDПотеря точности с плавающей точкой (underflow)
FPE_FLTRESНеоднозначный результат операции с плавающей точкой
FPE_FLTINVНедопустимая операция с плавающей точкой
FPE_FLTSUBИндекс вне диапазона
SIGSEGVНарушение сегментации
SEGV_MAPPERАдрес не отображается на объект
SEGV_ACCERRНедостаточно прав на отображаемый объект
SIGBUSОшибка адресации
BUS_ADRALNНедопустимое выравнивание адреса
BUS_ADRERRНесуществующий физический адрес
BUS_OBJERRАппаратная ошибка, связанная с объектом
SIGTRAPЛовушка
TRAP_BRKPTПроцессом достигнута точка останова
TRAP_TRACEЛовушка трассирования процесса
SIGCHLDЗавершение выполнения дочернего процесса
CLD_EXITEDДочерний процесс завершил выполнение
CLD_KILLEDДочерний процесс был "убит"
CLD_DUMPEDНенормальное завершение дочернего процесса
CLD_TRAPPEDТрассируемый дочерний процесс находится в ловушке
CLD_STOPPEDВыполнение дочернего процесса было остановлено
CLD_CONTINUEDВыполнение остановленного дочернего процесса было продолжено
SIGPOLLСобытие на опрашиваемом устройстве
POLL_INПоступили данные для ввода
POLL_OUTСвободны буферы данных
POLL_MSGСообщение ожидает ввода
POLL_ERRОшибка ввода/вывода
POLL_PRIВысокоприоритетные данные ожидают ввода
POLL_HUPУстройство отключено

Уже отмечалось, что при получении сигнала от пользовательского процесса структура siginfo_t содержит дополнительные поля (табл. 2.20).

Таблица 2.20. Дополнительные поля структуры siginfo_t

Значение поля si_signoДополнительные поляЗначение
SIGILL SIGFPEcaddr_t si_addrАдрес недопустимой инструкции
SIGSEGV SIGBUScaddr_t si_addrАдрес недопустимой области памяти
SIGCHLDpid_t si_pidИдентификатор дочернего процесса
int si_statusКод возврата сигнала
SIGPOLLlong si_bandОшибка канала (для модулей STREAMS)

Установить маску сигналов или получить текущую маску можно с помощью функции sigprocmask(2):

#include <signal.h>

int sigprocmask(int how, sigset_t *set, sigset_t *oset);

Маска сигналов изменяется в соответствии с аргументом how, который может принимать следующие значения:

SIG_BLOCKРезультирующая маска получится путем объединения текущей маски и набора set
SIG_UNBLOCKСигналы набора set будут удалены из текущей маски
SIG_SETMASKТекущая маска будет заменена на набор set

Если указатель set равен NULL, то аргумент how игнорируется. Если аргумент oset не равен NULL, то в набор, адресованный этим аргументом, помещается текущая маска сигналов.

Функция sigpending(2) используется для получения набора заблокированных сигналов, ожидающих доставки:

#include <signal.h>

int sigpending(int how, sigset_t *set, sigset_t *oset);

Список сигналов, ожидающих доставки, возвращается в наборе, адресованном аргументом set.

Системный вызов sigsuspend(2) замещает текущую маску набором, адресованным аргументом set, и приостанавливает выполнение процесса до получения сигналов, диспозиция которых установлена либо на завершение выполнения процесса, либо на вызов функции-обработчика сигнала.

#include <signal.h>

int sigsuspend(const sigset_t *set);

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

Заметим, что в BSD UNIX вызов signal(3) является упрощенным интерфейсом к более общей функции sigaction(2), в то время как в ветви System V signal(3) подразумевает использование старой семантики ненадежных сигналов.

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

#include <signal.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#include <unistd.h>

/* Вариант "надежной" функции signal() */

void (*mysignal(int signo, void (*hndlr)(int)))(int) {

struct sigaction act, oact;

/* Установим маску сигналов */

act.sa_handler = hndlr;

sigemptyset(&act.sa_mask);

act.sa_flags = 0;

if (signo != SIGALRM)

act.sa_flags = SA_RESTART;

/* Установим диспозицию */

if (sigaction(signo, &act, &oact) < 0)

return SIG_ERR;

return(oact.sa_handler);

}

/* Функция-обработчик сигнала */

static void sig_hndlr(int signo) {

/* Эта часть кода нам уже не нужна

mysignal(SIGINT, sig_hndlr);

*/

printf("Получен сигнал SIGINT\n");

}

main() {

/* Установим диспозицию */

mysignal(SIGINT, sig_hndlr);

mysignal(SIGUSR2, SIG_IGN);

/* Бесконечный цикл */

while (1)

pause();

}

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

Группы и сеансы

После создания процесса ему присваивается уникальный идентификатор, возвращаемый системным вызовом fork(2) родительскому процессу. Дополнительно ядро назначает процессу идентификатор группы процессов (process group ID). Группа процессов включает один или более процессов и существует, пока в системе присутствует хотя бы один процесс этой группы. Временной интервал, начинающийся с создания группы и заканчивающийся, когда последний процесс ее покинет, называется временем жизни группы. Последний процесс может либо завершить свое выполнение, либо перейти в другую группу.

Многие системные вызовы могут быть применены как к единичному процессу, так и ко всем процессам группы. Например, системный вызов kill(2) может отправить сигнал как одному процессу, так и всем процессам указанной группы. Точно так же функция waitpid(2) позволяет родительскому процессу ожидать завершения конкретного процесса или любого процесса группы.

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

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

#include <sys/types.h>

#include <unistd.h>

pid_t getpgrp(void);

pid_t getpgid(pid_t pid);

Аргумент pid, который передается функции getpgid(2), адресует процесс, идентификатор группы которого требуется узнать. Если этот процесс не принадлежит к тому же сеансу, что и процесс, сделавший системный вызов, функция возвращает ошибку.

Системный вызов setpgid(2) позволяет процессу стать членом существующей группы или создать новую группу.

#include <sys/types.h>

#include <unistd.h>

int setpgid(pid_t pid, pid_t pgid);

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

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

Идентификатор сеанса можно узнать с помощью функции getsid(2):

#include <sys/types.h>

#include <unistd.h>

pid_t getsid(pid_t pid);

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

Вызов функции setsid(2) приводит к созданию нового сеанса:

#include <sys/types.h>

#include <unistd.h>

pid_t setsid(void);

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

Понятия группы и сеанса тесно связаны с терминалом или, точнее, с драйвером терминала. Каждый сеанс может иметь один ассоциированный терминал, который называется управляющим терминалом (controlling terminal), а группы, созданные в данном сеансе, наследуют этот управляющий терминал. Наличие управляющего терминала позволяет ядру контролировать стандартный ввод/вывод процессов, а также дает возможность отправить сигнал всем процессам ассоциированной с терминалом группы, например, при его отключении. Типичным примером является регистрация и работа пользователя в системе. При входе в систему терминал пользователя становится управляющим для лидера сеанса (в данном случае для командного интерпретатора shell) и всех процессов, порожденных лидером (в данном случае для всех процессов, которые запускает пользователь из командной строки интерпретатора). При выходе пользователя из системы shell завершает свою работу и таким образом отключается от управляющего терминала, что вызывает отправление сигнала SIGHUP всем незавершенным процессам текущей группы. Это гарантирует, что после завершения работы пользователя в системе не останется запущенных им процессов.

Текущие и фоновые группы процессов

Как было показано, для каждого управляющего терминала существует сеанс, включающий одну или несколько групп процессов. Одна из этих групп является текущей (foreground group), а остальные фоновыми (background group). Сигналы SIGINT и SIGQUIT, которые генерируются драйвером терминала, посылаются всем процессам текущей группы. Попытка процессов фоновых групп осуществить доступ к управляющему терминалу, как правило, вызывает отправление им сигналов SIGSTP, SIGTTIN или SIGTTOU.

Рассмотрим следующие команды:

$ find / -name foo &

$ cat | sort

При этом происходит чтение ввода пользователя с клавиатуры (cat(1) и сортировка введенных данных (sort(1)). Если интерпретатор поддерживает управление заданиями, оба процесса, созданные для программ cat(1) и sort(1), будут помещены в отдельную группу. Это подтверждается выводом команды ps(1):

$ ps -efj | egrep "PID|andy"

Назад Дальше