Подтвердить что ты не робот

Существуют ли какие-либо платформы, где использование копии структуры на fd_set (для select() или 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 (Дарвин) безопасен; поэтому другие BSD-системы, вероятно, будут безопасными. Вы можете помочь, зарегистрировав другие системы, которые, как вы знаете, безопасны в ваших ответах.

(У меня есть незначительная озабоченность по поводу того, насколько хорошо fd_set будет работать с более чем 8192 дескрипторами открытых файлов - максимальное количество открытых файлов по умолчанию составляет всего 256, но максимальное число "неограничено". Кроме того, поскольку структуры составляют 1 КБ, код копирования не ужасно эффективен, но затем выполнение списка дескрипторов файлов для воссоздания маски ввода в каждом цикле также не обязательно эффективно. Возможно, вы не можете сделать select(), когда у вас есть много дескрипторы файлов открываются, хотя именно тогда вам, скорее всего, понадобятся функции.)


Есть связанный с этим вопрос SO - вопрос о 'poll() vs select()', в котором рассматривается другой набор проблем из этого вопроса.


Обратите внимание, что на MacOS X - и, предположительно, BSD в целом - есть макрос или функция FD_COPY() с эффективным прототипом:

  • extern void FD_COPY(const restrict fd_set *from, restrict fd_set *to);.

Возможно, стоит подражать на платформах, где он еще не доступен.

4b9b3361

Ответ 1

Так как struct fd_set является просто регулярной структурой C, это всегда должно быть хорошо. Мне лично не нравится выполнять копирование структуры с помощью оператора =, так как я работал над множеством платформ, которые не имели доступа к нормальному набору встроенных функций компилятора. Использование memcpy() явно, а не включение компилятора вызова функции - это лучший способ в моей книге.

Из раздела C spec, section 6.5.16.1 Простое назначение (для краткости отредактировано):

Одно из следующего:

...

  • левый операнд имеет квалифицированную или неквалифицированную версию структуры или типа объединения, совместимую с типом права;

...

В простом присваивании (=) значение правильного операнда преобразуется в тип выражения присваивания и заменяет значение, хранящееся в объекте, обозначенном левым операндом.

Если значение, хранящееся в объекте, считывается из другого объекта, который каким-либо образом перекрывает хранение первого объекта, то перекрытие должно быть точным, и два объекта должны иметь квалифицированные или неквалифицированные версии совместимого типа; в противном случае поведение undefined.

Итак, вы идете, пока struct fd_set на самом деле является регулярным C struct, вам гарантирован успех. Тем не менее, это зависит от того, как ваш компилятор испускает какой-то код для этого или полагается на то, что он использует memcpy() для присвоения структуры. Если по какой-либо причине ваша платформа не может связываться с внутренними библиотеками компилятора, она может не работать.

Вам нужно будет сыграть несколько трюков, если у вас есть более открытые дескрипторы файлов, чем вписывается в struct fd_set. В linux man page говорится:

An fd_set - буфер фиксированного размера. Выполнение FD_CLR() или FD_SET() со значением fd, отрицательным или равным или превышающим FD_SETSIZE, приведет к поведению undefined. Кроме того, POSIX требует, чтобы fd был допустимым файловым дескриптором.

Как упоминалось ниже, возможно, не стоит пытаться доказать, что ваш код безопасен для всех систем. FD_COPY() предоставляется только для такого использования и, по-видимому, всегда гарантируется:

FD_COPY(&fdset_orig, &fdset_copy) заменяет уже выделенный набор дескриптора файла &fdset_copy с копией &fdset_orig.

Ответ 2

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

Ответ 3

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

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

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

Ответ 4

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

Ответ 5

Я немного поработал над 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

  • Нет <sys/select.h> header - используйте <sys/time.h> вместо
  • По умолчанию FD_SETSIZE - 2048
  • Параметр FD_SETSIZE может быть изменен.
  • Нет FD_COPY

HP-UX 11.23

  • Имеет <sys/select.h>
  • По умолчанию 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 можно скопировать с помощью копии структуры, не подвергая риску поведение undefined.