Главная страница
Навигация по странице:

  • и специфические действия процессов. Термин "сигнал" используется также для обозначения самого события .

  • Содержание занятия


    Скачать 0.66 Mb.
    НазваниеСодержание занятия
    Анкор14314421
    Дата02.05.2023
    Размер0.66 Mb.
    Формат файлаrtf
    Имя файла330383.rtf
    ТипДокументы
    #1103574
    страница2 из 5
    1   2   3   4   5

    1.2 Сигналы



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

    Согласно стандарту POSIX-2001, под сигналом понимается механизм, с помощью которого процесс или поток управления уведомляют о некотором событии, произошедшем в системе, или подвергают воздействию этого события. Примерами подобных событий могут служить аппаратные исключительные ситуации и специфические действия процессов. Термин "сигнал" используется также для обозначения самого события.

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

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

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

    У каждого потока управления есть маска сигналов, определяющая набор блокируемых сигналов. Обычно она достается в наследство от родительского потока.

    С сигналом могут быть ассоциированы действия одного из трех типов.

    SIG_DFL

    Подразумеваемые действия, зависящие от сигнала. Они описаны в заголовочном файле .

    SIG_IGN

    Игнорировать сигнал. Доставка сигнала не оказывает воздействия на процесс.

    указатель на функцию

    Обработать сигнал, выполнив при его доставке заданную функцию. После завершения функции обработки процесс возобновляет выполнение с точки прерывания. Обычно функция обработки вызывается в соответствии со следующим C-заголовком: void func (int signo); где signo - номер доставленного сигнала.

    Первоначально, до входа в функцию main(), реакция на все сигналы установлена как SIG_DFL или SIG_IGN.

    Функция называется асинхронно-сигнально-безопасной (АСБ), если ее можно вызывать без каких-либо ограничений при обработке сигналов. В стандарте POSIX-2001 имеется список функций, которые должны быть либо повторно входимыми, либо непрерываемыми сигналами, что превращает их в АСБ-функции. В этот список включены 117 функций, в том числе почти все из рассматриваемых нами.

    Если сигнал доставляется потоку, а реакция заключается в завершении, остановке или продолжении, весь процесс должен завершиться, остановиться или продолжиться.

    Перейдем к изложению возможностей по генерации сигналов. Выше была кратко рассмотрена служебная программа kill как средство терминирования процессов извне. На самом деле она посылает заданный сигнал; то же делает и одноименная функция (пример 8.6).

    #include

    int kill (pid_t pid, int sig);

    Листинг 8.6. Описание функции kill().

    Сигнал задается аргументом sig, значение которого может быть нулевым; в этом случае действия функции kill() сводятся к проверке допустимости значения pid (нулевой результат - признак успешного завершения kill()).

    Если pid > 0, это значение трактуется как идентификатор процесса. При нулевом значении pid сигнал посылается всем процессам из той же группы, что и вызывающий. Если значение pid равно -1, адресатами являются все процессы, которым вызывающий имеет право посылать сигналы. При прочих отрицательных значениях pid сигнал посылается группе процессов, чей идентификатор равен абсолютной величине pid.

    Процесс имеет право послать сигнал адресату, заданному аргументом pid, если он (процесс) имеет соответствующие привилегии или его реальный или действующий идентификатор пользователя совпадает с реальным или сохраненным ПДП-идентификатором адресата.

    У служебной программы kill имеется полезная опция -l, позволяющая увидеть соответствие между номерами сигналов и их мнемоническими именами. Результат выполнения команды kill -l может выглядеть так, как показано в пример 8.7.

    1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL

    5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE

    9) SIGKILL10) SIGUSR111) SIGSEGV12) SIGUSR2

    13) SIGPIPE14) SIGALRM15) SIGTERM17) SIGCHLD

    18) SIGCONT19) SIGSTOP20) SIGTSTP21) SIGTTIN

    22) SIGTTOU23) SIGURG24) SIGXCPU25) SIGXFSZ

    26) SIGVTALRM27) SIGPROF28) SIGWINCH29) SIGIO

    30) SIGPWR31) SIGSYS32) SIGRTMIN33) SIGRTMIN+1

    34) SIGRTMIN+235) SIGRTMIN+336) SIGRTMIN+437) SIGRTMIN+5

    38) SIGRTMIN+639) SIGRTMIN+740) SIGRTMIN+841) SIGRTMIN+9

    42) SIGRTMIN+1043) SIGRTMIN+1144) SIGRTMIN+1245) SIGRTMIN+13

    46) SIGRTMIN+1447) SIGRTMIN+1548) SIGRTMAX-1549) SIGRTMAX-14

    50) SIGRTMAX-1351) SIGRTMAX-1252) SIGRTMAX-1153) SIGRTMAX-10

    54) SIGRTMAX-955) SIGRTMAX-856) SIGRTMAX-757) SIGRTMAX-6

    58) SIGRTMAX-559) SIGRTMAX-460) SIGRTMAX-361) SIGRTMAX-2

    62) SIGRTMAX-163) SIGRTMAX

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

    Мы не будем пояснять назначение всех представленных в листинге сигналов, ограничившись кратким описанием тех, что фигурируют в стандарте POSIX-2001 как обязательные для реализации. Попутно отметим, что, согласно стандарту языка C, должны быть определены имена всего шести сигналов: SIGABRT, SIGFPE, SIGILL, SIGINT, SIGSEGV и SIGTERM.

    SIGABRT

    Сигнал аварийного завершения процесса. Подразумеваемая реакция предусматривает, помимо аварийного завершения, создание файла с образом памяти процесса.

    SIGALRM

    Срабатывание будильника. Подразумеваемая реакция - аварийное завершение процесса.

    SIGBUS

    Ошибка системной шины как следствие обращения к неопределенной области памяти. Подразумеваемая реакция - аварийное завершение и создание файла с образом памяти процесса.

    SIGCHLD

    Завершение, остановка или продолжение порожденного процесса. Подразумеваемая реакция - игнорирование.

    SIGCONT

    Продолжение процесса, если он был остановлен. Подразумеваемая реакция - продолжение выполнения или игнорирование (если процесс не был остановлен).

    SIGFPE

    Некорректная арифметическая операция. Подразумеваемая реакция - аварийное завершение и создание файла с образом памяти процесса.

    SIGHUP

    Сигнал разъединения. Подразумеваемая реакция - аварийное завершение процесса.

    SIGILL

    Некорректная команда. Подразумеваемая реакция - аварийное завершение и создание файла с образом памяти процесса.

    SIGINT

    Сигнал прерывания, поступивший с терминала. Подразумеваемая реакция - аварийное завершение процесса.

    SIGKILL

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

    SIGPIPE

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

    SIGQUIT

    Сигнал выхода, поступивший с терминала. Подразумеваемая реакция - аварийное завершение и создание файла с образом памяти процесса.

    SIGSEGV

    Некорректное обращение к памяти. Подразумеваемая реакция - аварийное завершение и создание файла с образом памяти процесса.

    SIGSTOP

    Остановка выполнения (этот сигнал нельзя перехватить для обработки или проигнорировать). Подразумеваемая реакция - остановка процесса.

    SIGTERM

    Сигнал терминирования. Подразумеваемая реакция - аварийное завершение процесса.

    SIGTSTP

    Сигнал остановки, поступивший с терминала. Подразумеваемая реакция - остановка процесса.

    SIGTTIN

    Попытка чтения из фонового процесса. Подразумеваемая реакция - остановка процесса.

    SIGTTOU

    Попытка записи из фонового процесса. Подразумеваемая реакция - остановка процесса.

    SIGUSR1, SIGUSR2

    Определяемые пользователем сигналы. Подразумеваемая реакция - аварийное завершение процесса.

    SIGPOLL

    Опрашиваемое событие. Подразумеваемая реакция - аварийное завершение процесса.

    SIGPROF

    Срабатывание таймера профилирования. Подразумеваемая реакция - аварийное завершение процесса.

    SIGSYS

    Некорректный системный вызов. Подразумеваемая реакция - аварийное завершение и создание файла с образом памяти процесса.

    SIGTRAP

    Попадание в точку трассировки/прерывания. Подразумеваемая реакция - аварийное завершение и создание файла с образом памяти процесса.

    SIGURG

    Высокоскоростное поступление данных в сокет. Подразумеваемая реакция - игнорирование.

    SIGVTALRM

    Срабатывание виртуального таймера. Подразумеваемая реакция - аварийное завершение процесса.

    SIGXCPU

    Исчерпан лимит процессорного времени. Подразумеваемая реакция - аварийное завершение и создание файла с образом памяти процесса.

    SIGXFSZ

    Превышено ограничение на размер файлов. Подразумеваемая реакция - аварийное завершение и создание файла с образом памяти процесса.

    Процесс (поток управления) может послать сигнал самому себе с помощью функции raise() (пример 8.8). Для процесса вызов raise() эквивалентен kill (getpid(), sig);

    #include

    int raise (int sig);

    Листинг 8.8. Описание функции raise().

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

    #include

    void abort (void);

    Листинг 8.9. Описание функции abort().

    Опросить и изменить способ обработки сигналов позволяет функция sigaction() (пример 8.10).

    #include

    int sigaction (int sig, const struct sigaction

    *restrict act, struct sigaction

    *restrict oact);

    Листинг 8.10. Описание функции sigaction().

    Для описания способа обработки сигнала используется структура sigaction, которая должна содержать по крайней мере следующие поля:

    void (*sa_handler) (int);

    /* Указатель на функцию обработки сигнала */

    /* или один из макросов SIG_DFL или SIG_IGN */

    sigset_t sa_mask;

    /* Дополнительный набор сигналов, блокируемых */

    /* на время выполнения функции обработки */

    int sa_flags;

    /* Флаги, влияющие на поведение сигнала */

    void (*sa_sigaction) (int, siginfo_t *, void *);

    /* Указатель на функцию обработки сигнала */

    Приложение, соответствующее стандарту, не должно одновременно использовать поля обработчиков sa_handler и sa_sigaction.

    Тип sigset_t может быть целочисленным или структурным и представлять набор сигналов (см. далее).

    Тип siginfo_t должен быть структурным по крайней мере со следующими полями:

    int si_signo; /* Номер сигнала */

    int si_errno;

    /* Значение переменной errno, ассоциированное

    с данным сигналом */

    int si_code;

    /* Код, идентифицирующий причину сигнала */

    pid_t si_pid;

    /* Идентификатор процесса, пославшего сигнал */

    uid_t si_uid;

    /* Реальный идентификатор пользователя

    процесса, пославшего сигнал */

    void *si_addr;

    /* Адрес, вызвавший генерацию сигнала */

    int si_status;

    /* Статус завершения порожденного процесса */

    long si_band;

    /* Событие, связанное с сигналом SIGPOLL */

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

    SI_USER

    Сигнал послан функцией kill().

    SI_QUEUE

    Сигнал послан функцией sigqueue().

    SI_TIMER

    Сигнал сгенерирован в результате срабатывания таймера, установленного функцией timer_settime().

    SI_ASYNCIO

    Сигнал вызван завершением асинхронной операции ввода/вывода.

    SI_MESGQ

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

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

    ILL_ILLOPC

    Некорректный код операции.

    ILL_COPROC

    Ошибка сопроцессора.

    FPE_INTDIV

    Целочисленное деление на нуль.

    FPE_FLTOVF

    Переполнение при выполнении операции вещественной арифметики.

    FPE_FLTSUB

    Индекс вне диапазона.

    SEGV_MAPERR

    Адрес не отображен на объект.

    BUS_ADRALN

    Некорректное выравнивание адреса.

    BUS_ADRERR

    Несуществующий физический адрес.

    TRAP_BRKPT

    Процесс достиг точки прерывания.

    TRAP_TRACE

    Срабатывание трассировки процесса.

    CLD_EXITED

    Завершение порожденного процесса.

    CLD_STOPPED

    Остановка порожденного процесса.

    POLL_PRI

    Поступили высокоприоритетные данные.

    Вернемся непосредственно к описанию функции sigaction(). Если аргумент act отличен от NULL, он указывает на структуру, специфицирующую действия, которые будут ассоциированы с сигналом sig. По адресу oact (если он не NULL) возвращаются сведения о прежних действиях. Если значение act есть NULL, обработка сигнала остается неизменной; подобный вызов можно использовать для опроса способа обработки сигналов.

    Следующие флаги в поле sa_flags влияют на поведение сигнала sig.

    SA_NOCLDSTOP

    Не генерировать сигнал SIGCHLD при остановке или продолжении порожденного процесса (значение аргумента sig должно равняться SIGCHLD).

    SA_RESETHAND

    При входе в функцию обработки сигнала sig установить подразумеваемую реакцию SIG_DFL и очистить флаг SA_SIGINFO (см. далее).

    SA_SIGINFO

    Если этот флаг не установлен и определена функция обработки сигнала sig, она вызывается с одним целочисленным аргументом - номером сигнала. Соответственно, в приложении следует использовать поле sa_handler структуры sigaction. При установленном флаге SA_SIGINFO функция обработки вызывается с двумя дополнительными аргументами, как void func (int sig, siginfo_t *info, void *context); второй аргумент указывает на данные, поясняющие причину генерации сигнала, а третий может быть преобразован к указателю на тип ucontext_t - контекст процесса, прерванного доставкой сигнала. В этом случае приложение должно использовать поле sa_sigaction и поля структуры типа siginfo_t. В частности, если значение si_code неположительно, сигнал был сгенерирован процессом с идентификатором si_pid и реальным идентификатором пользователя si_uid.

    SA_NODEFER

    По умолчанию обрабатываемый сигнал добавляется к маске сигналов процесса при входе в функцию обработки; флаг SA_NODEFER предписывает не делать этого, если только sig не фигурирует явным образом в sa_mask.

    Опросить и изменить способ обработки сигналов можно и на уровне командного интерпретатора, посредством специальной встроенной команды trap:

    trap [действие условие ...]

    Аргумент "условие" может задаваться как EXIT (завершение командного интерпретатора) или как имя доставленного сигнала (без префикса SIG). При задании аргумента "действие" минус обозначает подразумеваемую реакцию, пустая цепочка ("") - игнорирование. Если в качестве действия задана команда, то при наступлении условия она обрабатывается как eval действие.

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

    save_traps=$(trap)

    . . .

    eval "$save_traps"

    Листинг 8.11. Пример сохранения и восстановления способа обработки сигналов посредством специальной встроенной команды trap.

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

    trap '$HOME/logout' EXIT

    Листинг 8.12. Пример использования специальной встроенной команды trap.

    При перенаправлении вывода в файл приходится считаться с возможностью возникновения ошибок, специфичных для каналов. Чтобы защитить от них процедуры начальной загрузки, в ОС Lunix применяются связки из игнорирования и последующего восстановления подразумеваемой реакции на сигнал SIGPIPE (пример 8.13).

    trap "" PIPE

    echo "$INITLOG_ARGS -n $0 -s \"$1\" -e 1" >&21

    trap - PIPE

    Листинг 8.13. Пример использования специальной встроенной команды trap для защиты от ошибок, специфичных для каналов.

    К техническим аспектам можно отнести работу с наборами сигналов, которая выполняется посредством функций, показанных в пример 8.14. Функции sigemptyset() и sigfillset() инициализируют набор, делая его, соответственно, пустым или "полным". Функция sigaddset() добавляет сигнал signo к набору set, sigdelset() удаляет сигнал, а sigismember() проверяет вхождение в набор. Обычно признаком завершения является нулевой результат, в случае ошибки возвращается -1. Только sigismember() выдает 1, если сигнал signo входит в набор set.

    #include

    int sigemptyset (sigset_t *set);

    int sigfillset (sigset_t *set);

    int sigaddset (sigset_t *set, int signo);

    int sigdelset (sigset_t *set, int signo);

    int sigismember (const sigset_t *set,

    int signo);

    Листинг 8.14. Описание функций для работы с наборами сигналов.

    Функция sigprocmask() (пример 8.15) предназначена для опроса и/или изменения маски сигналов процесса, определяющей набор блокируемых сигналов.

    #include

    int sigprocmask (int how, const sigset_t

    *restrict set, sigset_t *restrict oset);

    Листинг 8.15. Описание функции sigprocmask().

    Если аргумент set отличен от NULL, он указывает на набор, используемый для изменения текущей маски сигналов. Аргумент how определяет способ изменения; он может принимать одно из трех значений: SIG_BLOCK (результирующая маска получается при объединении текущей и заданной аргументом set), SIG_SETMASK (результирующая маска устанавливается равной set) и SIG_UNBLOCK (маска set вычитается из текущей).

    По адресу oset (если он не NULL) возвращается прежняя маска. Если значение set есть NULL, набор блокируемых сигналов остается неизменным; подобный вызов можно использовать для опроса текущей маски сигналов процесса.

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

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

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

    #include

    int sigpending (sigset_t *set);

    Листинг 8.16. Описание функции sigpending().

    #include

    int sigwait (const sigset_t *restrict set,

    int *restrict sig);

    Листинг 8.17. Описание функции sigwait().

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

    Отметим, что стандарт POSIX-2001 не специфицирует воздействие функции sigwait() на обработку сигналов, включенных в набор set. Чтобы дождаться доставки обрабатываемого или терминирующего процесс сигнала, можно воспользоваться функцией pause() (пример 8.18).

    #include

    int pause (void);

    Листинг 8.18. Описание функции pause().

    Функция pause() может ждать доставки сигнала неопределенно долго. Возврат из pause() осуществляется после возврата из функции обработки сигнала (результат при этом равен -1). Если прием сигнала вызывает завершение процесса, возврата из функции pause(), естественно, не происходит.

    Несмотря на внешнюю простоту, использование функции pause() сопряжено с рядом тонкостей. При наивном подходе сначала проверяют некоторое условие, связанное с сигналом, и, если оно не выполнено (сигнал отсутствует), вызывают pause(). К сожалению, сигнал может быть доставлен в промежутке между проверкой и вызовом pause(), что нарушает логику работы процесса и способно привести к его зависанию. Решить подобную проблему позволяет функция sigsuspend() (пример 8.19) в сочетании с рассмотренной выше функцией sigprocmask().

    #include

    int sigsuspend (const sigset_t *sigmask);

    Листинг 8.19. Описание функции sigsuspend().

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

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

    В качестве примера использования описанных выше функций работы с сигналами рассмотрим упрощенную реализацию функции abort() (пример 8.20).

    #include

    #include

    #include

    void abort (void) {

    struct sigaction sact;

    sigset_t sset;

    /* Вытолкнем буфера */

    (void) fflush (NULL);

    /* Снимем блокировку сигнала SIGABRT */

    if ((sigemptyset (&sset) == 0) && (sigaddset (&sset, SIGABRT) == 0)) {

    (void) sigprocmask (SIG_UNBLOCK, &sset, (sigset_t *) NULL);

    }

    /* Пошлем себе сигнал SIGABRT. */

    /* Возможно, его перехватит функция обработки, */

    /* и тогда вызывающий процесс может не завершиться */

    raise (SIGABRT);

    /* Установим подразумеваемую реакцию на сигнал SIGABRT */

    sact.sa_handler = SIG_DFL;

    sigfillset (&sact.sa_mask);

    sact.sa_flags = 0;

    (void) sigaction (SIGABRT, &sact, NULL);

    /* Снова пошлем себе сигнал SIGABRT */

    raise (SIGABRT);

    /* Если сигнал снова не помог, попробуем еще одно средство завершения */

    _exit (127);

    }

    int main (void) {

    printf ("Перед вызовом abort()\n");

    abort ();

    printf ("После вызова abort()\n");

    return 0;

    }

    Листинг 8.20. Упрощенная реализация функции abort() как пример использования функций работы с сигналами.

    В качестве нюанса, характерного для работы с сигналами, отметим, что до первого обращения к raise() нельзя закрыть потоки (можно только вытолкнуть буфера), поскольку функция обработки сигнала SIGABRT, возможно, осуществляет вывод. Еще одним примером использования механизма сигналов может служить приведенная в пример 8.13 упрощенная реализация функции sleep(), предназначенной для "засыпания" на заданное число секунд. (Можно надеяться, что не описанные пока средства работы с временем интуитивно понятны.)

    #include

    #include

    #include

    #include

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

    /* Она ничего не делает, но игнорировать сигнал нельзя */

    static void signal_handler (int sig) {

    /* В демонстрационных целях распечатаем номер обрабатываемого сигнала */

    printf ("Принят сигнал %d\n", sig);

    }

    /* Функция для "засыпания" на заданное число секунд */

    /* Результат равен разности между заказанной и фактической */

    /* продолжительностью "сна" */

    unsigned int sleep (unsigned int seconds) {

    time_t before, after;

    unsigned int slept;

    sigset_t set, oset;

    struct sigaction act, oact;

    if (seconds == 0) {

    return 0;

    }

    /* Установим будильник на заданное время, */

    /* но перед этим блокируем сигнал SIGALRM */

    /* и зададим свою функцию обработки для него */

    if ((sigemptyset (&set) < 0) || (sigaddset (&set, SIGALRM) < 0) ||

    sigprocmask (SIG_BLOCK, &set, &oset)) {

    return seconds;

    }

    act.sa_handler = signal_handler;

    act.sa_flags = 0;

    act.sa_mask = oset;

    if (sigaction (SIGALRM, &act, &oact) < 0) {

    return seconds;

    }

    before = time ((time_t *) NULL);

    (void) alarm (seconds);

    /* Как атомарное действие восстановим старую маску сигналов */

    /* (в надежде, что она не блокирует SIGALRM) */

    /* и станем ждать доставки обрабатываемого сигнала */

    (void) sigsuspend (&oset);

    /* сигнал доставлен и обработан */

    after = time ((time_t *) NULL);

    /* Восстановим прежний способ обработки сигнала SIGALRM */

    (void) sigaction (SIGALRM, &oact, (struct sigaction *) NULL);

    /* Восстановим первоначальную маску сигналов */

    (void) sigprocmask (SIG_SETMASK, &oset, (sigset_t *) NULL);

    return ((slept = after - before) > seconds ? 0 : (seconds - slept));

    }

    int main (void) {

    struct sigaction act;

    /* В демонстрационных целях установим обработку прерывания с клавиатуры */

    act.sa_handler = signal_handler;

    (void) sigemptyset (&act.sa_mask);

    act.sa_flags = 0;

    (void) sigaction (SIGINT, &act, (struct sigaction *) NULL);

    printf ("Заснем на 10 секунд\n");

    printf ("Проснулись, не доспав %d секунд\n", sleep (10));

    return (0);

    }

    Листинг 8.21. Упрощенная реализация функции sleep() как пример использования механизма сигналов.

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

    Вызвать "недосыпание" приведенной программы можно, послав ей сигнал SIGALRM (например, посредством команды kill -s SIGALRM идентификатор_процесса) или SIGINT (путем нажатия на клавиатуре терминала комбинации клавиш CTRL+C).
      1. 1   2   3   4   5


    написать администратору сайта