Книга: Основы программирования в Linux
select
Разделы на этой странице:
select
Очень часто при разработке приложений Linux вам может понадобиться проверка состояния ряда вводов для того, чтобы определить следующее предпринимаемое действие. Например, программа обмена данными, такая как эмулятор терминала, нуждается в эффективном способе одновременного чтения с клавиатуры и с последовательного порта. В однопользовательской системе подойдет цикл "активного ожидания", многократно просматривающий ввод в поиске данных и читающий их, как только они появятся. Такое поведение очень расточительно в отношении времени ЦП.
Системный вызов select
позволяет программе ждать прибытия данных (или завершения вывода) одновременно на нескольких низкоуровневых файловых дескрипторах. Это означает, что программа эмулятора терминала может блокироваться до тех пор, пока у нее не появится работа. Аналогичным образом сервер может иметь дело с многочисленными клиентами, ожидая запросы одновременно на многих открытых сокетах.
Функция select
оперирует структурами данных fd_set
, представляющими собой множества открытых файловых дескрипторов. Для обработки этих множеств определен набор макросов:
#include <sys/types.h> #include <sys/time.h>
void FD_ZERO(fd_set *fdset);
void FD_CLR(int fd, fd_set *fdset);
void FD_SET(int fd, fd_set *fdset);
int FD_ISSET(int fd, fd_set *fdset);
Как и предполагается в соответствии с их именами, макрос FD_ZERO
инициализирует структуру fd_set
пустым множеством, FD_SET
и FD_CLR
задают и очищают элементы множества, соответствующего файловому дескриптору, переданному как параметр fd
, а макрос FD_ISSET
возвращает ненулевое значение, если файловый дескриптор, на который ссылается fd
, является элементом структуры fd_set
, на которую указывает параметр fdset
. Максимальное количество файловых дескрипторов в структуре типа fd_set
задается константой FD_SETDIZE
.
Функция select
может также использовать значение для времени ожидания, чтобы помешать бесконечной блокировке. Это значение задается с помощью структуры struct timeval
. Она определена в файле sys/time.h и содержит следующие элементы:
struct timeval {
time_t tv_sec; /* Секунды */
long tv_usec; /* Микросекунды */
}
Тип time_t
, определенный в файле sys/types.h, — целочисленный. Системный вызов select
объявляется следующим образом:
#include <sys/types.h>
#include <sys/time.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *errorfds, struct timeval *timeout);
Вызов select
позволяет проверить, не готов ли хотя бы один из множества файловых дескрипторов к чтению или записи, или находится ли в ожидании из-за состояния ошибки и может быть заблокирован до момента готовности одного из дескрипторов.
Аргумент nfds
задает количество проверяемых файловых дескрипторов, имеются в виду дескрипторы от 0 до nfds-1
. Каждое из трех множеств дескрипторов может оказаться пустым указателем, тогда связанный с ним тест не выполняется.
Функция select
вернет управление, если какой-либо из дескрипторов в множестве readfds
готов к чтению, какой-нибудь дескриптор из множества writefds
готов к записи или у одного из дескрипторов множества errorfd
есть состояние ошибки. Если ни одно из условий не соблюдается, select
вернет управление после промежутка времени, заданного timeout
. Если параметр timeout
— пустой указатель и нет активности на сокетах, вызов может быть заблокирован на неопределенное время.
Когда select
возвращает управление программе, множества дескрипторов будут модифицированы для того, чтобы указать на готовые к чтению или записи или имеющие ошибки дескрипторы. Для их проверки следует использовать макрос FD_ISSET
, позволяющий определить, какие дескрипторы требуют внимания. Можно изменить значение timeout для того, чтобы показать время, остающееся до следующего превышения времени ожидания, но такое поведение не задано стандартом X/Open. При превышении времени ожидания все множества дескрипторов будут очищены.
Вызов select возвращает общее количество дескрипторов в модифицированных множествах. В случае сбоя он вернет -1 и установит значение переменной errno
, описывающее ошибку. Возможные ошибки — EBADF
для неверных дескрипторов, EINTR
для возврата из-за прерывания и EINVAL
для некорректных значений параметров nfds
или timeout
.
Примечание
Несмотря на то, что Linux модифицирует структуру, на которую указывает timeout
, фиксируя оставшееся неиспользованное время, большинство версий UNIX этого не делают. Большая часть существующего программного кода, применяющего функцию select
, инициализирует структуру типа timeval
и затем продолжает использовать ее без обновления содержимого. В системе Linux этот код может выполняться некорректно, поскольку ОС Linux изменяет структуру timeval
при каждом истечении отведенного времени ожидания. Если вы пишете или переносите программный код, использующий функцию select
, следует учитывать эту разницу и всегда повторно инициализировать время ожидания. Имейте в виду, что оба подхода корректны, они просто разные!
Выполните упражнение 15.8.
Упражнение 15.8. Функция select
Далее для демонстрации применения функции select приведена программа select.c. Более сложный пример вы увидите чуть позже. Программа читает данные с клавиатуры (стандартный ввод — дескриптор 0) со временем ожидания 2,5 секунды. Данные читаются только тогда, когда ввод готов. Естественно расширить программу, включив в зависимости от характера приложения другие дескрипторы, такие как последовательные каналы (serial lines) и сокеты.
1. Начните как обычно с директив include
и объявлений, а затем инициализируйте inputs
для обработки ввода с клавиатуры:
#include <sys/types.h>
#include <sys/time.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdlib.h>
int main() {
char buffer[128];
int result, nread;
fd_set inputs, testfds;
struct timeval timeout;
FD_ZERO(&inputs);
FD_SET(0, &inputs);
2. Подождите ввод из файла stdin в течение максимум 2,5 секунд:
while(1) {
testfds = inputs;
timeout.tv_sec = 2;
timeout.tv_usec = 500000;
result = select(FD_SETSIZE, &testfds, (fd_set *)NULL,
(fd_set*)NULL, &timeout);
3. Спустя это время проверьте result
. Если ввода не было, программа выполнит цикл еще раз. Если в нем возникла ошибка, программа завершается:
switch(result) {
case 0:
printf("timeoutn");
break;
case -1:
perror("select");
exit(1);
4. Если во время ожидания у вас наблюдаются некоторые действия, связанные с файловым дескриптором, читайте ввод из stdin и выводите его при каждом получении символа EOL (конец строки), до нажатой комбинации клавиш <Ctrl>+<D>:
default:
if (FD_ISSET(0, &testfds)) {
ioctl(0, FIONREAD, &nread);
if (nread == 0) {
printf("keyboard donen");
exit(0);
}
nread = read(0, buffer, nread);
buffer[nread] = 0;
printf("read %d from keyboard: %s", nread, buffer);
}
break;
}
}
}
Во время выполнения эта программа каждые две с половиной секунды выводит строку timeout
. Если вы набираете данные на клавиатуре, она читает файл стандартного ввода и отображает то, что было набрано. В большинстве командных оболочек ввод направляется в программу при нажатии пользователем клавиши <Enter> (или <Return>) или клавиш управляющей последовательности, поэтому программа будет отображать ввод каждый раз, когда вы нажимаете клавишу <Enter>. Учтите, что сама клавиша <Enter> тоже читается и обрабатывается как любой другой символ (попробуйте выполнить ввод без нажатия клавиши, введя ряд символов, за которыми следует комбинация <Ctrl>+<D>).
$ ./select
timeout
hello
read 6 from keyboard: hello
fred
read 5 from keyboard: fred
timeout
^D
keyboard done
$
Как это работает
Программа применяет вызов select
для проверки состояния стандартного ввода. За счет корректировки значения времени ожидания программа каждые 2,5 секунды выводит сообщение об истечении времени ожидания. О нем свидетельствует возвращение 0 функцией select
. При достижении конца файла дескриптор стандартного ввода помечается флагом как готовый к вводу, но при этом нет символов, предназначенных для считывания.
- 1. Оператор Select – базовый оператор языка структурированных запросов
- 2.3.3 Selecting and Pasting
- Максимальное число дескрипторов для функции select
- Software Selection and Installation
- Window Selector
- Selecting backup devices and media
- Selecting Specific Protocols
- Selecting a Group on Which to Operate
- Selecting a backup utility
- Displaying Titles from a Selected Publisher
- Раздел Select
- Оператор SELECT