sistemas operativos gestion archivos websocket operating-system message-queue zeromq file-descriptor

websocket - gestion - ¿Por qué los sistemas operativos limitan los descriptores de archivos?



sistemas de archivos sistemas operativos (4)

Hago esta pregunta después de hacer mi mejor esfuerzo para buscar la mejor manera de implementar un servidor de cola de mensajes. ¿Por qué los sistemas operativos ponen límites al número de descriptores de archivos abiertos que puede tener un proceso y el sistema global? La implementación de mi servidor actual utiliza zeromq y abre un socket de suscriptor para cada cliente websocket conectado. Obviamente, ese proceso único solo será capaz de manejar clientes hasta el límite de los fds. Cuando investigo el tema, encuentro mucha información sobre cómo aumentar los límites del sistema a niveles tan altos como 64k fds, pero nunca menciona cómo afecta el rendimiento del sistema y por qué es 1k y más bajo para empezar. Mi enfoque actual es intentar enviar mensajes a todos los clientes utilizando una rutina en su propio bucle y un mapa de todos los clientes y sus canales de suscripción. ¿Pero me encantaría escuchar una respuesta sólida sobre las limitaciones del descriptor de archivos y cómo afectan a las aplicaciones que intentan usarlas en un nivel por cliente con conexiones persistentes?


En los sistemas UNIX, el lenguaje de creación de procesos fork () y fork () / exec () requiere una iteración sobre todos los posibles descriptores de archivos de procesos que intentan cerrar cada uno, dejando típicamente solo algunos descriptores de archivos como stdin, stdout, stderr sin tocar o redirigido a otro lugar.

Dado que esta es la API de Unix para iniciar un proceso, debe realizarse cada vez que se cree un nuevo proceso, incluida la ejecución de todos y cada uno de los comandos no incorporados que se invocan dentro de los scripts de shell.

Otros factores a considerar son que, si bien algunos programas pueden usar sysconf(OPEN_MAX) para determinar dinámicamente la cantidad de archivos que pueden abrirse mediante un proceso, muchos programas aún utilizan el FD_SETSIZE predeterminado de la biblioteca de FD_SETSIZE , que suele ser de 1024 descriptores y como tal nunca puede tener más que muchos archivos abiertos, independientemente de cualquier límite superior definido administrativamente.

Unix tiene un mecanismo de E / S asíncrono heredado basado en conjuntos de descriptores de archivos que utilizan desplazamientos de bits para representar archivos en los que hay que esperar y archivos que están listos o en una condición de excepción. No se adapta bien a miles de archivos, ya que estos conjuntos de descriptores deben configurarse y borrarse cada vez que se ejecuta alrededor del runloop. Han aparecido nuevas apis no estándar en las principales variantes de Unix, incluyendo kqueue() en * BSD y epoll() en Linux para solucionar las deficiencias de rendimiento cuando se trata de un gran número de descriptores.

Es importante tener en cuenta que select()/poll() todavía es usado por MUCHO software, ya que durante mucho tiempo ha sido la api de POSIX para E / S asíncronas. El enfoque asíncrono moderno de POSIX IO ahora es aio_* API pero es probable que no sea competitivo con las kqueue() o epoll() . No he usado aio en la ira y ciertamente no tendría el rendimiento y la semántica ofrecidos por los enfoques nativos en la forma en que pueden agregar múltiples eventos para un mayor rendimiento. kqueue () en * BSD tiene una semántica activada por el borde realmente bueno para la notificación de eventos, lo que le permite reemplazar select () / poll () sin forzar grandes cambios estructurales en su aplicación. Linux epoll () sigue el ejemplo de * BSD kqueue () y lo mejora, lo que a su vez sigue el ejemplo de los evports de Sun / Solaris.

El resultado es que al aumentar la cantidad de archivos abiertos permitidos en todo el sistema se agrega tiempo y espacio para cada proceso en el sistema, incluso si no pueden hacer uso de esos descriptores basados ​​en la API que están usando. También hay límites de sistema agregados, así como para la cantidad de archivos abiertos permitidos. Este resumen de sintonía más antiguo pero interesante para conexiones simultáneas de 100k a 200k con nginx en FreeBSD proporciona información sobre los gastos generales para mantener las conexiones abiertas y otro que cubre una gama más amplia de sistemas pero "solo" al ver las conexiones de 10K como el Monte Everest.

Probablemente la mejor referencia para la programación del sistema Unix es W. Richard Stevens Programación avanzada en el entorno Unix


Para fines de rendimiento, la tabla de archivos abiertos debe asignarse de forma estática, por lo que su tamaño debe ser fijo. Los descriptores de archivos son solo compensaciones en esta tabla, por lo que todas las entradas deben ser contiguas. Puede cambiar el tamaño de la tabla, pero esto requiere detener todos los subprocesos en el proceso y asignar un nuevo bloque de memoria para la tabla de archivos, y luego copiar todas las entradas de la tabla anterior a la nueva. No es algo que quieras hacer dinámicamente, especialmente cuando la razón por la que lo haces es porque la mesa antigua está llena.


Puede deberse a que un valor de descriptor de archivo es un índice en una tabla de descriptores de archivo. Por lo tanto, el número de descriptores de archivo posibles determinaría el tamaño de la tabla. El promedio de usuarios no querría que la mitad de su ram se consumiera en una tabla de descriptores de archivos que puede manejar millones de descriptores de archivos que nunca necesitarán.


Hay ciertas operaciones que se ralentizan cuando tienes muchos descriptores de archivo potenciales . Un ejemplo es la operación "cerrar todos los descriptores de archivos excepto stdin , stdout y stderr ", la única forma portátil * de hacerlo es intentar cerrar todos los descriptores de archivos posibles, excepto esos tres, que pueden convertirse en una operación lenta si pudiera potencialmente tener millones de descriptores de archivo abiertos.

*: Si está dispuesto a ser no portátil, puede buscar en /proc/self/fd , pero eso no es lo importante.

Esta no es una razón particularmente buena, pero es una razón. Otra razón es simplemente para evitar que un programa con errores (es decir, uno que "filtra" los descriptores de archivos) consuma demasiados recursos del sistema.