Есть ли какие-либо платформы, где использование копии структуры на fd_set (для выбора () или pselect ()) вызывает проблемы?

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);.

Это могло бы стоить эмулировать на платформах, где это не уже доступно.

16
задан Community 23 May 2017 в 12:00
поделиться

5 ответов

Поскольку 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.

9
ответ дан 30 November 2019 в 22:02
поделиться

У меня недостаточно комментариев, чтобы добавить это в качестве комментария к ответу caf, но есть библиотеки для абстрагирования по нестандартным интерфейсам, например epoll () и kqueue . Libevent - это одно, а libev - другое. Я думаю, что у GLib также есть тот, который связан с его основным циклом.

1
ответ дан 30 November 2019 в 22:02
поделиться

Вы правы, что POSIX не гарантирует, что копирование fd_set должно «работать». Я лично не знаю, где бы это не было, но я никогда не проводил эксперимент.

Вы можете использовать альтернативу poll () (которая также является POSIX). Он работает аналогично select () , за исключением того, что параметр ввода / вывода не является непрозрачным (и не содержит указателей, поэтому будет работать голый memcpy ), а его design также полностью устраняет необходимость делать копию структуры «запрошенные файловые дескрипторы» (поскольку «запрошенные события» и «возвращенные события» хранятся в разных полях).

Вы также правы, полагая, что select () poll () ) не особенно хорошо масштабируются для большого количества файловых дескрипторов - это потому, что каждый раз функция возвращает, вы должны пройти через каждый дескриптор файла, чтобы проверить, была ли в нем активность. Решением этой проблемы являются различные нестандартные интерфейсы (например, Linux epoll () , FreeBSD kqueue ), которые вам, возможно, придется изучить, если вы обнаружите, что у вас проблемы с задержкой.

3
ответ дан 30 November 2019 в 22:02
поделиться

Я провел небольшое исследование 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 .

AIX 5.3

  • По умолчанию FD_SETSIZE - 65536
  • Размер параметра FD_SETSIZE можно изменить
  • Нет FD_COPY

HP-UX 11.11

  • Нет заголовок - используйте вместо
  • По умолчанию FD_SETSIZE - 2048
  • Параметр FD_SETSIZE может быть изменен
  • Нет FD_COPY

HP-UX 11.23

  • Имеет
  • По умолчанию FD_SETSIZE - 2048
  • Параметр FD_SETSIZE может быть изменен
  • Нет FD_COPY

Linux (ядро 2.6.9, glibc 2.3.4)

  • По умолчанию FD_SETSIZE равно 1024
  • Параметр FD_SETSIZE не может изменить размер
  • Нет FD_COPY

MacOS X 10.6.2

  • По умолчанию FD_SETSIZE - 1024
  • Параметр FD_SETSIZE может быть изменен
  • FD_COPY определяется, если не требуется строгое соответствие POSIX или если указано _DARWIN_C_SOURCE

Solaris 10 (SPARC)

  • По умолчанию FD_SETSIZE составляет 1024 для 32-битных, 65536 для 64-битных
  • Размер параметра FD_SETSIZE может быть изменен
  • Нет FD_COPY

Очевидно, тривиальная модификация программы позволяет автоматически проверять FD_COPY:

#ifdef FD_COPY
    printf("FD_COPY is a macro\n");
#endif

Что не обязательно является тривиальным, так это выяснить, как обеспечить его доступность; вы в конечном итоге выполняете сканирование вручную и выясняете, как его запустить.

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

2
ответ дан 30 November 2019 в 22:02
поделиться

Прежде всего, отсутствует структура 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 , если он существует, и только вернетесь к теоретически потенциально опасной версии, если она отсутствует.

7
ответ дан 30 November 2019 в 22:02
поделиться
Другие вопросы по тегам:

Похожие вопросы: