¿Hay alguna plataforma en la que el uso de la copia de estructura en un fd_set (para select() o pselect()) cause problemas?

10 minutos de lectura

avatar de usuario
jonathan leffler

los select() y pselect() las llamadas al sistema modifican sus argumentos (el ‘fd_set *‘ argumentos), por lo que el valor de entrada le dice al sistema qué descriptores de archivo verificar y los valores devueltos le dicen al programador qué descriptores de archivo se pueden usar actualmente.

Si va a llamarlos repetidamente por el mismo conjunto de descriptores de archivo, 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 incorrecto struct fd_set referencias – como lo señala ‘R..’.)

Mi pregunta:

  • ¿Hay alguna plataforma en la que no sea seguro hacer una copia de la estructura del fd_set valores como se muestra?

Me preocupa que haya una asignación de memoria oculta o algo inesperado por el estilo. (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; por lo tanto, es probable que otros sistemas basados ​​en BSD sean seguros. Puede ayudar documentando otros sistemas que sabe que son seguros en sus respuestas.

(Tengo preocupaciones menores acerca de qué tan bien el fd_set funcionaría 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 copia no es terriblemente 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 tiene tantos descriptores de archivo abiertos, aunque es cuando es más probable que necesite la funcionalidad).


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


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

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

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

avatar de usuario
carl norum

Ya que struct fd_set es solo una estructura C regular, eso siempre debería estar bien. Personalmente, no me gusta copiar estructuras a través de = operador, ya que he trabajado en muchas plataformas que no tenían acceso al conjunto normal de intrínsecos del compilador. Utilizando memcpy() explícitamente en lugar de que el compilador 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 (editado aquí por brevedad):

Se cumplirá uno de los siguientes:

  • el operando izquierdo tiene una versión calificada o no calificada de un tipo de estructura o unión compatible con el tipo de la derecha;

En tarea sencilla (=), el valor del operando derecho se convierte al tipo de la 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 al almacenamiento del primer objeto, entonces la superposición será exacta y los dos objetos tendrán versiones cualificadas o no cualificadas de un tipo compatible; de lo contrario, el comportamiento no está definido.

Así que ahí tienes, siempre y cuando struct fd_set es en realidad una C regular struct, tienes el éxito garantizado. Depende, sin embargo, de que su compilador emita algún tipo de código para hacerlo, o confíe en lo que sea memcpy() intrínseco que utiliza para la asignación de estructura. Si su plataforma no puede vincularse con las bibliotecas intrínsecas del compilador por algún motivo, es posible que no funcione.

Tendrá que jugar algunos trucos si tiene más descriptores de archivos abiertos de los que caben en struct fd_set. el linux página man dice:

Un fd_set es un búfer de tamaño fijo. ejecutando FD_CLR() o FD_SET() con un valor de fd que es negativo o es igual o mayor que FD_SETSIZE resultará en un comportamiento indefinido. Además, POSIX requiere fd ser un descriptor de archivo válido.

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

FD_COPY(&fdset_orig, &fdset_copy) reemplaza uno ya asignado &fdset_copy descriptor de archivo establecido con una copia de &fdset_orig.

  • Pero supongamos que alguien se puso elegante y almacenó un puntero a una matriz asignada para los bits… luego, al copiar la estructura, se copiaría el puntero y no los datos señalados. La estructura podría copiarse; todas las estructuras se pueden copiar. Pero entonces había potencial para que las cosas salieran mal. Creo que es poco probable que sea un problema, pero no puedo ver la redacción en POSIX que descarte esto, a menos que se implique que la macro FD_SETSIZE es una constante y …

    –Jonathan Leffler

    11 de marzo de 2010 a las 0:34

  • @Jonathan, ese es un buen punto. Las personas pueden tomar decisiones de implementación locas a veces. Editaré mi respuesta para mencionar FD_COPY.

    –Carl Norum

    11 de marzo de 2010 a las 0:38

  • Claramente, alguien más tuvo problemas similares, por lo que inventaron FD_COPY(). Desafortunadamente, no está en el estándar POSIX 2008 ni en muchas otras plataformas, incluidas las versiones anteriores de Linux (kernel 2.6.9; glibc 2.3.4).

    –Jonathan Leffler

    11 de marzo de 2010 a las 4:53

  • @Jonathan: expliqué en mi respuesta por qué fd_setsiempre que se implemente bajo el estándar C, nunca podría usar la memoria asignada.

    – R.. GitHub DEJA DE AYUDAR A ICE

    29 de septiembre de 2010 a las 17:02

avatar de usuario
R.. GitHub DEJAR DE AYUDAR A ICE

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 la copia está bien definida.

En segundo lugar, no hay forma bajo el estándar C en el que el fd_set El objeto podría contener memoria asignada dinámicamente, ya que no es necesario usar ninguna función/macro para liberarla antes de regresar. Incluso si el compilador tiene alloca (una extensión pre-vla para la asignación basada en pilas), fd_set no podía usar la memoria asignada en la pila, porque un programa podría pasar un puntero a la fd_set a otra función que utiliza 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 compilador de C ofreciera alguna extensión para los destructores podría fd_set utilizar la asignación dinámica.

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

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

o alternativamente simplemente:

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

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

  • Gracias por señalar el error en la pregunta, lo arreglaré.

    –Jonathan Leffler

    26 de julio de 2010 a las 18:45

  • Esa macro FD_COPY tiene un problema si le está dando punteros a fd_sets (que es lo que toman las otras funciones FD_*()). Por ejemplo, en FD_COPY(pFdSet1, pFdSet2), el tamaño de (pFdSet1) normalmente será 4, ya que pFdSe3t1 es un puntero a fd_set. En lugar de “sizeof(dest)”, sustituya “sizeof(fd_set)”.

    – Jim Kleck

    9 de agosto de 2012 a las 21:57

  • Tenga en cuenta que la respuesta tiene dest como primer argumento mientras que la implementación de BSD tiene src como primer argumento.

    – Denis

    19 de marzo de 2020 a las 11:07

avatar de usuario
coste y flete

Tiene razón en que POSIX no garantiza que copiar un fd_set tiene que trabajar”. No estoy personalmente al tanto de ningún lugar que no lo haga, pero nunca he hecho el experimento.

Puedes usar el poll() alternativa (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 un simple memcpy funcionará), y su diseño también elimina por completo la necesidad de hacer una copia de la estructura de “descriptores de archivos solicitados” (porque los “eventos solicitados” y los “eventos devueltos” se almacenan en campos diferentes).

También tiene razón al 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 regresa, debe recorrer cada descriptor de archivo para comprobar si hubo actividad en él. Las soluciones a esto son varias interfaces no estándar (por ejemplo, Linux’s epoll()FreeBSD kqueue), que es posible que deba analizar si descubre que tiene problemas de latencia.

Investigué un poco sobre MacOS X, Linux, AIX, Solaris y HP-UX, y obtuve algunos resultados interesantes. Usé el siguiente programa:

#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;
}

Fue compilado 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 que las cosas se compilaran). Hice una verificación visual por separado en FD_COPY: solo MacOS X parecía incluirlo, y eso tenía que ser activado por asegurando que _POSIX_C_SOURCE no fue definido o por definir _DARWIN_C_SOURCE.

AIX 5.3

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

HP-UX 11.11

  • No <sys/select.h> encabezado – uso <sys/time.h> en lugar de
  • El FD_SETSIZE predeterminado es 2048
  • El parámetro FD_SETSIZE se puede cambiar de tamaño
  • Sin FD_COPY

HP-UX 11.23

  • Posee <sys/select.h>
  • El FD_SETSIZE predeterminado es 2048
  • El parámetro FD_SETSIZE se puede cambiar de tamaño
  • Sin FD_COPY

Linux (núcleo 2.6.9, glibc 2.3.4)

  • El FD_SETSIZE predeterminado es 1024
  • El parámetro FD_SETSIZE no poder ser redimensionado
  • Sin FD_COPY

Mac OS X 10.6.2

  • El FD_SETSIZE predeterminado 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 _DARWIN_C_SOURCE está especificado

Solaris 10 (SPARC)

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

Claramente, una modificación trivial al programa 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 garantizar que esté disponible; terminas haciendo el escaneo manual y averiguando cómo activarlo.

En todas estas máquinas, parece un fd_set puede ser copiado por una copia de estructura sin correr el riesgo de un comportamiento indefinido.

No tengo suficiente representante 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 libev otro. Creo que GLib también tiene uno que se vincula con su bucle principal.

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad