Programación de red de Linux: discusión sobre E / S asíncrona

concepto

El primero es el bloqueo de E / S. Al bloquear la solicitud de lectura iniciada por E / S, el subproceso se suspenderá hasta que los datos del kernel estén listos y los datos se copien desde el área del kernel al búfer de la aplicación.Cuando se completa el proceso de copia, la llamada de solicitud de lectura regresa. A continuación, la aplicación puede analizar los datos en el búfer.

                                                         

La segunda es la E / S sin bloqueo. La solicitud de lectura sin bloqueo regresa inmediatamente cuando los datos no están listos. La aplicación puede sondear continuamente el kernel hasta que los datos estén listos. El kernel copia los datos en el búfer de la aplicación y completa la llamada de lectura. Tenga en cuenta que la última llamada de lectura aquí, el proceso de obtención de datos, es un proceso sincrónico. La sincronización aquí se refiere al proceso de copiar los datos en el área del kernel al área del búfer.

                                                     

No es económico pedirle a la aplicación que sondee si la E / S del kernel está lista cada vez, porque el proceso de aplicación no puede hacer nada durante el proceso de sondeo. Como resultado, las tecnologías de multiplexación de E / S como select y poll están en el escenario. A través de la distribución de eventos de E / S, cuando los datos del kernel están listos, se notifica a la aplicación para que funcione. Este enfoque mejora en gran medida la utilización de la CPU del proceso de aplicación. El proceso de aplicación puede utilizar la CPU para hacer otras cosas sin ser notificado.

Tenga en cuenta que el proceso de llamar a leer y obtener datos también es un proceso sincrónico.

                                                  

El primer tipo de bloqueo de E / S, la aplicación se suspenderá hasta que se obtengan los datos. La segunda E / S sin bloqueo y la tercera tecnología de multiplexación basada en E / S sin bloqueo, no se bloqueará la operación de obtención de datos. Aquí, todas son tecnologías de llamadas sincrónicas . ¿Por qué dices eso? Debido a que los términos de llamada síncrona y llamada asíncrona son para el proceso de obtención de datos, las primeras llamadas a operaciones de lectura que finalmente obtienen datos son todas sincrónicas. Durante la llamada de lectura, el kernel copia los datos del espacio del kernel a la aplicación. , este proceso se realiza sincrónicamente en la función de lectura Si la eficiencia de copia implementada por el kernel es muy pobre, la llamada de lectura consumirá un tiempo relativamente largo en este proceso de sincronización.

Pero las llamadas asincrónicas reales no se preocupan por este tema, vamos a introducir una cuarta tecnología de E / S , cuando lanzamos aio_read, regresa inmediatamente, dentro del núcleo copia automáticamente los datos del espacio del kernel al espacio de la aplicación , esto El proceso de copia es asincrónico y el kernel se realiza automáticamente.A diferencia de la operación síncrona anterior, la aplicación no necesita iniciar la acción de copia.

                                                   

Aquí se coloca una tabla para resumir los modelos de E / S anteriores.

                                     

Uso de aio_read y aio_write

Primero, un ejemplo de programa:

const int BUF_SIZE = 512;

int main() {
    int err;
    int result_size;
    // 创建一个临时文件
    char tmpname[256];
    snprintf(tmpname, sizeof(tmpname), "/tmp/aio_test_%d", getpid());
    unlink(tmpname);
    int fd = open(tmpname, O_CREAT | O_RDWR | O_EXCL, S_IRUSR | S_IWUSR);
    if (fd == -1) {
        error(1, errno, "open file failed ");
    }
    char buf[BUF_SIZE];
    struct aiocb aiocb;

    //初始化buf缓冲,写入的数据应该为0xfafa这样的,
    memset(buf, 0xfa, BUF_SIZE);
    memset(&aiocb, 0, sizeof(struct aiocb));
    aiocb.aio_fildes = fd;
    aiocb.aio_buf = buf;
    aiocb.aio_nbytes = BUF_SIZE;
    //开始写
    if (aio_write(&aiocb) == -1) {
        printf(" Error at aio_write(): %s\n", strerror(errno));
        close(fd);
        exit(1);
    }
    //因为是异步的,需要判断什么时候写完
    while (aio_error(&aiocb) == EINPROGRESS) {
        printf("writing... \n");
    }
    //判断写入的是否正确
    err = aio_error(&aiocb);
    result_size = aio_return(&aiocb);
    if (err != 0 || result_size != BUF_SIZE) {
        printf(" aio_write failed() : %s\n", strerror(err));
        close(fd);
        exit(1);
    }

    //下面准备开始读数据
    char buffer[BUF_SIZE];
    struct aiocb cb;
    cb.aio_nbytes = BUF_SIZE;
    cb.aio_fildes = fd;
    cb.aio_offset = 0;
    cb.aio_buf = buffer;
    // 开始读数据
    if (aio_read(&cb) == -1) {
        printf(" air_read failed() : %s\n", strerror(err));
        close(fd);
    }
    //因为是异步的,需要判断什么时候读完
    while (aio_error(&cb) == EINPROGRESS) {
        printf("Reading... \n");
    }
    // 判断读是否成功
    int numBytes = aio_return(&cb);
    if (numBytes != -1) {
        printf("Success.\n");
    } else {
        printf("Error.\n");
    }

    // 清理文件句柄
    close(fd);
    return 0;
}

Aquí, las principales funciones utilizadas son:

  • aio_write: se utiliza para enviar operaciones de escritura asincrónicas al kernel;
  • aio_read: se utiliza para enviar operaciones de lectura asincrónicas al kernel;
  • aio_error: Obtiene el estado de la operación asincrónica actual;
  • aio_return: Obtiene el número de bytes leídos y escritos por operaciones asincrónicas.

Al principio, este programa usa el método aio_write para enviar una operación de escritura de archivo asincrónica al kernel. La estructura aiocb es la estructura de datos de la aplicación asíncrona que la aplicación pasa al kernel del sistema operativo. Aquí usamos el descriptor de archivo, el puntero del búfer aio_buf y el número de bytes que deben escribirse aio_nbytes.

struct aiocb {
   int       aio_fildes;       /* File descriptor */
   off_t     aio_offset;       /* File offset */
   volatile void  *aio_buf;     /* Location of buffer */
   size_t    aio_nbytes;       /* Length of transfer */
   int       aio_reqprio;      /* Request priority offset */
   struct sigevent    aio_sigevent;     /* Signal number and value */
   int       aio_lio_opcode;       /* Operation to be performed */
};

A continuación, usamos aio_read para leer los datos del archivo. Con este fin, hemos preparado una nueva estructura aiobc para decirle al kernel que los datos deben copiarse en el búfer, que es lo mismo que la escritura asincrónica. Después de que se inicia una lectura asincrónica, el resultado de la acción de lectura asincrónica es siempre investigado.

A continuación, cuando ejecutamos este programa, vemos una serie de caracteres impresos en la pantalla, mostrando que esta operación la realiza el kernel en segundo plano.

./aio01
writing... 
writing... 
writing... 
writing... 
writing... 
writing... 
writing... 
writing... 
writing... 
writing... 
writing... 
writing... 
writing... 
writing... 
Reading... 
Reading... 
Reading... 
Reading... 
Reading... 
Reading... 
Reading... 
Reading... 
Reading... 
Success.

Abra el archivo aio_test_xxxx en el directorio / tmp y podrá ver que este archivo ha escrito correctamente los datos que esperamos.

                                

Soporte asincrónico para sockets en Linux

La serie de funciones aio son interfaces de operación asíncronas definidas por POSIX. Desafortunadamente, la operación aio en Linux no es compatible con el nivel de sistema operativo real. Solo se implementa mediante las funciones de la biblioteca libc de GNU en el espacio de usuario mediante pthread, y solo para E / S de disco, E / S de socket no es compatible .

También hay muchos desarrolladores de Linux que intentan admitir directamente aio en el kernel del sistema operativo. Por ejemplo, una persona llamada Ben LaHaise fusionó con éxito aio en 2.5.32. Esta parte de la capacidad existe como un parche, pero aún no es compatible enchufes.

Solaris tiene otros sistemas de aio en el sistema real, pero no está seguro de cómo funciona en los sockets, especialmente cómo se compara con la E / S de disco.

Con base en las conclusiones anteriores, el soporte para operaciones asíncronas bajo Linux es muy limitado, razón fundamental por la cual se utilizan múltiples tecnologías de distribución como epoll y E / S sin bloqueo para resolver el problema de alta concurrencia y alto rendimiento de la red. / O bajo Linux.

A diferencia de Linux, Windows implementa un conjunto completo de interfaces de programación asincrónicas que admiten sockets. Este conjunto de interfaces generalmente se denomina IOCompletetionPort (IOCP). De esta forma se produce el llamado modo Proactor basado en IOCP. Ya sea en modo Reactor o Proactor, es un modo de programación de red basado en la distribución de eventos. El modo Reactor se basa en los eventos de E / S a completar, mientras que el modo Proactor se basa en los eventos de E / S completados. La esencia de ambos se basa en la idea de distribución de eventos, diseñada para ser compatible, extensible y amigable con la interfaz Un conjunto de marcos de programa.

 

En resumen, las acciones de lectura y escritura de E / S asincrónicas son completadas automáticamente por el kernel.Sin embargo, actualmente solo las operaciones asincrónicas aio simples basadas en archivos locales son compatibles con Linux.Esto también nos hace preferir el modo Reactor al escribir redes de alto rendimiento. programas. Se ha desarrollado tecnología de distribución de E / S como epoll ; e IOCP bajo Windows es una tecnología de E / S asíncrona, y esto ha producido el modo Proactor, que es tan famoso como Reactor. Con este modo, el alto rendimiento en Windows puede ser logrado Programación en red.

 

¡Aprenda lo nuevo revisando el pasado!

 

 

Supongo que te gusta

Origin blog.csdn.net/qq_24436765/article/details/104829772
Recomendado
Clasificación