【Linux】几种典型的IO模型

几种典型的IO模型

  • 常见IO场景----输入和输出。
  1. 读写文件 read/write/fread/fwrite
  2. 网络接收与发送 send/recv/sendto/recvfrom
  3. 上述两种场景都有一个共同点,就是最终都会和操作系统打交道
  • IO过程
  1. 等待数据
  2. 拷贝数据到用户空间

阻塞IO

1.当程序员在代码当中调用一个IO接口,如果内核还没有将数据准备好,IO接口就会阻塞等待,把这种IO的过程称之为阻塞IO

2. IO调用的返回,预示着一定拿到了想要的数据

非阻塞IO

当程序员在代码当中发起一个非阻塞IO调用,本质上就是判断IO数据是否准备完毕

  • 准备完毕:直接拷贝,IO调用返回
  • 没准备好:直接返回(因为实际想要拷贝数据的操作,并没有完成)搭配循环使用(采用轮询的方式),直到数据拷贝完毕

阻塞vs 非阻塞

阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态。

阻塞调用是指调用结果返回之前,当前线程会被挂起,调用线程只有在得到结果之后才会返回。

非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。

信号驱动IO

信号驱动IO就是在内核将数据准备好的时候,使用SIGIO信号通知应用程序进行IO操作。

异步IO

异步IO是指数据的等待和数据的拷贝都是由内核来完成的,程序员在异步调用成功之后,可以直接操作拷贝好的数据。

aio_read 函数请求对一个有效的文件描述符进行异步读操作。这个文件描述符可以表示一个文件、套接字甚至管道。

任何IO过程中, 都包含两个步骤. 第一是等待, 第二是拷贝. 而且在实际的应用场景中, 等待消耗的时间往往都远远高于拷贝的时间。让IO更高效, 最核心的办法就是让等待的时间尽量少。

同步通信 vs 异步通信

同步和异步关注的是消息通信机制。

所谓同步,就是在发出一个调用时,在没有得到结果之前,该调用就不返回。但是一旦调用返回,就得到返回值了,换句话说,就是由调用者主动等待这个调用的结果。

异步则是相反,调用在发出之后,这个调用就直接返回了,所以没有返回结果,换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果,而是在调用发出后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用。

另外,在表明多进程多线程的时候,也提到同步和互斥,这里的同步通信和进程之间的同步是完全不相干的概念。

进程/线程同步也是进程/线程之间直接的制约关系,是为完成某种任务而建立的两个或多个线程,这个线程需要在某些位置上协调他们的工作次序而等待、传递信息所产生的制约关系,尤其是在访问临界资源的时候。

多路转接IO模型(多路复用)

多路转接的作用——可以监控多个文件描述符,当文件描述符当中有事件(读,写,异常)产生的时候,则通知调用者。

多路转接IO模型之slelect

/* According to POSIX.1-2001 */
#include <sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,
                fd_set *exceptfds, struct timeval *timeout);

参数:

  1. nfds:取值为要监控的最大文件描述符数值+1
  2. readfds:读事件集合
  3. writefds:写事件集合
  4. exceptfds:异常事件集合
  5. timeout:
struct timeval{
long tv_sec;/*seconds秒*/
long tv_usec;/*microseconds微秒*/
  • 阻塞监控:传递的参数位 NULL
  • 非阻塞监控:传递的参数为 0
eg: tv_sec!= 0 && ty_usec!= 0
  • 带有超时事件的监控方式
eg: tv_sec!= 0 || ty_usec!= 0
  • 在超时时间范围内,是阻塞监控,如果监控的文件描述符对应的事件发生,则返回;超过超时时间还没有文件描述符对应的事件产生,则select也会返回

返回值:

  1. 大于0:监控成功了,返回的数字为有事件产生的文件描述符的个数也就是就绪的文件描述符个数
  2. 等于0:监控超时
  3. 小于0:监控出错

关于fd_set

1. d_set是一个结构体,这个结构体内部是一个数组,而数组的元素类型为long类型

2. fd_set在使用的时候并不是按照数组的方式来进行使用的,而是按照位图的方式来进行使用的

3. select接口中的readfds、writefds、exceptfds参数都是fd_set类型的,在Linux源码中fd_set表示如下:

fd_set事件集合位图的使用

fd_set事件集合分为读时间集合、写事件集合和异常事件集合,不同类型的集合代表着对该集合中文件描述符相应事件的关心,例如读事件集合,会关心相应的文件描述符的读事件。

1. select 最大监控的文件描述符的数量为1024个,对应监控的文件描述符数值的范围为:[0,1023]

2. 如果关心某个文件描述符的某个事件,则将文件描述符”添加”到对应的事件集合当中。添加的含义:将文件描述符对应的比特位置为1,则表示关心该文件描述符发生什么事件。

操作fd_set位图的接口

void FD_CLR(int fd, fd_set *set);

功能:将fd文件描述符,从事件结合set当中删除掉

本质:将fd文件描述符对应的比特位置为0

int  FD_ISSET(int fd, fd_set *set);

功能:判断fd是否存在在事件集合set当中

本质:判断fd文件描述符对应的比特位是否为1

返回值:

如果为0,则不在

如果为1,则存在

void FD_SET(int fd, fd_set *set);

功能:将文件描述符fd,设置到set事件集合当中

本质:将fd文件描述符对应的比特位置为1

void FD_ZERO(fd_set *set);

功能:将事件结合set清空

本质:将所有的比特位置为0

总结

当我们在调用上述接口操作事件集合的时候,本质上是在执行自己写的代码,说明当前执行流在用户空间,当我们调用select监控事件集合的时候,需要将事件集合拷贝到内核,让内核进行监控。

select监控成功之后,事件集合的文件描述符的变化。

select_tcp程序&select的优缺点:

  1. 优点
  • 可以跨平台,因为遵守的是Posix标准
  • 超时的监控时间可以精确到微妙

     2. 缺点

  • 监控的文件描述符的数量最大是1024,取决于内核当中的__FD_SETSIZE宏
  • select采用轮询遍历事件集合的方式,所以,随着监控文件描述符的增加,性能会下降
  • select的事件集合,在监控的时候,需要从用户空间拷贝到内核空间
  • select在监控成功之后,会将未就绪的文件描述符对应的比特位置为0,不方便下次监控

多路转接IO模型之poll

poll和select相比,没有了select跨平台的优势,除此之外,poll和后面要提到的epoll相比,没有epoll性能高。

相关接口

#include <poll.h>
int poll(struct pollfd fds[], nfds_t nfds, int timeout);

参数:

  1. fds:待要监控的事件结构数组
struct pollfd{
    int fd;/*file descriptor*/待要监控的文件描述符
    short events;/*requested events*/关心文件描述符的什么事件
        指定为POLLIN表示可读事件
        指定位POLLOUT表示可写事件
        如果关注的是可读并且可写事件,则
        需将POLLIN和POLLOUT使用按位或的方式连接起来
        eg:POLLIN | POLLOUT 
    short revents;/*returned events*/
    保存poll监控该文件描述符成功之后,该文件描述符产生的事件poll函数
    会对revents进行初始化,在传递参数的时候,程序员可以不用关心
};

     2. nfds:数组有效元素的个数,本质上也是告诉poll函数,监控的范围

     3. timeout:

  • <0:阻塞监控
  • ==0:非阻塞监控
  • >0:带有超时时间的监控方式,单位是毫秒

返回值:

  1. >0:表示就绪的文件描述符的个数
  2. ==0:超时
  3. <0:表示监控出错

poll的优缺点:

  1. 优点
  • 采用了事件结构的方式,不用针对文件描述符的每一种事件进行分类监控。
  • 要监控多少文件描述符,就定义多大的事件结构数组,数组的每一个元素的类型都是一个事件结构,可以对应一个文件描述符,并且将文件描述符关心的事件也填充进去了

    2. 缺点:

  • 不能跨平台
  • 同样采用轮询遍历的方式,随着文件描述符的增多,轮询效率就会下降(监控效率也就会下降)
  • 同样也是需要将事件结构数组从用户空间拷贝到内核空间,监控成功之后还需要从内核空间拷贝到用户空间

多路转接IO模型之epoll

epoll是当今公认的在linux操作系统下,性能最高的多路转接IO模型。

相关接口

1. 创建epoll句柄

#include <sys/epoll.h>
int epoll_create(int size);

作用:

创建epoll的操作句柄,在内核当中会创建一个eventpol结构体。

参数:

size:目前的内核版本当中这个size是毫无意义的,size定义了epoll维护的结构体的大小,epoll现在采用扩容的方式。(传参的时候只要不传递负数就行)

返回值:

返回了epol的操作句柄。

2. 添加/删除/修改事件结构

#include <sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

作用:

向内核维护的红黑树当中添加或删除或修改事件结构。

参数:

  1. epfd:也就是epoll_create的返回值,epoll的操作句柄
  2. op(options):告知epoll_cl函数做什么操作
  • 添加:EPOLL_CTL_ADD
  • 删除:EPOLL_CTL_DEL
  • 修改:EPOLL_CTL_MOD

     3. fd:待操作的文件描述符

     4. event:事件结构

联合体epoll_data中的fd——保存的监控的文件描述符,是为了监控成功之后,让我们知道从双向链表当中获取的事件结构是属于哪一个文件描述符的,从而可以针对文件描述符已经产生的事件进行处理。

使用epoll_data_t的方式有两种:

a. 使用fd,但是不使用ptr。如果使用了fd,还使用ptr,ptr当中的值会覆盖fd的值,导致后续找不到该事件结构对应哪一个文件描述符。

b. 使用ptr,但是不使用fd注意:ptr的类型为void*,接收一个地址在传递给ptr的类型当中一定要包含一个字段为文件描述符,换言之,就是要传递一个结构体。

3. 监控

#include <sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout;

参数:

  1. epfd:epoll的操作句柄
  2. events:事件结构数组

作用:从双向链表当中拷贝的事件结构放到events当中

例子:如果就绪了30个文件描述符,引申含义就是双向链表当中有30个事件结构

第一种:准备的事件结构数组大小大于就绪的文件描述符的个数

eg: struct epoll_event arr[50]

第二种:准备的事件结构数组大小等于就绪的文件描述符的个数

第三种:准备的事件结构数组大小小于就绪的文件描述符的个数

eg:struct epoll event arr[10];

    3. maxevents:告知epoll,当前最多能拷贝多少个就绪的事件结构到用户准备的事件结构数组当中,防止越界

    4. timeout:

<0:阻塞监控

==0:非阻塞监控

>0:带有超时时间的监控方式,单位是毫秒

返回值:

>0:表示就绪的文件描述符的个数

==0:超时

<0:表示监控出错

epoll工作原理

  1. epoll_create创建epol操作句柄,相当于在内核当中创建了一个struct eventpoll...}的结构体。
  2. epoll操作句柄就是用户用来操作eventpoll结构体的“钥匙”。
  3. 添加/删除/修改事件结构都是针对于这个红黑树而言的,意味着红黑树的每个节点都是一个事件结构,也就是epitem结构体。
  4. epoll监控是在遍历红黑树,红黑树的遍历效率是O(lgn),n为红黑树的高度。

socket就绪条件

读就绪

1. socket内核中, 接收缓冲区中的字节数, 大于等于低水位标记SO_RCVLOWAT. 此时可以无阻塞的读该文件描述符, 并且返回值大于0;

2. socket TCP通信中, 对端关闭连接, 此时对该socket读, 则返回0;

3. 监听的socket上有新的连接请求;

4. socket上有未处理的错误.

写就绪

1. socket内核中, 发送缓冲区中的可用字节数(发送缓冲区的空闲位置大小), 大于等于低水位标记SO_SNDLOWAT, 此时可以无阻塞的写, 并且返回值大于0;

2. socket的写操作被关闭(close或者shutdown). 对一个写操作被关闭的socket进行写操作, 会触发SIGPIPE信号;

3. socket使用非阻塞connect连接成功或失败之后;

4. socket上有未读取的错误.

LT:水平触发

如果文件描述符有事件产生,epoll就会一直进行通知,直到程序员将该文件描述符所对应的事件处理掉

就好比妈妈在弟弟打游戏时催他吃饭,如果他没有立即放下手机去吃饭,妈妈会时不时地过来催一下

ET:边缘触发

如果文件描述符有事件产生,只会通知一次。直到新的数据或者新连接到来才会再次触发

就好比爸爸在弟弟打游戏时催他吃饭没如果他没有立即放下手机去吃饭,如果他没有立即放下手机去吃饭,爸爸会再下一顿饭做好的时候再过来催他

epoll的使用场景

epoll的高性能, 是有一定的特定场景的. 如果场景选择的不适宜, epoll的性能可能适得其反

对于多连接, 且多连接中只有一部分连接比较活跃时, 比较适合使用epoll

例如, 典型的一个需要处理上万个客户端的服务器, 例如各种互联网APP的入口服务器, 这样的服务器就很适合epoll.

如果只是系统内部, 服务器和服务器之间进行通信, 只有少数的几个连接, 这种情况下用epoll就并不合适, 具体要根据需求和场景特点来决定使用哪种IO模型

epoll的优点

  1. 接口使用方便: 虽然拆分成了三个函数, 但是反而使用起来更方便高效。不需要每次循环都设置关注的文件描述符, 也做到了输入输出参数分离开
  2. 数据拷贝轻量: 只在合适的时候调用 EPOLL_CTL_ADD 将文件描述符结构拷贝到内核中, 这个操作并不频繁(而select/poll都是每次循环都要进行拷贝)
  3. 事件回调机制: 避免使用遍历, 而是使用回调函数的方式, 将就绪的文件描述符结构加入到就绪队列中,epoll_wait 返回,直接访问就绪队列就知道哪些文件描述符就绪,这个操作时间复杂度O(1). 即使文件描述符数目很多, 效率也不会受到影响
  4. 没有数量限制: 文件描述符数目无上限
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Linux IO 模型是指 Linux 操作系统中的 IO 处理机制。它的目的是解决多个程序同时使用 IO 设备时的资源竞争问题,以及提供一种高效的 IO 处理方式。 Linux IO 模型主要分为三种:阻塞 IO、非阻塞 IOIO 多路复用。 阻塞 IO 指的是当程序进行 IO 操作时,会被挂起直到 IO 操作完成,这种方式简单易用,但是对于高并发环境不太适用。 非阻塞 IO 指的是程序进行 IO 操作时,如果无法立即完成,会立即返回一个错误码,程序可以通过循环不断地进行 IO 操作来实现轮询的效果。非阻塞 IO 可以提高程序的响应速度,但是会增加程序的复杂度。 IO 多路复用指的是程序可以同时监听多个 IO 设备,一旦有 IO 事件发生,就会立即执行相应的操作。IO 多路复用可以提高程序的效率,但是需要程序员手动编写代码来实现。 Linux IO 模型还有其他的实现方式,比如信号驱动 IO 和异步 IO 等。但是这些方式的使用比较复杂,一般不常用。 ### 回答2: Linux中的IO模型是指操作系统在处理输入输出的过程中所遵循的一种方式。它主要包括阻塞IO、非阻塞IO、多路复用IO和异步IO四种模型。 阻塞IO是最简单的IO模型,当一个IO操作发生时,应用程序会被阻塞,直到IO操作完成才能继续执行。这种模型的特点是简单直接,但是当有多个IO操作时会造成线程的阻塞,影响系统的性能。 非阻塞IO是在阻塞IO的基础上发展而来的,应用程序在发起一个IO操作后可以继续执行其他任务,不必等待IO操作的完成。但是需要通过轮询来不断地检查IO操作是否完成,效率相对较低。 多路复用IO使用select、poll、epoll等系统调用来监听多个IO事件,当某个IO事件就绪时,应用程序才会进行读写操作,避免了前两种模型的效率问题。多路复用IO模型适用于连接数较多时的场景,如服务器的网络通信。 异步IO是最高效的IO模型,应用程序发起一个IO操作后,立即可以执行其他任务,不需要等待IO操作的完成。当IO操作完成后,操作系统会通知应用程序进行后续处理。异步IO模型常用于高吞吐量、低延迟的应用,如高性能服务器和数据库等。 总之,Linux IO模型提供了多种不同的方式来处理输入输出,每种模型都有其适用的场景和特点。选择合适的IO模型可以提高系统的性能和效率。 ### 回答3: Linux IO模型是指操作系统中用于处理输入输出操作的一种方法或机制。在Linux中,常见的IO模型有阻塞IO、非阻塞IOIO多路复用和异步IO。 阻塞IO是最基本的IO模型,当应用程序发起一个IO请求时,它将一直阻塞等待直到IO操作完成,期间无法做其他任务。虽然简单易用,但是对资源的利用不高。 非阻塞IO在发起一个IO请求后,不会阻塞等待IO操作完成,而是立即返回并继续做其他任务。应用程序需要不断地轮询IO操作状态,直到操作完成。由于需要不断轮询,对CPU的占用较高,但可以提高资源的利用率。 IO多路复用是通过一个线程同时监听多个IO事件,从而实现并发处理多个IO操作。在IO多路复用模型中,应用程序不需要进行轮询,而是通过调用select、poll或epoll等系统调用监听多个文件描述符的IO事件。这样可以在单个线程中处理多个IO操作,提高并发性能。 异步IO模型在发起一个IO请求后,应用程序不需要等待IO操作完成,而是继续做其他任务。当IO操作完成后,操作系统会通知应用程序。异步IO模型需要操作系统的支持,效率较高,但实现较为复杂。 通过选择合适的IO模型,可以根据不同的应用场景来提高IO操作的效率和性能。例如,对于需要同时处理大量连接的服务器应用,IO多路复用是一种常见的选择;而对于需要处理大量IO操作的高性能服务器,则可以考虑使用异步IO模型

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值