¿Hay plataformas donde el uso de la copia de estructura en un fd_set (para select () o pselect ()) causa problemas?

Las llamadas al sistema select() y pselect() modifican sus argumentos (los argumentos ‘ fd_set * ‘), de modo que el valor de entrada le dice al sistema qué descriptores de archivos verificar y los valores de retorno le dicen al progtwigdor qué descriptores de archivos son actualmente utilizables.

Si va a llamarlos repetidamente para obtener el mismo conjunto de descriptores de archivos, debe asegurarse de tener una copia nueva de los descriptores para cada llamada. La forma obvia de hacerlo es usar una copia de estructura:

 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... } } 

( Editado para eliminar referencias struct fd_set incorrectas, como lo señala ‘R ..’ ) .

Mi pregunta:

  • ¿Hay plataformas donde no es seguro hacer una copia de estructura de los valores de fd_set como se muestra?

Me preocupa que haya una asignación oculta de memoria o algo inesperado como ese. (Hay macros / funciones FD_SET (), FD_CLR (), FD_ZERO () y FD_ISSET () para enmascarar las partes internas de la aplicación).

Puedo ver que MacOS X (Darwin) es seguro; otros sistemas basados ​​en BSD probablemente sean seguros, por lo tanto. Puede ayudar documentando otros sistemas que sabe que son seguros en sus respuestas.

(Tengo fd_set preocupaciones sobre qué tan bien funcionaría fd_set con más de 8192 descriptores de archivos abiertos; el número máximo predeterminado de archivos abiertos es solo 256, pero el número máximo es ‘ilimitado’. Además, dado que las estructuras son de 1 KB, el código de copiado no es tremendamente eficiente, pero luego ejecutar una lista de descriptores de archivos para recrear la máscara de entrada en cada ciclo tampoco es necesariamente eficiente. Tal vez no puedas hacer select() cuando tienes muchos descriptores de archivos abiertos, aunque es cuando es más probable que necesite la funcionalidad).


Hay una pregunta relacionada con SO, que pregunta sobre ‘poll () vs select ()’ que aborda un conjunto diferente de problemas de esta pregunta.


Tenga en cuenta que en MacOS X – y presumiblemente en BSD en general – hay una macro o función FD_COPY() , con el prototipo efectivo:

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

Puede valer la pena emular en plataformas donde aún no está disponible.

Como struct fd_set es solo una estructura C normal, siempre debería estar bien. Personalmente, no me gusta copiar estructuras mediante el operador = , ya que he trabajado en muchas plataformas que no tenían acceso al conjunto normal de intrínsecos del comstackdor. Usar memcpy() explícita en lugar de hacer que el comstackdor inserte una llamada de función es una mejor manera de hacerlo, en mi libro.

De la especificación C, sección 6.5.16.1 Asignación simple (editada aquí para abreviar):

Uno de los siguientes debe contener:

  • el operando izquierdo tiene una versión calificada o no calificada de una estructura o tipo de unión compatible con el tipo del derecho;

En la asignación simple (=), el valor del operando derecho se convierte al tipo de expresión de asignación y reemplaza el valor almacenado en el objeto designado por el operando izquierdo.

Si el valor almacenado en un objeto se lee de otro objeto que se superpone de alguna manera con el almacenamiento del primer objeto, entonces la superposición será exacta y los dos objetos tendrán versiones calificadas o no calificadas de un tipo compatible; de lo contrario, el comportamiento no está definido.

Así que ahí lo tienes, siempre y cuando struct fd_set sea ​​realmente una struct C normal, tienes garantizado el éxito. Depende, sin embargo, de que el comstackdor emita algún tipo de código para hacerlo, o que memcpy() cualquier intrínseco memcpy() que utilice para la asignación de estructuras. Si su plataforma no puede vincularse con las bibliotecas intrínsecas del comstackdor por algún motivo, es posible que no funcione.

Tendrá que jugar algunos trucos si tiene más descriptores de archivos abiertos que los que caben en struct fd_set . La página del hombre de Linux dice:

Un fd_set es un buffer de tamaño fijo. Ejecutar FD_CLR() o FD_SET() con un valor de fd que sea negativo o sea igual o mayor que FD_SETSIZE dará como resultado un comportamiento indefinido. Además, POSIX requiere que fd sea ​​un descriptor de archivo válido.

Como se menciona a continuación, puede que no valga la pena intentar demostrar que su código es seguro en todos los sistemas. FD_COPY() se proporciona solo para dicho uso y, presumiblemente, siempre está garantizado:

FD_COPY(&fdset_orig, &fdset_copy) reemplaza un conjunto de descriptores de archivos ya asignados &fdset_copy con una copia de &fdset_orig .

En primer lugar, no hay struct fd_set . Simplemente se llama fd_set . Sin embargo, POSIX requiere que sea un tipo de estructura, por lo que el copiado está bien definido.

En segundo lugar, no hay forma bajo el estándar C en el que el objeto fd_set pueda contener memoria asignada dinámicamente, ya que no es necesario usar ninguna función / macro para liberarla antes de regresar. Incluso si el comstackdor tiene alloca (una extensión pre-vla para asignación basada en stack), fd_set no podría usar la memoria asignada en la stack, porque un progtwig podría pasar un puntero al fd_set a otra función que use FD_SET , etc., y la memoria asignada dejaría de ser válida tan pronto como regrese a la persona que llama. Solo si el comstackdor de C ofrece alguna extensión para los destructores, fd_set puede usar la asignación dinámica.

En conclusión, parece seguro solo asignar / fd_set objetos fd_set , pero para estar seguro, haría algo como:

 #ifndef FD_COPY #define FD_COPY(dest,src) memcpy((dest),(src),sizeof *(dest)) #endif 

o alternativamente solo:

 #ifndef FD_COPY #define FD_COPY(dest,src) (*(dest)=*(src)) #endif 

Luego, usará la macro FD_COPY proporcionada por el sistema si existe, y solo recurrirá a la versión teóricamente potencialmente insegura si falta.

Tiene razón en que POSIX no garantiza que la copia de un fd_set tenga que “funcionar”. Personalmente, no estoy al tanto de ningún lugar que no sea así, pero nunca hice el experimento.

Puede usar la alternativa poll() (que también es POSIX). Funciona de una manera muy similar a select() , excepto que el parámetro de entrada / salida no es opaco (y no contiene punteros, por lo que una memcpy funcionará), y su diseño también elimina por completo la necesidad de hacer una copia del estructura de “descriptores de archivos solicitados” (porque los “eventos solicitados” y los “eventos devueltos” se almacenan en diferentes campos).

También es correcto suponer que select() (y poll() ) no se adaptan particularmente bien a un gran número de descriptores de archivo; esto se debe a que cada vez que la función retorna, debe recorrer cada descriptor de archivo para comprobar si hubo alguna actividad en él. Las soluciones a esto son varias interfaces no estándar (por ejemplo, el epoll() Linux, el epoll() FreeBSD), que es posible que deba examinar si observa que tiene problemas de latencia.

No tengo suficientes representantes para agregar esto como un comentario a la respuesta de caf, pero hay bibliotecas para abstraer sobre las interfaces no estándar como epoll() y kqueue . libevent es uno, y libera a otro. Creo que GLib también tiene uno que se vincula con su mainloop.

He investigado un poco sobre MacOS X, Linux, AIX, Solaris y HP-UX, y hay algunos resultados interesantes. Usé el siguiente progtwig:

 #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  #else #include  #endif /* USE_SYS_TIME_H */ #include  int main(void) { printf("FD_SETSIZE = %d; sizeof(fd_set) = %d\n", (int)FD_SETSIZE, (int)sizeof(fd_set)); return 0; } 

Fue comstackdo dos veces en cada plataforma:

 cc -o select select.c cc -o select -DSET_FD_SETSIZE=16384 

(Y en una plataforma, HP-UX 11.11, tuve que agregar -DUSE_SYS_TIME_H para comstackr las cosas.) Hice una comprobación visual de FD_COPY por separado, solo MacOS X parecía incluirlo, y eso tuvo que ser activado por asegurándose de que _POSIX_C_SOURCE no estaba definido o definiendo _DARWIN_C_SOURCE .

AIX 5.3

  • El valor predeterminado FD_SETSIZE es 65536
  • El parámetro FD_SETSIZE se puede cambiar de tamaño
  • No FD_COPY

HP-UX 11.11

  • Sin encabezado – use lugar
  • El valor predeterminado FD_SETSIZE es 2048
  • El parámetro FD_SETSIZE se puede cambiar de tamaño
  • No FD_COPY

HP-UX 11.23

  • Tiene
  • El valor predeterminado FD_SETSIZE es 2048
  • El parámetro FD_SETSIZE se puede cambiar de tamaño
  • No FD_COPY

Linux (kernel 2.6.9, glibc 2.3.4)

  • El valor predeterminado FD_SETSIZE es 1024
  • El parámetro FD_SETSIZE no se puede cambiar de tamaño
  • No FD_COPY

MacOS X 10.6.2

  • El valor predeterminado FD_SETSIZE es 1024
  • El parámetro FD_SETSIZE se puede cambiar de tamaño
  • FD_COPY se define si no se solicita el cumplimiento estricto de POSIX o si se especifica _DARWIN_C_SOURCE

Solaris 10 (SPARC)

  • Valor predeterminado FD_SETSIZE es 1024 para 32 bits, 65536 para 64 bits
  • El parámetro FD_SETSIZE se puede cambiar de tamaño
  • No FD_COPY

Claramente, una modificación trivial del progtwig permite la verificación automática de FD_COPY:

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

Lo que no es necesariamente trivial es averiguar cómo asegurarse de que esté disponible; terminas haciendo el escaneo manual y resolviendo cómo dispararlo.

En todas estas máquinas, parece que un fd_set puede copiarse mediante una copia de estructura sin correr el riesgo de un comportamiento indefinido.