select()
и pselect()
системные вызовы изменяют свои аргументы ('fd_set *
'аргументы), таким образом, входное значение говорит систему, которую дескрипторы файлов проверить и возвращаемые значения говорят программисту, какие дескрипторы файлов в настоящее время применимы.
Если Вы собираетесь назвать их неоднократно для того же набора дескрипторов файлов, необходимо удостовериться, чтобы у Вас была новая копия дескрипторов для каждого вызова. Очевидный способ сделать, который должен использовать копию структуры:
fd_set ref_set_rd;
fd_set ref_set_wr;
fd_set ref_set_er;
...
...code to set the reference fd_set_xx values...
...
while (!done)
{
fd_set act_set_rd = ref_set_rd;
fd_set act_set_wr = ref_set_wr;
fd_set act_set_er = ref_set_er;
int bits_set = select(max_fd, &act_set_rd, &act_set_wr,
&act_set_er, &timeout);
if (bits_set > 0)
{
...process the output values of act_set_xx...
}
}
(Отредактированный для удаления неправильный struct fd_set
ссылки - как указано 'R..'.)
Мой вопрос:
fd_set
значения как показано?Я заинтересован, чтобы не быть скрытое выделение памяти или что-либо неожиданное как этот. (Существуют макросы/функции FD_SET (), FD_CLR (), FD_ZERO () и FD_ISSET () для маскирования внутренностей из приложения.)
Я вижу, что MacOS X (Darwin) безопасна; другие основанные на BSD системы, вероятно, будут безопасны, поэтому. Можно помочь путем документирования других систем, которые Вы знаете, безопасны в Ваших ответах.
(У меня действительно есть незначительные опасения по поводу как хорошо fd_set
работал бы больше чем с 8 192 открытыми дескрипторами файлов - максимальное количество по умолчанию открытых файлов - только 256, но максимальное количество 'неограниченно'. Кроме того, так как структуры составляют 1 КБ, код копирования не ужасно эффективен, но затем пробежка списка дескрипторов файлов для воссоздания маски ввода на каждом цикле не обязательно эффективна также. Возможно, Вы не можете сделать select()
когда у Вас есть это много открытых дескрипторов файлов, хотя это - когда Вам, скорее всего, будет нужна функциональность.)
Существует связанное ТАК вопрос - спрашивающий о 'опросе () по сравнению с выбором ()', который обращается к другому набору проблем от этого вопроса.
Обратите внимание, что на MacOS X - и по-видимому BSD в более общем плане - существует FD_COPY()
макрос или функция, с эффективным прототипом:
extern void FD_COPY(const restrict fd_set *from, restrict fd_set *to);
.Это могло бы стоить эмулировать на платформах, где это не уже доступно.
Поскольку struct fd_set
- это обычная структура Си, это всегда должно быть нормально. Мне лично не нравится выполнять копирование структуры через оператор =
, поскольку я работал на множестве платформ, не имеющих доступа к обычному набору интринсиков компилятора. Использование memcpy()
в явном виде, а не вставка компилятором вызова функции - лучший выход, по моему мнению.
Из спецификации C, раздел 6.5.16.1 Простое присваивание (здесь отредактировано для краткости):
Должно выполняться одно из следующих условий:
...
- левый операнд имеет квалифицированную или неквалифицированную версию типа структуры или объединения, совместимого с типом правого операнда;
...
В простом присваивании (=) значение правого операнда преобразуется к типу выражения присваивания и заменяет значение, хранящееся в объекте, обозначенном левым операндом.
Если значение, хранящееся в объекте, считывается из другого объекта, который каким-либо образом перекрывает хранилище первого объекта, то перекрытие должно быть точным, и оба объекта должны иметь квалифицированные или неквалифицированные версии совместимого типа; в противном случае поведение не определено.
Вот так, пока struct fd_set
является обычной структурой struct
языка C, вам гарантирован успех. Однако, это зависит от того, выдает ли ваш компилятор какой-то код для этого или полагается на memcpy()
, который он использует для присвоения структуры. Если ваша платформа по какой-то причине не может компоновать внутренние библиотеки компилятора, это может не сработать.
Если у вас больше открытых файловых дескрипторов, чем помещается в struct fd_set
, придется прибегнуть к некоторым хитростям. На странице linux man page сказано:
fd_set
- это буфер фиксированного размера. ВыполнениеFD_CLR()
илиFD_SET()
со значениемfd
, которое является отрицательным или равно или большеFD_SETSIZE
, приведет к неопределенному поведению. Более того, POSIX требует, чтобыfd
был действительным дескриптором файла.
Как сказано ниже, возможно, не стоит тратить усилия на доказательство того, что ваш код безопасен на всех системах. FD_COPY()
предусмотрена именно для такого использования и, надо полагать, всегда гарантирована:
FD_COPY(&fdset_orig, &fdset_copy)
заменяет уже выделенный&fdset_copy
набор файловых дескрипторов копией&fdset_orig
.
У меня недостаточно комментариев, чтобы добавить это в качестве комментария к ответу caf, но есть библиотеки для абстрагирования по нестандартным интерфейсам, например epoll ()
и kqueue
. Libevent - это одно, а libev - другое. Я думаю, что у GLib также есть тот, который связан с его основным циклом.
Вы правы, что POSIX не гарантирует, что копирование fd_set
должно «работать». Я лично не знаю, где бы это не было, но я никогда не проводил эксперимент.
Вы можете использовать альтернативу poll ()
(которая также является POSIX). Он работает аналогично select ()
, за исключением того, что параметр ввода / вывода не является непрозрачным (и не содержит указателей, поэтому будет работать голый memcpy
), а его design также полностью устраняет необходимость делать копию структуры «запрошенные файловые дескрипторы» (поскольку «запрошенные события» и «возвращенные события» хранятся в разных полях).
Вы также правы, полагая, что select ()
(и poll ()
) не особенно хорошо масштабируются для большого количества файловых дескрипторов - это потому, что каждый раз функция возвращает, вы должны пройти через каждый дескриптор файла, чтобы проверить, была ли в нем активность. Решением этой проблемы являются различные нестандартные интерфейсы (например, Linux epoll ()
, FreeBSD kqueue
), которые вам, возможно, придется изучить, если вы обнаружите, что у вас проблемы с задержкой.
Я провел небольшое исследование MacOS X, Linux, AIX, Solaris и HP-UX и получил некоторые интересные результаты. Я использовал следующую программу:
#if __STDC_VERSION__ >= 199901L
#define _XOPEN_SOURCE 600
#else
#define _XOPEN_SOURCE 500
#endif /* __STDC_VERSION__ */
#ifdef SET_FD_SETSIZE
#define FD_SETSIZE SET_FD_SETSIZE
#endif
#ifdef USE_SYS_TIME_H
#include <sys/time.h>
#else
#include <sys/select.h>
#endif /* USE_SYS_TIME_H */
#include <stdio.h>
int main(void)
{
printf("FD_SETSIZE = %d; sizeof(fd_set) = %d\n", (int)FD_SETSIZE, (int)sizeof(fd_set));
return 0;
}
Она была скомпилирована дважды на каждой платформе:
cc -o select select.c
cc -o select -DSET_FD_SETSIZE=16384
(И на одной платформе, HP-UX 11.11, мне пришлось добавить -DUSE_SYS_TIME_H, чтобы заставить вещи вообще компилироваться.) Я отдельно сделал визуальная проверка FD_COPY - казалось, только MacOS X включает его, и это нужно было активировать, убедившись, что _POSIX_C_SOURCE
не был определен, или определив _DARWIN_C_SOURCE
.
заголовок - используйте
вместо
_DARWIN_C_SOURCE
Очевидно, тривиальная модификация программы позволяет автоматически проверять FD_COPY:
#ifdef FD_COPY
printf("FD_COPY is a macro\n");
#endif
Что не обязательно является тривиальным, так это выяснить, как обеспечить его доступность; вы в конечном итоге выполняете сканирование вручную и выясняете, как его запустить.
На всех этих машинах похоже, что fd_set
может быть скопирован копией структуры без риска неопределенного поведения.
Прежде всего, отсутствует структура struct fd_set
. Он просто называется fd_set
. Однако POSIX требует, чтобы это был структурный тип, поэтому копирование четко определено.
Во-вторых, в стандарте C нет способа, в котором объект fd_set
мог бы содержать динамически выделяемую память, поскольку нет необходимости использовать какую-либо функцию / макрос для ее освобождения перед возвратом. Даже если у компилятора есть alloca
(расширение pre-vla для распределения на основе стека), fd_set
не может использовать память, выделенную в стеке, потому что программа может передать указатель на fd_set
другой функции, которая использует FD_SET
и т. Д., И выделенная память перестает быть действительной, как только она возвращается вызывающей стороне. Только если компилятор C предлагает какое-то расширение для деструкторов, fd_set
может использовать динамическое размещение.
В заключение, кажется безопасным просто назначить объекты / memcpy
fd_set
, но для уверенности я бы сделал что-то вроде:
#ifndef FD_COPY
#define FD_COPY(dest,src) memcpy((dest),(src),sizeof *(dest))
#endif
или, альтернативно, просто:
#ifndef FD_COPY
#define FD_COPY(dest,src) (*(dest)=*(src))
#endif
Затем вы будете использовать предоставленный системой макрос FD_COPY
, если он существует, и только вернетесь к теоретически потенциально опасной версии, если она отсутствует.