MST

星途 面试题库

面试题:C语言Linux异步I/O操作下的事件处理

在Linux C语言异步I/O环境下,当多个异步I/O操作并发执行时,如何有效地处理I/O完成事件?请阐述使用aio_suspend函数和信号驱动的I/O完成通知这两种方式的原理、优缺点以及适用场景,并分别给出简单的代码示例。
27.7万 热度难度
编程语言C

知识考点

AI 面试

面试题答案

一键面试

aio_suspend函数方式

  1. 原理:aio_suspend函数允许程序暂停执行,直到一个或多个异步I/O请求完成。它接受一个指向aiocb结构数组的指针,以及数组中的元素数量,阻塞调用线程,直到指定的异步I/O操作中的任何一个完成。
  2. 优点
    • 简单直接,对于需要集中处理多个异步I/O完成事件的场景很方便。
    • 不会产生信号处理函数带来的异步复杂性问题,例如重入问题。
  3. 缺点
    • 调用aio_suspend会阻塞调用线程,可能影响程序的并发性能,特别是在有其他需要并发执行的任务时。
    • 灵活性相对较差,不能像信号驱动方式那样实时响应I/O完成事件。
  4. 适用场景:适用于对响应实时性要求不高,且希望集中处理多个异步I/O完成事件的场景,例如批量文件读取操作完成后的统一处理。
  5. 代码示例
#include <stdio.h>
#include <stdlib.h>
#include <aio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>

#define BUFFER_SIZE 1024
#define NUM_REQUESTS 3

int main() {
    int i, fd;
    char buffer[BUFFER_SIZE];
    struct aiocb aiocb_list[NUM_REQUESTS];

    for (i = 0; i < NUM_REQUESTS; i++) {
        fd = open("testfile", O_RDONLY);
        if (fd == -1) {
            perror("open");
            exit(EXIT_FAILURE);
        }

        aiocb_list[i].aio_fildes = fd;
        aiocb_list[i].aio_buf = buffer;
        aiocb_list[i].aio_nbytes = BUFFER_SIZE;
        aiocb_list[i].aio_offset = i * BUFFER_SIZE;
        aiocb_list[i].aio_sigevent.sigev_notify = SIGEV_NONE;

        if (aio_read(&aiocb_list[i]) == -1) {
            perror("aio_read");
            close(fd);
            exit(EXIT_FAILURE);
        }
    }

    if (aio_suspend(aiocb_list, NUM_REQUESTS, NULL) == -1) {
        perror("aio_suspend");
        for (i = 0; i < NUM_REQUESTS; i++) {
            aio_cancel(aiocb_list[i].aio_fildes, &aiocb_list[i]);
        }
        exit(EXIT_FAILURE);
    }

    for (i = 0; i < NUM_REQUESTS; i++) {
        if (aio_error(&aiocb_list[i]) == 0) {
            ssize_t bytes_read = aio_return(&aiocb_list[i]);
            printf("Read %zd bytes in request %d\n", bytes_read, i);
        } else {
            perror("aio_error");
        }
        close(aiocb_list[i].aio_fildes);
    }

    return 0;
}

信号驱动的I/O完成通知方式

  1. 原理:通过设置aiocb结构中的aio_sigevent成员,指定当异步I/O操作完成时发送的信号。程序通过安装信号处理函数来捕获该信号,在信号处理函数中处理I/O完成事件。
  2. 优点
    • 实时性强,I/O完成事件可以立即通过信号处理函数进行处理,不会阻塞主线程。
    • 提高程序的并发性能,主线程可以继续执行其他任务。
  3. 缺点
    • 信号处理函数编写复杂,需要处理重入问题,因为信号处理函数可能在任何时候被调用。
    • 信号处理函数的上下文与主线程上下文不同,可能需要额外的同步机制来共享数据。
  4. 适用场景:适用于对实时性要求高,需要及时响应I/O完成事件的场景,例如网络服务器在接收数据完成后立即处理。
  5. 代码示例
#include <stdio.h>
#include <stdlib.h>
#include <aio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <signal.h>

#define BUFFER_SIZE 1024
#define NUM_REQUESTS 3

void io_completion_handler(int signum, siginfo_t *info, void *context) {
    struct aiocb *aiocbp = (struct aiocb *)info->si_value.sival_ptr;
    ssize_t bytes_read = aio_return(aiocbp);
    if (bytes_read != -1) {
        printf("Read %zd bytes\n", bytes_read);
    } else {
        perror("aio_return");
    }
}

int main() {
    int i, fd;
    char buffer[BUFFER_SIZE];
    struct aiocb aiocb_list[NUM_REQUESTS];
    struct sigaction sa;

    sa.sa_sigaction = io_completion_handler;
    sa.sa_flags = SA_SIGINFO;
    sigemptyset(&sa.sa_mask);
    if (sigaction(SIGUSR1, &sa, NULL) == -1) {
        perror("sigaction");
        exit(EXIT_FAILURE);
    }

    for (i = 0; i < NUM_REQUESTS; i++) {
        fd = open("testfile", O_RDONLY);
        if (fd == -1) {
            perror("open");
            exit(EXIT_FAILURE);
        }

        aiocb_list[i].aio_fildes = fd;
        aiocb_list[i].aio_buf = buffer;
        aiocb_list[i].aio_nbytes = BUFFER_SIZE;
        aiocb_list[i].aio_offset = i * BUFFER_SIZE;
        aiocb_list[i].aio_sigevent.sigev_notify = SIGEV_SIGNAL;
        aiocb_list[i].aio_sigevent.sigev_signo = SIGUSR1;
        aiocb_list[i].aio_sigevent.sigev_value.sival_ptr = &aiocb_list[i];

        if (aio_read(&aiocb_list[i]) == -1) {
            perror("aio_read");
            close(fd);
            exit(EXIT_FAILURE);
        }
    }

    while (1) {
        sleep(1);
    }

    return 0;
}