Олег Цилюрик - QNX/UNIX: Анатомия параллелизма
Cycle # 11
...
2. Запрет прерывания выполнения программы с терминала. Для этого достаточно заменить строку инициализации структуры sigaction на:
static struct sigaction act = { SIG_IGN, 0, (sigset_t)0 };
Можно проигнорировать сразу несколько сигналов (прерывающих выполнение программы с клавиатуры):
sigaction(SIGINT, &act, NULL );
sigaction(SIGQUIT, &act, NULL);
Далее остановимся еще на одном вызове API-сигналов, который широко используется в этой и последующих моделях обработки (сигналы реального времени, реакция в потоках):
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
Этот вызов позволяет прочитать текущее значение (если set установлено в NULL, то параметр how игнорируется) или переустановить сигнальную маску для текущего потока. Параметры вызова:
• set — это то значение, в соответствии с которым корректируется сигнальная маска процесса;
• how — указывает, какое именно действие переустановки сигнальной маски требуется осуществить:
• SIG_BLOCK — добавить сигналы, указанные в set к маске процесса (заблокировать реакцию на эти сигналы);
• SIG_UNBLOCK — сбросить указанные set сигналы в сигнальной маске;
• SIG_SETMASK — переустановить сигнальную маску процесса на значение, указанное в set.
• oset — значение, в котором будет сохранено значение маски, предшествующее вызову (старое значение).
Как и большинство сигнальных функций, данная функция возвращает нулевое значение в результате успешного выполнения и -1 в случае неудачи, при этом код ошибки устанавливается в errno.
Именно эта функция снимает одно из самых существенных ограничений, свойственных модели «ненадежных сигналов», — позволяет заблокировать реакцию на сигналы при выполнении критических участков кода и восстановить ее при завершении выполнения этих участков.
Модель сигналов реального времени
Сигналы реального времени были добавлены в POSIX относительно недавно (1996 г.). Эта новая модель в различных ОС UNIX реализуется с разной степенью полноты и с отклонениями от спецификаций, и QNX не исключение. Модель еще до конца не отработана, поэтому возможны сюрпризы (и сейчас они будут).
Модель сигналов реального времени, которую специфицирует POSIX, устанавливается флагом SA_SIGINFO (который уже упоминался выше) при вызове sigaction(). В нижеследующем перечислении того, что предусматривает эта модель, мы излагаем в первую очередь качественную картину происходящего, предлагаемую POSIX, кое-где уточняя ее конкретными данными реализации QNX (артефакты в поведении QNX будут отдельно отмечены позже):
1. Сигналы, называемые сигналами реального времени, могут принимать значения между SIGRTMIN и SIGRTMAX. Количество таких сигналов определяется в системе константой RTSIG_MAX, которая должна быть не менее 8 (POSIX). В QNX: SIGRTMIN = 41, SIGRTMAX = 56.
2. Обработка сигналов реального времени строится на основе очереди. Если сигнал порожден N раз, то он должен быть и N раз получен адресатом (в описываемых ранее моделях это не так, в них процесс получает только единичный экземпляр сигнала). Повторные экземпляры одного и того же сигнала в модели реального времени доставляются обработчику в порядке FIFO.
3. Помимо 8-битного кода с сигналом реального времени ассоциируется 32-битное значение (si_value, мы им займемся позже), заполняемое отправителем и доставляемое получателю (что позволяет «различать» экземпляры сигналов в очереди, о которой говорилось выше).
4. Для работы с сигналами реального времени добавлено несколько новых функций. В частности, в этой модели для отправки сигнала некоторому процессу используется sigqueue() вместо kill().
Эти два вызова определяются очень близкими формами:
int kill(pid_t pid, int signo);
int sigqueue(pid_t pid, int signo, const union sigval value);
ПримечаниеКак мы вскоре увидим, эти две синтаксические формы одного и того же вызова отличаются лишь тем, помещают ли они в сигнал указанное значение или оставляют его нулевым. Если процесс устанавливает обработку сигнала на основании очереди, он будет получать почти одинаковым образом сигналы, посланные обоими вызовами. Разница «почти» состоит в том, что получатель на основании анализа поля si_code в siginfo_t в состоянии отличить, каким вызовом ему был послан сигнал.
ПримечаниеПри ошибке выполнения sigqueue() (код возврата -1) могут устанавливаться (в errno) следующие коды ошибок:
• EAGAIN — недостаточно ресурсов для помещения сигнала в очередь;
• EINVAL — недопустимое значение signo или неподдерживаемый сигнал;
• ENOSYS — вызов sigqueue() не поддерживается реализацией (возможно, версией);
• EPERM — у процесса недостаточно привилегий для посылки сигнала принимающему процессу;
• ESRCH — несуществующий PID процесса получателя.
Последний случай особо интересен, так как при указании в качестве номера сигнала signo = 0 реальная посылка сигнала не производится, но устанавливается код ошибки. Это простейший и эффективный способ выяснить, выполняется ли в системе процесс с заданным PID.
5. Когда в очередь помещаются различные не заблокированные процессом (потоком) сигналы в диапазоне SIGRTMIN…SIGRTMAX, то сигналы с меньшими номерами доставляются обработчику из FIFO-очереди раньше сигналов с большими номерами (то есть сигналы с меньшими номерами имеют более высокий приоритет).
6. Обработчик для сигналов реального времени устанавливается с флагом SA_SIGINFO, а функция обработчика объявляется теперь с другим прототипом:
void func(int signo, siginfo_t* info, void* context);
Обработчик имеет больше параметров и получает больше информации. POSIX требует, чтобы тип siginfo_t содержал как минимум:
typedef struct {
int si_signo;
int si_code;
union sigval si_value; /* целое или указатель от отправителя */
} siginfo_t;
В QNX sigval определяется так (подобное определение дают и другие ОС UNIX):
union sigval {
int sival_int;
void *sival_ptr;
};
Это 32-битное значение предназначено для посылки совместно с сигналом данных для получателя, которые, как видно из синтаксиса определения sigval, могут быть целочисленным значением или указателем неспецифицированного типа.
7. Поле si_code типа siginfo_t, передаваемое получателю, определяет природу возбуждения сигнала:
• SI_ASINCIO — сигнал порожден завершением операций асинхронного ввода/вывода, запущенного одной из функций POSIX aio_*();
• SI_MESGQ — сигнал возбуждается при помещении сообщения в пустую очередь сообщений UNIX;
• SI_QUEUE — сигнал был отправлен функцией sigqueue() (в этом разделе нас интересуют только такие сигналы);
• SI_TIMER — сигнал был порожден по истечении установленного времени интервального таймера;
• SI_USER — сигнал был отправлен функцией kill().
8. Допускается, что при возбуждении сигнала еще каким-либо механизмом (сверх перечисленных, что может определяться специфическими особенностями ОС) значение si_code может отличаться от перечисленных. Однако значение поля si_value считается актуальным только в тех случаях, когда si_code имеет одно из значений: SI_ASINCIO, SI_MESGQ, SI_QUEUE, SI_TIMER.
9. Согласно POSIX сигналы, обработчики для которых также устанавливаются с флагом SA_SIGINFO, но не входящие в диапазон сигналов реального времени, например стандартные сигналы UNIX, могут обрабатываться как на основе помещения их в очередь, так и без ее использования; выбор оставляется на усмотрение разработчика ОС.
Мы перечислили основные требования POSIX к модели обработки сигналов реального времени. Дополнения, отличия и специфические структуры данных QNX будут рассмотрены немного позже.
Весьма доходчивый пример для проверки и иллюстрации обработки сигналов реального времени приведен У. Стивенсом [2]. Мы же построим приложение, реализующее его основную идею:[33]
Приоритеты сигналов реального времени#include <stdlib.h>
#include <stdio.h>
#include <iostream.h>
#include <signal.h>
#include <unistd.h>
static void handler(int signo, siginfo_t* info, void* context) {
cout << "received signal " << signo << " code = " << info->si_code <<
" val = " << info->si_value.sival_int << endl;
}
int main(int argc, char *argv[]) {
cout << "signal SIGRTMIN=" << (int)SIGRTMIN
<< " - signal SIGRTMAX=" << (int)SIGRTMAX << endl;
int opt, val, beg = SIGRTMAX, num = 3,