stringtranslate.com

E/S asincrónicas

En informática, la E/S asíncrona (también E/S no secuencial ) es una forma de procesamiento de entrada/salida que permite que otros procesos continúen antes de que finalice la operación de E/S. Un nombre utilizado para E/S asincrónicas en la API de Windows es E/S superpuesta .

Las operaciones de entrada y salida (E/S) en una computadora pueden ser extremadamente lentas en comparación con el procesamiento de datos. Un dispositivo de E/S puede incorporar dispositivos mecánicos que deben moverse físicamente, como un disco duro que busca una pista para leer o escribir; Esto suele ser órdenes de magnitud más lento que la conmutación de la corriente eléctrica. Por ejemplo, durante una operación de disco que tarda diez milisegundos en realizarse, un procesador con una frecuencia de un gigahercio podría haber realizado diez millones de ciclos de procesamiento de instrucciones.

Un enfoque simple para la E/S sería iniciar el acceso y luego esperar a que se complete. Pero tal enfoque, llamado E/S síncrona o E/S de bloqueo , bloquearía el progreso de un programa mientras la comunicación está en curso, dejando los recursos del sistema inactivos. Cuando un programa realiza muchas operaciones de E/S (como un programa que depende principalmente o en gran medida de la entrada del usuario ), esto significa que el procesador puede pasar casi todo su tiempo inactivo esperando que se completen las operaciones de E/S.

Alternativamente, es posible iniciar la comunicación y luego realizar un procesamiento que no requiera que se complete la E/S. Este enfoque se llama entrada/salida asincrónica. Cualquier tarea que dependa de que se haya completado la E/S (esto incluye tanto el uso de los valores de entrada como las operaciones críticas que afirman garantizar que se ha completado una operación de escritura) todavía necesita esperar a que se complete la operación de E/S y, por lo tanto, es todavía bloqueado, pero otros procesamientos que no dependen de la operación de E/S pueden continuar.

Existen muchas funciones del sistema operativo para implementar E/S asíncronas en muchos niveles. De hecho, una de las funciones principales de todos los sistemas operativos, excepto los más rudimentarios , es realizar al menos alguna forma de E/S asíncrona básica, aunque esto puede no ser particularmente evidente para el usuario o el programador. En la solución de software más simple, el estado del dispositivo de hardware se sondea a intervalos para detectar si el dispositivo está listo para su siguiente operación. (Por ejemplo, el sistema operativo CP/M se construyó de esta manera. Su semántica de llamadas al sistema no requería una estructura de E/S más elaborada que esta, aunque la mayoría de las implementaciones eran más complejas y, por lo tanto, más eficientes). Acceso directo a memoria (DMA ) ) puede aumentar en gran medida la eficiencia de un sistema basado en sondeo, y las interrupciones de hardware pueden eliminar por completo la necesidad de realizar sondeos. Los sistemas operativos multitarea pueden explotar la funcionalidad proporcionada por las interrupciones de hardware, al tiempo que ocultan al usuario la complejidad del manejo de las interrupciones. El spooling fue una de las primeras formas de multitarea diseñada para explotar la E/S asíncrona. Finalmente, las API de E/S asíncronas explícitas y de subprocesos múltiples dentro de los procesos de usuario pueden explotar aún más las E/S asíncronas, a costa de una complejidad adicional del software.

La E/S asíncrona se utiliza para mejorar la eficiencia energética y, en algunos casos, el rendimiento. Sin embargo, en algunos casos puede tener efectos negativos sobre la latencia y el rendimiento.

Formularios

Formas de E/S y ejemplos de funciones POSIX:

Todas las formas de E/S asincrónicas abren aplicaciones a posibles conflictos de recursos y fallas asociadas. Se requiere una programación cuidadosa (a menudo utilizando exclusión mutua , semáforos , etc.) para evitar esto.

Cuando se exponen E/S asíncronas a aplicaciones, existen algunas clases amplias de implementación. La forma de la API proporcionada a la aplicación no se corresponde necesariamente con el mecanismo realmente proporcionado por el sistema operativo; Las emulaciones son posibles. Además, una sola aplicación puede utilizar más de un método, dependiendo de sus necesidades y de los deseos de su(s) programador(es). Muchos sistemas operativos proporcionan más de uno de estos mecanismos, es posible que algunos los proporcionen todos.

Proceso

Disponible en las primeras versiones de Unix. En un sistema operativo multitarea , el procesamiento se puede distribuir entre diferentes procesos, que se ejecutan de forma independiente, tienen su propia memoria y procesan sus propios flujos de E/S; estos flujos normalmente están conectados en tuberías . Los procesos son bastante costosos de crear y mantener, [ cita necesaria ] por lo que esta solución solo funciona bien si el conjunto de procesos es pequeño y relativamente estable. También supone que los procesos individuales pueden operar de forma independiente, además de procesar las E/S de cada uno; si necesitan comunicarse de otras maneras, coordinarlos puede resultar difícil. [ cita necesaria ]

Una extensión de este enfoque es la programación de flujo de datos , que permite redes más complicadas que solo las cadenas que soportan las tuberías.

Votación

Variaciones:

El sondeo proporciona una API sincrónica sin bloqueo que puede usarse para implementar alguna API asincrónica. Disponible en Unix tradicional y Windows . Su principal problema es que puede perder tiempo de CPU realizando sondeos repetidamente cuando el proceso de emisión no tiene nada más que hacer, lo que reduce el tiempo disponible para otros procesos. Además, debido a que una aplicación de sondeo es esencialmente de un solo subproceso, es posible que no pueda aprovechar completamente el paralelismo de E/S del que es capaz el hardware.

Seleccionar (/encuesta) bucles

Disponible en BSD Unix y casi cualquier otra cosa con una pila de protocolo TCP/IP que utilice o esté modelada según la implementación de BSD. Una variación del tema del sondeo, un bucle de selección utiliza la selectllamada del sistema para dormir hasta que ocurre una condición en un descriptor de archivo (por ejemplo, cuando hay datos disponibles para lectura), ocurre un tiempo de espera o se recibe una señal (por ejemplo, cuando un el proceso hijo muere). Al examinar los parámetros de retorno de la selectllamada, el bucle descubre qué descriptor de archivo ha cambiado y ejecuta el código apropiado. A menudo, para facilitar su uso, el bucle de selección se implementa como un bucle de eventos , quizás utilizando funciones de devolución de llamada ; la situación se presta particularmente bien a la programación basada en eventos .

Si bien este método es confiable y relativamente eficiente, depende en gran medida del paradigma de Unix de que " todo es un archivo "; cualquier E/S de bloqueo que no involucre un descriptor de archivo bloqueará el proceso. El bucle de selección también depende de poder involucrar todas las E/S en la selectllamada central; Las bibliotecas que realizan sus propias E/S son particularmente problemáticas a este respecto. Un problema potencial adicional es que las operaciones de selección y E/S todavía están lo suficientemente desacopladas como para que el resultado de la selección pueda ser efectivamente una mentira: si dos procesos están leyendo desde un único descriptor de archivo (posiblemente un mal diseño), la selección puede indicar la disponibilidad de lectura. datos que han desaparecido en el momento en que se emite la lectura, lo que resulta en un bloqueo; Si dos procesos están escribiendo en un solo descriptor de archivo (lo que no es tan infrecuente), la selección puede indicar capacidad de escritura inmediata, pero la escritura aún puede bloquearse, porque el otro proceso ha llenado un búfer mientras tanto, o debido a que la escritura es demasiado grande. por el buffer disponible o de otras formas inadecuadas para el destinatario.

El bucle de selección no alcanza la máxima eficiencia posible del sistema con, digamos, el método de colas de finalización , porque la semántica de la selectllamada, que permite el ajuste por llamada del conjunto de eventos aceptable, consume cierta cantidad de tiempo por invocación atravesada. la matriz de selección. Esto crea poca sobrecarga para las aplicaciones de usuario que pueden tener un descriptor de archivo abierto para el sistema de ventanas y algunos para los archivos abiertos, pero se convierte en un problema mayor a medida que crece el número de posibles fuentes de eventos y puede obstaculizar el desarrollo de aplicaciones de servidor de muchos clientes. , como en el problema C10k ; Otros métodos asincrónicos pueden ser notablemente más eficientes en tales casos. Algunos Unix proporcionan llamadas específicas del sistema con mejor escala; por ejemplo, epollen Linux (que llena la matriz de selección de retorno solo con aquellos orígenes de eventos en los que se ha producido un evento), kqueueen FreeBSD y puertos de eventos (y /dev/poll) en Solaris .

SVR3 Unix proporcionó la pollllamada al sistema. Podría decirse que tiene mejor nombre que select, pero para los fines de esta discusión es esencialmente lo mismo. SVR4 Unixes (y por tanto POSIX ) ofrecen ambas llamadas.

Señales (interrupciones)

Disponible en BSD y POSIX Unix. La E/S se emite de forma asincrónica y, cuando se completa, se genera una señal ( interrupción ). Al igual que en la programación del kernel de bajo nivel, las funciones disponibles para un uso seguro dentro del manejador de señales son limitadas y el flujo principal del proceso podría haberse interrumpido en casi cualquier punto, lo que resultaría en estructuras de datos inconsistentes tal como las ve el manejador de señales. El manejador de señales normalmente no puede emitir más E/S asincrónicas por sí solo.

El enfoque de señales , aunque relativamente simple de implementar dentro del sistema operativo, trae al programa de aplicación el bagaje no deseado asociado con la escritura del sistema de interrupción del núcleo de un sistema operativo. Su peor característica es que cada llamada al sistema de bloqueo (sincrónica) es potencialmente interrumpible; el programador normalmente debe incorporar un código de reintento en cada llamada. [ cita necesaria ]

Funciones de devolución de llamada

Disponible en los clásicos Mac OS , VMS y Windows . Tiene muchas de las características del método de la señal , ya que es fundamentalmente lo mismo, aunque rara vez se reconoce como tal. La diferencia es que cada solicitud de E/S normalmente puede tener su propia función de finalización, mientras que el sistema de señales tiene una única devolución de llamada.

Por otro lado, un problema potencial del uso de devoluciones de llamada es que la profundidad de la pila puede crecer de manera inmanejable, ya que una cosa extremadamente común cuando finaliza una E/S es programar otra. Si esto debe satisfacerse inmediatamente, la primera devolución de llamada no se "desenrolla" de la pila antes de que se invoque la siguiente. Los sistemas para evitar esto (como la programación intermedia de nuevos trabajos) añaden complejidad y reducen el rendimiento. En la práctica, sin embargo, esto generalmente no es un problema porque la nueva E/S generalmente regresará tan pronto como se inicie, lo que permitirá que la pila se "desenrolle". El problema también se puede evitar evitando más devoluciones de llamada, mediante una cola, hasta que regrese la primera devolución de llamada.

Procesos o hilos ligeros.

Los procesos ligeros (LWP) o subprocesos están disponibles en la mayoría de los sistemas operativos modernos. Como el método de procesos , pero con menores gastos generales y sin el aislamiento de datos que dificulta la coordinación de los flujos. Cada LWP o subproceso utiliza E/S síncrona de bloqueo tradicional, lo que simplifica la lógica de programación; Este es un paradigma común utilizado en muchos lenguajes de programación, incluidos Java y Rust. El subproceso múltiple necesita utilizar mecanismos de sincronización proporcionados por el kernel y bibliotecas seguras para subprocesos . Este método no es el más adecuado para aplicaciones de escala extremadamente grande, como servidores web, debido a la gran cantidad de subprocesos necesarios.

Este enfoque también se utiliza en el sistema de ejecución del lenguaje de programación Erlang . La máquina virtual Erlang utiliza E/S asincrónicas utilizando un pequeño grupo de solo unos pocos subprocesos o, a veces, solo un proceso, para manejar E/S de hasta millones de procesos Erlang. El manejo de E/S en cada proceso se escribe principalmente mediante el bloqueo de E/S síncronas. De esta manera, el alto rendimiento de las E/S asíncronas se combina con la simplicidad de las E/S normales (consulte el modelo Actor ). Muchos problemas de E/S en Erlang se asignan al paso de mensajes, que se pueden procesar fácilmente mediante la recepción selectiva incorporada.

Fibers / Coroutines pueden verse como un enfoque igualmente liviano para realizar E/S asincrónicas fuera del sistema de ejecución de Erlang, aunque no brindan exactamente las mismas garantías que los procesos de Erlang.

Colas/puertos de finalización

Disponible en Microsoft Windows , Solaris , AmigaOS , DNIX y Linux (usando io_uring , disponible en 5.1 y superiores). [1] Las solicitudes de E/S se emiten de forma asíncrona, pero las notificaciones de finalización se proporcionan a través de un mecanismo de cola de sincronización en el orden en que se completan. Generalmente asociado con una estructuración de máquina de estados del proceso principal ( programación basada en eventos ), que puede tener poca semejanza con un proceso que no usa E/S asincrónicas o que usa una de las otras formas, lo que dificulta la reutilización del código [ cita necesaria ] . No requiere mecanismos de sincronización especiales adicionales ni bibliotecas seguras para subprocesos , ni están separados los flujos textuales (código) y temporales (evento).

Banderas de eventos

Disponible en VMS y AmigaOS (a menudo se usa junto con un puerto de finalización). Tiene muchas de las características del método de cola de finalización , ya que es esencialmente una cola de finalización de profundidad. Para simular el efecto de la "profundidad" de la cola, se requiere un indicador de evento adicional para cada evento potencial no procesado (pero completado), o se puede perder la información del evento. Esperar el próximo evento disponible en un grupo de este tipo requiere mecanismos de sincronización que pueden no adaptarse bien a un mayor número de eventos potencialmente paralelos.

E/S de canal

Disponible en mainframes de IBM , Groupe Bull y Unisys . Channel I/O está diseñado para maximizar la utilización y el rendimiento de la CPU descargando la mayor parte de las E/S en un coprocesador. El coprocesador tiene DMA integrado, maneja las interrupciones del dispositivo, está controlado por la CPU principal y solo interrumpe la CPU principal cuando es realmente necesario. Esta arquitectura también admite los llamados programas de canal que se ejecutan en el procesador de canal para realizar tareas pesadas en actividades y protocolos de E/S.

E/S registradas

Disponible en Windows Server 2012 y Windows 8 . Optimizado para aplicaciones que procesan una gran cantidad de mensajes pequeños para lograr mayores operaciones de E/S por segundo con fluctuación y latencia reducidas. [2]

Implementación

La gran mayoría del hardware informático de uso general se basa completamente en dos métodos para implementar E/S asíncronas: sondeo e interrupciones. Normalmente ambos métodos se utilizan juntos; el equilibrio depende en gran medida del diseño del hardware y de sus características de rendimiento requeridas. ( DMA no es en sí mismo otro método independiente, es simplemente un medio por el cual se puede hacer más trabajo por sondeo o interrupción).

Los sistemas de sondeo puro son completamente posibles; los microcontroladores pequeños (como los sistemas que utilizan PIC ) a menudo se construyen de esta manera. Los sistemas CP/M también se podrían construir de esta manera (aunque rara vez se hacía), con o sin DMA. Además, cuando sólo se necesita el máximo rendimiento para unas pocas tareas, a expensas de otras tareas potenciales, el sondeo también puede ser apropiado, ya que la sobrecarga de aceptar interrupciones puede no ser bienvenida. (Atender una interrupción requiere tiempo [y espacio] para salvar al menos parte del estado del procesador, junto con el tiempo necesario para reanudar la tarea interrumpida).

La mayoría de los sistemas informáticos de propósito general dependen en gran medida de las interrupciones. Un sistema de interrupción puro puede ser posible, aunque normalmente también se requiere algún componente de sondeo, ya que es muy común que múltiples fuentes potenciales de interrupciones compartan una línea de señal de interrupción común, en cuyo caso se utiliza el sondeo dentro del controlador del dispositivo para resolver el problema. fuente real. (Este tiempo de resolución también contribuye a la penalización en el rendimiento del sistema de interrupción. A lo largo de los años se ha trabajado mucho para tratar de minimizar la sobrecarga asociada con el servicio de una interrupción. Los sistemas de interrupción actuales son bastante indiferentes en comparación con algunos anteriores altamente optimizados. , pero el aumento general en el rendimiento del hardware ha mitigado en gran medida esto).

También son posibles enfoques híbridos, en los que una interrupción puede desencadenar el comienzo de alguna ráfaga de E/S asincrónicas y el sondeo se utiliza dentro de la propia ráfaga. Esta técnica es común en controladores de dispositivos de alta velocidad, como redes o discos, donde el tiempo perdido al regresar a la tarea previa a la interrupción es mayor que el tiempo hasta el siguiente servicio requerido. (El hardware de E/S común que se utiliza hoy en día depende en gran medida de DMA y grandes buffers de datos para compensar un sistema de interrupción de rendimiento relativamente bajo. Estos típicamente utilizan sondeos dentro de los bucles del controlador y pueden exhibir un rendimiento tremendo. Idealmente, el per-datum Las encuestas siempre tienen éxito o, como mucho, se repiten un pequeño número de veces).

Hubo un tiempo en que este tipo de enfoque híbrido era común en controladores de disco y de red donde no había DMA ni un buffer significativo disponible. Debido a que las velocidades de transferencia deseadas eran más rápidas incluso de lo que podía tolerar el bucle mínimo de cuatro operaciones por dato (prueba de bits, rama condicional a uno mismo, recuperación y almacenamiento), el hardware a menudo se construiría con generación automática de estados de espera. en el dispositivo de E/S, sacando el sondeo de datos listos del software y colocándolos en el hardware de recuperación o almacenamiento del procesador y reduciendo el bucle programado a dos operaciones. (De hecho, se utiliza el propio procesador como motor DMA). El procesador 6502 ofrecía un medio inusual para proporcionar un bucle por dato de tres elementos, ya que tenía un pin de hardware que, cuando se afirmaba, causaba que el bit de desbordamiento del procesador se desactivara. establecer directamente. (Obviamente, habría que tener mucho cuidado en el diseño del hardware para evitar anular el bit de desbordamiento fuera del controlador del dispositivo).

Síntesis

Utilizando sólo estas dos herramientas (sondeo e interrupciones), todas las demás formas de E/S asincrónicas analizadas anteriormente pueden sintetizarse (y de hecho lo son).

En un entorno como una máquina virtual Java (JVM), se pueden sintetizar E/S asíncronas incluso aunque el entorno en el que se ejecuta la JVM no lo ofrezca en absoluto. Esto se debe a la naturaleza interpretada de la JVM. La JVM puede sondear (o interrumpir) periódicamente para instituir un flujo interno de cambio de control, lo que efectúa la aparición de múltiples procesos simultáneos, al menos algunos de los cuales presumiblemente existen para realizar E/S asincrónicas. (Por supuesto, a nivel microscópico el paralelismo puede ser bastante burdo y exhibir algunas características no ideales, pero en la superficie parecerá ser el deseado.)

Ése, de hecho, es el problema de utilizar el sondeo en cualquier forma para sintetizar una forma diferente de E/S asíncrona. Cada ciclo de CPU que es una encuesta se desperdicia y se pierde por gastos generales en lugar de realizar una tarea deseada. Cada ciclo de CPU que no sea un sondeo representa un aumento en la latencia de reacción a las E/S pendientes. Es difícil lograr un equilibrio aceptable entre estas dos fuerzas opuestas. (Esta es la razón por la que se inventaron los sistemas de interrupción de hardware en primer lugar).

El truco para maximizar la eficiencia es minimizar la cantidad de trabajo que se debe realizar al recibir una interrupción para activar la aplicación adecuada. En segundo lugar (pero quizás no menos importante) está el método que utiliza la propia aplicación para determinar lo que debe hacer.

Particularmente problemáticos (para la eficiencia de la aplicación) son los métodos de sondeo expuestos, incluidos los mecanismos de selección/encuesta. Aunque es muy probable que los eventos de E/S subyacentes que les interesan estén impulsados ​​por interrupciones, la interacción con este mecanismo se sondea y puede consumir una gran cantidad de tiempo en la encuesta. Esto es particularmente cierto en el caso de las encuestas potencialmente a gran escala posibles mediante select (y poll). Las interrupciones se asignan muy bien a señales, funciones de devolución de llamada, colas de finalización e indicadores de eventos; estos sistemas pueden ser muy eficientes.

Ejemplos

Los siguientes ejemplos muestran tres enfoques para leer E/S. Los objetos y funciones son abstractos.

1. Bloqueo, sincrónico:

dispositivo  =  IO . datos abiertos () = dispositivo . read () # el hilo se bloqueará hasta que haya datos en el dispositivo print ( datos )   

2. Bloqueo y no bloqueo, sincrónico: (aquí IO.poll()bloquea hasta por 5 segundos, pero device.read()no lo hace)

dispositivo  =  IO . open () listo  =  False mientras  no  esté listo :  print ( "¡No hay datos para leer!" )  listo  =  IO . poll ( dispositivo ,  IO . INPUT ,  5 )  # devuelve el control si han transcurrido 5 segundos o hay datos para leer (ENTRADA) datos  =  dispositivo . leer () imprimir ( datos )

3. Sin bloqueo, asíncrono:

ios  =  IO . Dispositivo IOService () = IO . abierto ( ios )  def  inputHandler ( datos ,  err ):  "Manejador de datos de entrada"  si  no es  err :  imprimir ( datos )dispositivo . readSome ( inputHandler ) ios . loop ()  # esperar hasta que se hayan completado todas las operaciones y llamar a todos los controladores apropiados

Aquí está el mismo ejemplo con Async/await :

ios  =  IO . Dispositivo IOService () = IO . abierto ( ios )   tarea async def  (): prueba : datos = dispositivo de espera . readSome () imprime ( datos ) excepto Excepción : pasar         ios . addTask ( tarea ) ios . loop ()  # esperar hasta que se hayan completado todas las operaciones y llamar a todos los controladores apropiados

Aquí está el ejemplo con el patrón Reactor :

dispositivo  =  IO . reactor abierto () = IO . reactor ()  def  inputHandler ( datos ):  "Manejador de datos de entrada"  print ( datos )  reactor . detener ()reactor . addHandler ( inputHandler ,  dispositivo ,  IO . INPUT ) reactor . run ()  # ejecuta el reactor, que maneja eventos y llama a los controladores apropiados

Ver también

Referencias

  1. ^ Corbet, Jonathan. "Suena una nueva API de E/S asincrónica". LWN.net . Consultado el 27 de julio de 2020 .
  2. ^ "Extensiones de API de entrada-salida registradas (RIO)". technet.microsoft.com . 31 de agosto de 2016.

enlaces externos