Асинхронное оповещение о завершении операции

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

Поле aio_sigevent имеет тип struct sigevent. Эта структура определена в <signal.h> и содержит следующие поля:

int sigev_notify – режим нотификации. Допустимые значения – SIGEV_NONE (не посылать подтверждения), SIGEV_SIGNAL (генерировать сигнал при завершении запроса) и SIGEV_THREAD (при завершении запроса запускать указанную функцию в отдельной нити). Solaris 10 также поддерживает тип оповещения SIGEV_PORT, который рассматривается в приложении к этой лекции.

int sigev_signo – номер сигнала, который будет сгенерирован при использовании SIGEV_SIGNAL.

union sigval sigev_value – параметр, который будет передан обработчику сигнала или функции обработки. При использовании для асинхронного ввода/вывода это обычно указатель на запрос. При использовании SIGEV_PORT это должен быть указатель на структуру port_event_t, содержащую номер порта и, возможно, дополнительные данные.

void (*sigev_notify_function)(union sigval) – функция, которая будет вызвана при использовании SIGEV_THREAD.

pthread_attr_t *sigev_notify_attributes – атрибуты нити, в которой будет запущена sigev_notify_function при использовании SIGEV_THREAD.

Далеко не все реализации libaio поддерживают оповещение SIGEV_THREAD. Некоторые Unix-системы используют вместо него нестандартное оповещение SIGEV_CALLBACK. Далее в этой лекции мы будем обсуждать только оповещение сигналом.

В качестве номера сигнала некоторые приложения используют SIGIO или SIGPOLL (в Unix SVR4 это один и тот же сигнал). Часто используют также SIGUSR1 или SIGUSR2; это удобно потому, что гарантирует, что аналогичный сигнал не возникнет по другой причине. В приложениях реального времени используются также номера сигналов в диапазоне от SIGRTMIN до SIGRTMAX. Некоторые реализации выделяют для этой цели специальный номер сигнала SIGAIO или SIGASYNCIO, но в Solaris 10 такого сигнала нет.

Разумеется, перед тем, как исполнять асинхронные запросы с оповещением сигналом, следует установить обработчик этого сигнала. Для оповещения необходимо использовать сигналы, обрабатываемые в режиме SA_SIGINFO. Установить такой обработчик при помощи системных вызовов signal(2) и sigset(2) невозможно, необходимо использовать sigaction(2). Установка обработчиков при помощи sigaction рассматривается в приложении 2 к этой лекции.

Обработка сигнала в режиме SA_SIGINFO имеет два свойства, каждое из которых полезно для наших целей. Во первых, обработчики таких сигналов имеют три параметра, в отличие от единственного параметра у традиционных обработчиков. Первый параметр имеет тип int и соответствует номеру сигнала, второй параметр имеет тип siginfo_t *, генерируется системой и содержит ряд интересных полей. Нас в данном случае больше всего интересует поле этой структуры si_value. Как раз в этом поле нам передается значение aio_sigevent.sigev_value, которое мы создали при настройке структуры aiocb.

Приложение 1. Порты Solaris

В Solaris 10 введен новый API, который может быть полезен и в качестве замены select/poll, и для управления запросами асинхронного ввода/вывода. В действительности, select и poll в Solaris 10 реализуются с использованием портов.

Порт представляет собой объект, идентифицируемый файловым дескриптором. Порт создается функцией port_create(3C) и уничтожается системным вызовом close(2). C уже созданным портом можно связывать объекты, способные генерировать события. В настоящее время поддерживаются следующие типы источников событий:

PORT_SOURCE_AIO – запросы асинхронного ввода-вывода. Событием считается завершение запроса.

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

PORT_SOURCE_TIMER – таймеры.

PORT_SOURCE_USER – события, генерируемые пользователем при помощи функции port_send(3C).

PORT_SOURCE_ALERT – события, генерируемые пользователем при помощи функции port_send(3C).

Связывание асинхронного запроса с портом происходит в момент создания запроса, установкой поля aio_sigevent.sigev_notify=SIGEV_PORT.

Связывание файлового дескриптора с портом производится функцией port_associate(3C). Судя по количеству и типам параметров, эта функция должна допускать связывание с портом различных объектов, но в Solaris 10 поддерживается единственный тип источника - PORT_SOURCE_FD.

С одним портом можно связывать разнородные источники событий.

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

Порты представляют собой новый API. В страницах системного руководства Solaris 10 функции этого API отмечены как evolving (развивающиеся).

В настоящее время важным недостатком портов является то, что примитивы синхронизации POSIX Thread API нельзя использовать в качестве источников событий наравне с файловыми дескрипторами и таймерами.

Приложение 2. Установка обработчиков сигналов при помощи sigaction(2)

Системный вызов sigaction(2) рассматривается в разделе «Сигналы. Он предоставляет возможность регистрации обработчиков сигналов с дополнительными параметрами. Для этого необходимо использовать поле sa_sigaction. В качестве параметра, sigaction(2) получает

struct sigaction со следующими полями:

void (*sa_handler)(int); - Адрес традиционного обработчика сигнала, SIG_IGN или SIG_DFL

void (*sa_sigaction)(int, siginfo_t *, void *) – Адрес обработчика сигнала в режиме SA_SIGINFO. Реализации могут совмещать это поле с полем sa_handler, поэтому не следует пытаться установить sa_handler и sa_sigaction в одной структуре.

sigset_t sa_mask - Маска сигналов, которые должны быть заблокированы, когда вызывается функция обработки сигнала.

int sa_flags - Флаги, управляющие доставкой сигнала.

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

Поле sa_flags в struct sigaction формируется побитовым ИЛИ следующих

значений:

SA_ONSTACK - Используется для бработки сигналов на альтернативном сигнальном стеке.

SA_RESETHAND - Во время исполнения функции обработки сбрасывает реакцию на сигнал к SIG_DFL; обрабатываемый сигнал при этом не блокируется.

SA_NODEFER - Во время обработки сигнала сигнал не блокируется.

SA_RESTART - Системные вызовы, которые будут прерваны исполнением функции обработки, автоматически перезапускаются.

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


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: