Неожиданное поведение функции select
- 06.07.12, 21:33
В Linux
man 2 select
НАЗВАНИЕ
select, pselect, FD_CLR, FD_ISSET, FD_SET, FD_ZERO - синхронное
мультиплексирование ввода-вывода
Во FreeBSD - есть еще такая пометка: "The default size of FD_SETSIZE is currently 1024. In order to accommodate programs which might potentially use a larger number of open files with select(), it is possible to increase this size by having the program define FD_SETSIZE before the inclusion of any header which includes <sys/types.h>." (Что в переводе означает что лимит FD_SET 1024 и чтобы его увеличить нужно переопределить дефайн...
Сейчас select редко где встретишь в проекте, а вот раньше она встречалась сплошь и рядом.
Одна из самых общераспространенных схем использования select:
struct timeval null_time;
null_time.tv_sec = 2;
null_time.tv_usec = 0;
fd_set inSet;
FD_ZERO(&inSet);
FD_SET(descr, &inSet);
int res = select(descr+1, &inSet, NULL, NULL, &null_time);
Данный фрагмент кода ожидает когда из сокета можно будет читать данные с таймаутом 2 секунды
И все у нас будет замечательно, пока в один прекрасный момент в нашей программе по какой-то причине не появится 1024+ открытых файлов и значение descr станет больше 1024. И в этот чудесный день функция, как ожидается не вернет -1 (ошибочное значение), а замечательно потрет память за переменной fd_set inSet, что приведет к очень сложно отлаживаемым багам.
Вот пример кода который демонстрирует затирание:
#include <sys/time.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
int descr;
for (int i = 0; i < 1024; i++)
{
descr = open("/dev/null", O_RDONLY);
if (descr < 0)
{
printf("Can't create descr\n");
return -1;
}
}
struct timeval null_time;
null_time.tv_sec = 2;
null_time.tv_usec = 0;
fd_set inSet;
char pad[] = "1234567890abcdefg";
printf("pad before select [%s]\n", pad);
FD_ZERO(&inSet);
FD_SET(descr, &inSet);
int res = select(descr+1, &inSet, NULL, NULL, &null_time);
printf("Pad after select [%s], res=%d\n", pad, res);
}
$ g++ testselect.cpp
$ ./a.out
Pad bafore select [1234567890abcdefg]
Pad after [], res=1
Для избежания затирания я рекомендую использовать poll, для большого числа соединений - epoll. Или в крайнем случае сделать проверку значения descr перед выполнением select
0