操作系统IO模型

本文详细介绍了操作系统中的IO模型,包括阻塞IO、非阻塞IO、同步IO、多路复用(select、poll、epoll,重点讨论epoll的高效特性)、信号驱动IO以及异步IO。阐述了各种模型的工作原理,特别是epoll在解决拷贝和遍历效率问题上的优势,以及信号驱动IO和异步IO的特点。
摘要由CSDN通过智能技术生成

IO模型

阻塞IO

同步IO

要等待两个阶段

1.DMA从硬盘考贝数据到内存缓存区,然后通知CPU数据准备好了

2.CPU把内存缓冲区的数据 考到用户缓冲区,然后用户进程开始漫长的拷贝数据阶段,同步操作

用户read操作后,就会一直阻塞等待数据

非阻塞IO

1.用户进程发起read命令,cpu给dma发取读取命令,如果dma没有把数据准备好,内核会立刻给用户进程返回一个error

2.用户进程角度,虽然用户进程不会被阻塞,但是它会一直轮询,直到数据准备好了,停止轮询,开始漫长的数据拷贝,这是同步操作

用户read后,不会阻塞,但是一直轮询

同步IO

同步IO就是指 必须一直等待IO传输数据完成。一直阻塞在这里。

在同步文件IO中,线程启动一个IO操作然后就立即进入等待状态,直到IO操作完成后才醒来继续执行。

多路复用

也叫事件驱动IO,select epoll

一个进程,可以处理多条网络IO

基本原理是 内核空间 有一个内核线程,然后不断轮询 ,然后一旦这个socket有网络事件产生,就可以把他拷贝给用户进程

select

  1. select会把用户进程的所有socket文件描述符放在一个集合里,然后拷贝到内核空间,然后有一个内核线程会轮询的方式,询问网络事件,然后遍历文件描述符集合,把他们设置可读或者可写,然后拷贝到用户进程,然后用户进程再遍历集合,对网络事件处理,
  2. 所以有两次拷贝,两次遍历
  3. select 使用固定长度的 BitsMap,表示文件描述符集合,而且所支持的文件描述符的个数是有限制的,在 Linux 系统中,由内核中的 FD_SETSIZE 限制, 默认最大值为 1024,只能监听 0~1023 的文件描述符

poll

方式和上面一样,只不过存储文件描述符,是以链表的形式,解决了限制问题,但是还是受系统限制

两者每次都要遍历socket集合,寻找可读可写的socket ,on复杂度

进程调用select的时候,进程会被阻塞,当返回socket的时候,就可以继续操作了

所以说 没有网络连接的时候,select poll还是会被阻塞。

它是对通知事件进行阻塞,而不是IO调用阻塞,即不是被数据准备阻塞,socket有读写状态时,多路复用器,就会通知线程进行读写

epoll

解决select poll 的效率问题,因为他们都要遍历 拷贝,并发上来后就很低效。

1.内核使用红黑树来跟踪每个socket文件描述符的状态, 而且每次只用通过epoll_ctl(),传入一个文件描述符,而不用拷贝整个文件描述符集合,大大提高了效率,解决了拷贝问题。

2.使用了事件驱动的机制,内核维护了一个就绪链表,当文件描述符有事件发生,就加入到就绪链表

3.当用户进程调用epoll_wait的时候,就会把就绪链表拷贝到用户空间,返回的是就绪事件的个数,这样就不用再去遍历了,直接一个个取出来。

其实写个 epoll 的例子就什么都明白了,调用 epoll_wait 时阻塞,之后调用 readwrite 时是非阻塞(因为对文件描述符调用了 fcntl 将其设为非阻塞模式)。

边缘触发

使用边缘触发模式时,当被监控的 Socket 描述符上有可读事件发生时,服务器端只会从 epoll_wait 中苏醒一次,即使进程没有调用 read 函数从内核读取数据,也依然只苏醒一次,因此我们程序要保证一次性将内核缓冲区的数据读取完

水平触发

使用水平触发模式时,当被监控的 Socket 上有可读事件发生时,服务器端不断地从 epoll_wait 中苏醒,直到内核缓冲区数据被 read 函数读完才结束,目的是告诉我们有数据需要读取;

如果使用边缘触发模式,I/O 事件发生时只会通知一次,而且我们不知道到底能读写多少数据,所以在收到通知后应尽可能地读写数据,以免错失读写的机会。因此,我们会循环从文件描述符读写数据,那么如果文件描述符是阻塞的,没有数据可读写时,进程会阻塞在读写函数那里,程序就没办法继续往下执行。所以,边缘触发模式一般和非阻塞 I/O 搭配使用,程序会一直执行 I/O 操作,直到系统调用(如 readwrite)返回错误,错误类型为 EAGAINEWOULDBLOCK

一般来说,边缘触发的效率比水平触发的效率要高,因为边缘触发可以减少 epoll_wait 的系统调用次数,系统调用也是有一定的开销的的,毕竟也存在上下文的切换。

select/poll 只有水平触发模式,epoll 默认的触发模式是水平触发,但是可以根据应用场景设置为边缘触发模式。

另外,使用 I/O 多路复用时,最好搭配非阻塞 I/O 一起使用,Linux 手册关于 select 的内容中有如下说明:

在Linux下,select() 可能会将一个 socket 文件描述符报告为 “准备读取”,而后续的读取块却没有。例如,当数据已经到达,但经检查后发现有错误的校验和而被丢弃时,就会发生这种情况。也有可能在其他情况下,文件描述符被错误地报告为就绪。因此,在不应该阻塞的 socket 上使用 O_NONBLOCK 可能更安全。

简单点理解,就是多路复用 API 返回的事件并不一定可读写的,如果使用阻塞 I/O, 那么在调用 read/write 时则会发生程序阻塞,因此最好搭配非阻塞 I/O,以便应对极少数的特殊情况。

  • epoll 在内核里使用「红黑树」来关注进程所有待检测的 Socket,红黑树是个高效的数据结构,增删查一般时间复杂度是 O(logn),通过对这棵黑红树的管理,不需要像 select/poll 在每次操作时都传入整个 Socket 集合,减少了内核和用户空间大量的数据拷贝和内存分配。
  • epoll 使用事件驱动的机制,内核里维护了一个「链表」来记录就绪事件,只将有事件发生的 Socket 集合传递给应用程序,不需要像 select/poll 那样轮询扫描整个集合(包含有和无事件的 Socket ),大大提高了检测的效率。

epoll 解决了 拷贝集合问题 ,还有遍历的问题。

多路复用 只是能处理更多的连接,但是不能解决阻塞问题,所以一般配合非阻塞IO

信号驱动IO

在信号驱动 IO 模型中,当用户线程发起一个 IO 请求操作,会给对应的 socket 注册一个信号函

数,然后用户线程会继续执行其他操作,当内核数据就绪时会发送一个信号给用户线程,用户线程接收到

信号之后,便在信号函数中调用 IO 读写操作来进行实际的 IO 请求操作。

这个最终还是同步操作,因为要和cpu进行IO数据传输,数据准备过程,它可以干别的事情,传输还是要靠自己完成

异步IO

异步io 才是完全不需要等待数据准备,也不需要和cpu进行数据传输,也就是说IO的两个阶段,用户线程也不参与。 非阻塞IO,虽然名义上 立马返回,但是它会一直轮询问数据准备好了没有, io的所有操作全部由内核完成,当内核完成了数据的准备,和数据的传输,会给用户一个信号,表示数据可以使用了

非阻塞IO是立刻返回,还要一直轮询问数据是否准备好。最终IO操作还是同步的

而异步IO不用管数据是否准备好了,而是立刻返回干别的事情,

其实 阻塞非阻塞IO,多路复用最终都是同步IO

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值