Linux系统--五种IO模型

1、简介

  Linux IO 模型根据实现的功能可以划分为为阻塞 IO、 非阻塞 IO、 信号驱动 IO, IO 多路复用和异
步 IO。 根据等待 IO 的执行结果进行划分, 前四个 IO 模型又被称为同步 IO,如下图:
在这里插入图片描述

2、详细介绍

2.1 阻塞IO

  在阻塞IO模型中,调用read或write时,如果没有数据可读或写,进程会被挂起,直到数据可用。这是最简单的IO模型。以阻塞读为例: 进程进行 IO 操作时(如 read 操作), 首先会发起一个系统调用, 从而转到内核空间进行处理, 内核空间的数据没有准备就绪时, 进程会被阻塞, 不会继续向下执行, 直到内核空间的数据准备完成后, 数据才会从内核空间拷贝到用户空间, 最后返回用户进程, 由用户空间进行数据的处理, 如下图所示:
在这里插入图片描述
  示例代码如下:

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

int main() {
    char buffer[100];
    int fd = open("file.txt", O_RDONLY);
    
    // 阻塞调用
    ssize_t bytes_read = read(fd, buffer, sizeof(buffer));
    if (bytes_read < 0) {
        perror("read error");
    } else {
        printf("Read %zd bytes: %s\n", bytes_read, buffer);
    }

    close(fd);
    return 0;
}

2.2 非阻塞IO

  在非阻塞IO模型中,调用read或write时,如果没有数据可读或写,返回-1,errno设置为EAGAIN。进程不会被挂起。和阻塞 IO 模型不同, 非阻塞 IO 进行 IO 操作时, 如果内核数据没有准备好, 内核会立即向进程返回 err, 不会进行阻塞; 如果内核空间数据准备就绪, 内核会立即把数据返回给用户空间的进程, 如下图所示:
在这里插入图片描述
  示例代码:

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>

int main() {
    char buffer[100];
    int fd = open("file.txt", O_RDONLY | O_NONBLOCK);
    
    // 非阻塞调用
    ssize_t bytes_read = read(fd, buffer, sizeof(buffer));
    if (bytes_read < 0) {
        if (errno == EAGAIN) {
            printf("No data available\n");
        } else {
            perror("read error");
        }
    } else {
        printf("Read %zd bytes: %s\n", bytes_read, buffer);
    }

    close(fd);
    return 0;
}

2.3 IO复用

  通常情况下使用 select()、 poll()、epoll()函数实现 IO 多路复用。 这里以 select 函数为例进行讲解, 使用时可以对 select 传入多个描述符, 并设置超时时间。 当执行 select 的时候, 系统会发起一个系统调用, 内核会遍历检查传入的描述符是否有事件发生(如可读、 可写事件) 。 如有, 立即返回, 否则进入睡眠状态, 使进程进入阻塞状态, 直到任何一个描述符事件产生后(或者等待超时) 立刻返回。 此时用户空间需要对全部描述符进行遍历, 以确认具体是哪个发生了事件, 这样就能使用一个进程对多个 IO 进行管理, 如下图所示:
在这里插入图片描述
  示例代码:

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/select.h>

int main() {
    int fd1 = open("file1.txt", O_RDONLY);
    int fd2 = open("file2.txt", O_RDONLY);
    
    fd_set readfds;
    FD_ZERO(&readfds);
    FD_SET(fd1, &readfds);
    FD_SET(fd2, &readfds);

    // 等待文件描述符就绪
    int max_fd = (fd1 > fd2) ? fd1 : fd2;
    int retval = select(max_fd + 1, &readfds, NULL, NULL, NULL);

    if (retval == -1) {
        perror("select error");
    } else if (retval) {
        if (FD_ISSET(fd1, &readfds)) {
            printf("file1.txt is ready for reading\n");
        }
        if (FD_ISSET(fd2, &readfds)) {
            printf("file2.txt is ready for reading\n");
        }
    } else {
        printf("No file descriptors are ready\n");
    }

    close(fd1);
    close(fd2);
    return 0;
}

2.4 信号驱动IO

  信号驱动 IO 顾名思义与信号相关。 系统在一些事件发生之后, 会对进程发出特定的信号,而信号与处理函数相绑定, 当信号产生时就会调用绑定的处理函数。 例如在 Linux 系统任务执行的过程中可以按下 ctrl+C 来对任务进行终止, 系统实际上是对该进程发送一个 SIGINT 信号,该信号的默认处理函数就是退出当前程序。具体到 IO 模型上, 可以对 SIGIO 信号注册相应的信号处理函数, 并打开对应描述符的信号驱动。 每当有 IO 数据产生时, 系统就会发送一个 SIGIO 信号, 进而调用相应的信号处理函数,从而在这个处理函数中对数据进行读取, 如下图 所示:
在这里插入图片描述
  示例代码:

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>

void handler(int sig) {
    printf("Data is available to read!\n");
}

int main() {
    signal(SIGIO, handler);
    
    int fd = open("file.txt", O_RDONLY | O_NONBLOCK);
    fcntl(fd, F_SETFL, O_ASYNC); // 设置异步IO
    
    // 允许进程接收SIGIO信号
    fcntl(fd, F_SETOWN, getpid());

    // 主循环,保持进程运行
    while (1) {
        pause(); // 等待信号
    }

    close(fd);
    return 0;
}

2.5 异步IO

  在异步IO模型中,进程提交IO请求后可以继续执行,IO操作在后台完成,完成后操作系统会通知进程。aio_read 函数常常用于异步 IO, 当进程使用 aio_read 读取数据时, 如果数据尚未准备就绪就立即返回, 不会阻塞。 若数据准备就绪就会把数据从内核空间拷贝到用户空间的缓冲区中,然后执行定义好的回调函数对接收到的数据进行处理。
在这里插入图片描述
  示例代码:

#include <stdio.h>
#include <aio.h>
#include <fcntl.h>
#include <string.h>

int main() {
    struct aiocb cb;
    char buffer[100];
    int fd = open("file.txt", O_RDONLY);

    memset(&cb, 0, sizeof(struct aiocb));
    cb.aio_fildes = fd;
    cb.aio_buf = buffer;
    cb.aio_nbytes = sizeof(buffer);

    // 提交异步读取
    if (aio_read(&cb) == -1) {
        perror("aio_read error");
    }

    // 检查IO是否完成
    while (aio_error(&cb) == EINPROGRESS) {
        // 可以执行其他操作
        printf("Doing other work...\n");
        sleep(1);
    }

    // 获取结果
    ssize_t bytes_read = aio_return(&cb);
    printf("Read %zd bytes: %s\n", bytes_read, buffer);

    close(fd);
    return 0;
}

3、总结

  Linux中的五种IO模型主要包括阻塞IO、非阻塞IO、IO复用、信号驱动IO和异步IO。

  • 阻塞IO:调用read或write时,如果没有数据可读或写,进程会被挂起,直到操作完成。这种模型简单易用,但在高并发场景下效率低。
  • 非阻塞IO:在调用read或write时,如果没有数据可读或写,返回-1,errno设置为EAGAIN。这样,进程可以继续执行其他任务,但需要轮询或管理状态。
  • IO复用:使用select、poll或epoll等系统调用,可以监视多个文件描述符,等待其中一个或多个就绪。这种方法适合处理大量并发连接,提高了效率。
  • 信号驱动IO:通过设置信号处理程序,进程在文件描述符就绪时会收到信号。这种模型减少了轮询,但处理信号的复杂性增加。
  • 异步IO:提交IO请求后,进程可以继续执行,IO操作在后台完成。当完成后,操作系统会通知进程。这种方式能有效利用CPU资源,但实现较复杂。

  这些模型各有优缺点,选择时需根据具体应用场景进行权衡。

参考资料:《itop-3568开发板驱动开发指南v2.0》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值