Linux系统接口--五种IO模型

server/2024/9/24 16:04:43/

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》


http://www.ppmy.cn/server/121415.html

相关文章

Unity 设计模式 之 行为型模式 -【访问者模式】【模板模式】【策略模式】

Unity 设计模式 之 行为型模式 -【访问者模式】【模板模式】【策略模式】 目录 Unity 设计模式 之 行为型模式 -【访问者模式】【模板模式】【策略模式】 一、简单介绍 二、访问者模式&#xff08;Visitor Pattern&#xff09; 1、什么时候使用访问者模式 2、使用访问者模…

Flink的反压机制:底层原理、产生原因、排查思路与解决方案

反压&#xff08;Backpressure&#xff09;是流处理框架&#xff08;如 Apache Flink&#xff09;中非常重要的概念。反压的产生和有效处理&#xff0c;直接影响整个流处理作业的稳定性和性能。本文将从 Flink 的底层原理、反压产生的原因、如何排查反压问题&#xff0c;以及如…

【设计模式】UML类图

目录 前言 一、类图概述 二、类图的作用 三、类图表示法 四、类之间关系的表示方法 1. 关联关系 1.1 单向关联 1.2 双向关联 1.3 自关联 2. 聚合关系 3. 组合关系 4. 依赖关系 5. 继承关系 6. 实现关系 总结 前言 统一建模语言&#xff08; Unified Modeling La…

基于R语言的统计分析基础:使用SQL语句操作数据集

在使用R语言数据分析时&#xff0c;可以融合SQL语言使数据聚集操作更加便利&#xff0c;同时也可以增加对SQL语句的熟悉。借助sqldf、DBI、RSDLite等包&#xff0c;可以在R环境中直接运用SQL语句&#xff0c;轻松实现数据的分组统计、汇总分析&#xff0c;SQL的强大查询能力简化…

【STM32】定时器

一、 定时器概述 定义 ​ 设置等待时间&#xff0c; 到达后则执行指定操作的硬件。 STM32F407 的定时器有以下特征 ​ 具有基本的定时功能&#xff0c; 也有 PWM 输出&#xff08;灯光亮度控制、 电机的转速&#xff09;、 脉冲捕获功能&#xff08;红外捕捉&#xff09;。…

6--SpringBootWeb案例(详解)

目录 环境搭建 部门管理 查询部门 接口文档 代码 删除部门 接口文档 代码 新增部门 接口文档 代码 已有前端&#xff0c;根据接口文档完成后端功能的开发 成品如下&#xff1a; 环境搭建 1. 准备数据库表 (dept 、 emp) -- 部门管理 create table dept( id int un…

仿黑神话悟空跑动-脚下波纹特效(键盘wasd控制走动)

vue使用three.js实现仿黑神话悟空跑动-脚下波纹特效 玩家角色的正面始终朝向鼠标方向&#xff0c;且在按下 W 键时&#xff0c;玩家角色会朝着鼠标方向前进 空格建跳跃 <template><div ref"container" class"container" click"onClick"…

在vue中嵌入vitepress,基于markdown文件生成静态网页从而嵌入社团周报系统的一些想法和思路

什么是vitepress vitepress是一种将markdown文件渲染成静态网页的技术 其使用仅需几行命令即可 //在根目录安装vitepress npm add -D vitepress //初始化vitepress&#xff0c;添加相关配置文件&#xff0c;选择主题&#xff0c;描述&#xff0c;框架等 npx vitepress init //…