вторник, 7 декабря 2010 г.

6.4. Асинхронное сообщение

6/4 Асинхронное сообщение

Хотя сочетание блокирующих и неблокирующих операций и метода select большую часть времени является достаточным для запросов к устройству, в некоторых ситуациях управление по методикам, которые мы видели до сих пор, не эффективно.

Давайте представим себе процесс, который выполняет продолжительный вычислительный цикл с низким приоритетом, но нуждается в обработке входящих данных так быстро, как это возможно. Если этот процесс реагирует на новые наблюдения, получаемые от какого-то периферийного устройства сбора данных, хотелось бы знать сразу, когда появляются новые данные. Это приложение может быть написано так, чтобы регулярно вызывать poll для проверки данных, однако, во многих ситуациях есть лучший путь. Разрешая асинхронное уведомление, это приложение может получить сигнал всякий раз, когда данные становятся доступными и не должно заниматься опросом.

Пользовательским программам необходимо выполнить два шага, чтобы разрешить асинхронное уведомление от входного файла. Во-первых, они определяют процесс как "владельца" этого файла. Когда процесс вызывается командой F_SETOWN, используя системный вызов fcntl, process ID владельца процесса сохраняется для последующего использования в filp->f_owner. Этот шаг необходим для ядра, чтобы знать, кого уведомлять. Для того, чтобы действительно разрешить асинхронное уведомление, пользовательским программам необходимо установить в устройстве флаг FASYNC с помощью команды F_SETFL fcntl.

После выполнения этих двух вызовов входной файл может запросить доставку сигнала SIGIO при получении новой информации. Этот сигнал передаётся в процесс (или группу процессов, если значение отрицательное), хранящийся в filp->f_owner.

Например, следующие строки кода пользовательской программы разрешают асинхронное уведомление текущего процесса для входного файла stdin:

signal(SIGIO, &input_handler); /* пустышка; лучше использовать sigaction( ) */
fcntl(STDIN_FILENO, F_SETOWN, getpid( ));
oflags = fcntl(STDIN_FILENO, F_GETFL);
fcntl(STDIN_FILENO, F_SETFL, oflags | FASYNC);

Программа в исходниках, названная asynctest, является простой программой, которая, как показано, считывает stdin. Она может быть использована для тестирования асинхронных возможностей scullpipe. Программа похожа на cat, но не прекращается при достижении конца файла; она реагирует только на ввод, но не на отсутствие ввода.

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

Осталась одна проблема с уведомлением ввода. Когда процесс получает SIGIO, он не знает, какой входной файл предлагает новый ввод. Если более чем один файл имеет возможность асинхронно уведомить об этом процесс, ожидающий ввод, приложение должно по-прежнему прибегать к poll или select, чтобы выяснить, что произошло.
С точки зрения драйвера
Более актуальной темой для нас является то, как асинхронную сигнализацию может реализовать драйвер устройства. В следующем списке подробности последовательности операций с точки зрения ядра:

1.При вызове F_SETOWN ничего не происходит, кроме того, что filp->f_owner присваивается значение.
2.Когда выполняется F_SETFL, чтобы включить FASYNC, вызывается метод драйвера fasync. Этот метод вызывается всякий раз, когда в filp->f_flags меняется значение FASYNC для уведомления драйвера об изменении, чтобы он мог реагировать должным образом. При открытии файла флаг по умолчанию очищается. Мы будем рассматривать стандартную реализацию метода драйвера далее в этом разделе.
3.При поступлении данных все процессы, зарегистрированные для асинхронного уведомления, должны отправить сигнал SIGIO.

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

Общая реализация, предлагаемая Linux, основана на одной структуре данных и двух функциях (которые называются вторым и третьим шагами, описанными ранее). Заголовком, который декларирует соответствующий материал, является (здесь ничего нового) и структура данных, названная struct fasync_struct. Как и с очередью ожидания, нам необходимо вставить указатель на структуру в зависящую от устройства структуру данных.

Две функции, которые вызывает драйвер, соответствуют следующим прототипам:

int fasync_helper(int fd, struct file *filp, int mode, struct fasync_struct **fa);
void kill_fasync(struct fasync_struct **fa, int sig, int band);

fasync_helper вызывается, чтобы добавить или удалить записи из списка заинтересованных процессов, когда для открытого файла изменяется флаг FASYNC. Все эти аргументы, за исключением последнего, предоставляются методом fasync и могут быть переданы напрямую через него. kill_fasync используется, чтобы при поступлении данных просигнализировать заинтересованным процессам. Её аргументами являются сигнал для отправки (обычно SIGIO) и диапазон, который почти всегда POLL_IN (* POLL_IN является символом, используемым в коде асинхронного уведомления; это эквивалентно POLLIN | POLLRDNORM.) (но это может быть использовано для передачи "срочно" или о наличии данных во вспомогательном канале в сетевом коде). Вот как реализуется метод fasync в scullpipe:

static int scull_p_fasync(int fd, struct file *filp, int mode)
{
    struct scull_pipe *dev = filp->private_data;

    return fasync_helper(fd, filp, mode, &dev->async_queue);
}

Понятно, что вся работа выполняется fasync_helper. Однако, было бы невозможно реализовать функциональность без метода в драйвере, так как вспомогательной функции требуется доступ к правильному указателю на структуру fasync_struct * (здесь &dev->async_queue) и только драйвер может предоставить такую информацию.

Затем при поступлении данных, до посылки сигнала асинхронным читателям, должна быть выполнена следующая команда. Поскольку новые данные для читателя scullpipe порождаются процессом, делающим запись, эта команда выполняется в методе write драйвера scullpipe.

if (dev->async_queue)
    kill_fasync(&dev->async_queue, SIGIO, POLL_IN);

Заметим, что некоторые устройства также реализуют асинхронное уведомление, чтобы сообщить, когда устройство может быть доступно для записи; конечно, в этом случае kill_fasync должна вызываться с режимом POLL_OUT.

Может показаться, что мы всё доделали, но всё ещё отсутствует одна вещь. Мы должны вызвать наш метод fasync, когда файл закрыт для удаления этого файла из списка активных асинхронных читателей. Хотя этот вызов требуется, только если filp->f_flags имеет установленный FASYNC, вызов функции в любом случае не повредит и является обычной реализацией. Следующие строчки, например, являются частью метода release в scullpipe:

/* удалить этот filp из асинхронно уведомляемых filp-ов */
scull_p_fasync(-1, filp, 0);

Структура данных, лежащая в основе асинхронного уведомления, почти идентична структуре wait_queue, потому что обе эти ситуации связаны с ожиданием события. Разница в том, что вместо структуры task_struct используется структура file. Затем, чтобы послать сигнал процессу, для получения f_owner используется структура file из очереди.

Комментариев нет:

Отправить комментарий

Примечание. Отправлять комментарии могут только участники этого блога.