Существуют ли какие-либо платформы, где использование структурного копирования для fd_set (для select () или pselect ()) вызывает проблемы? - PullRequest
16 голосов
/ 11 марта 2010

Системные вызовы 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(), когда у вас столько дескрипторы файлов открыты, хотя именно тогда вам, скорее всего, понадобится эта функциональность.)


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


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

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

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

Ответы [ 5 ]

9 голосов
/ 11 марта 2010

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

Из спецификации C, раздел 6.5.16.1 Простое назначение (отредактировано здесь для краткости):

Должно быть выполнено одно из следующих действий:

...

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

...

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

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

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

Вам придется сыграть несколько хитростей, если у вас больше открытых файловых дескрипторов, чем уместится в struct fd_set. Справочная страница linux говорит:

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

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

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

7 голосов
/ 26 июля 2010

Прежде всего, нет struct fd_set. Это просто называется fd_set. Однако POSIX требует, чтобы это был структурный тип, поэтому копирование четко определено.

Во-вторых, в стандарте C нет способа, при котором объект fd_set мог бы содержать динамически распределенную память, поскольку не требуется использовать какую-либо функцию / макрос для ее освобождения перед возвратом. Даже если компилятор имеет alloca (расширение до 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 голосов
/ 11 марта 2010

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

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

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

2 голосов
/ 13 марта 2010

Я провел небольшое исследование 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> заголовок - используйте <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 может быть скопировано структурной копией без риска неопределенного поведения.

1 голос
/ 11 марта 2010

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...