【Linux】Linux的五种I/O方式

一、背景

在搞清楚Linux的五种I/O方式之前,我们首先需要弄清楚的是同步/异步、以及阻塞/非阻塞的概念,先用一个事例做一个简单的介绍。某一天你去银行办理某个业务,而这个时候有很多人在办理业务,因此你需要等待,现在有下面4种情况:

  1. 你自己主动去排队(同步),然后排队过程中你担心会漏掉你,于是不玩手机不干其他事情只是专心的等待队伍前进(阻塞)
  2. 你自己主动去排队(同步),但是在这个过程中你觉得很无聊,于是开始边玩手机边等(非阻塞)
  3. 银行有一个排号机器可以帮你排号并播放到号提醒(异步),于是你拿了一个号,但是你担心叫号的时候你听不见,于是你还是什么都不敢坐在大厅等着(阻塞)
  4. 银行有一个排号机器可以帮你排号并播放到号提醒(异步),于是你拿了一个号,然后你就在旁边玩手机甚至还出去买了瓶水回来喝(非阻塞)

在这个银行排队事务中,你就是一个处理业务的进程、而银行则是中央处理器CPU。

  • 第一种/第二种情况都是你自己在队伍里等待,由你自己主动同步的去查看银行窗口的状态,区别在于中途干不干别的;
  • 第三种/第四种情况则是银行的机器异步的通知你是否可以办理业务,区别同样在于中途干不干别的;

二、同步/异步、阻塞/非阻塞的简单总结

1、同步/异步

关注的是消息通知机制(即是由你自己主动查看消息,还是由其他人通知你消息),根本区别在于同步是主动等待消息通知,而异步是被动接受消息通知,通过回调、通知、状态等方式来被动获取信息;

  • 所谓同步,就是在发出一个*调用*时,在没有得到结果之前,该*调用*就不返回。但是一旦调用返回,就得到返回值了。换言之,由*调用者*主动等待这个*调用*的结果。
  • 异步则相反,*调用*在发出后,这个调用就直接返回了,所以没有返回结果。换言之,当一个异步过程调用发出后,调用者不会立刻得到结果。而是在*调用*发出后,*被调用者*通过状态、通知来通知调用者,或通过回调函数处理这个调用。

2、阻塞/非阻塞

关注的是程序/线程等待消息通知时的状态(即你在能不能在等待的过程中干其他事情);

3、简单总结

同步的情况下,是由处理消息者自己去等待消息是否被触发;而异步的情况下是由触发机制来通知处理消息者,所以在异步机制中,处理消息者和触发机制之间就需要一个连接的桥梁

三、Linux的五种I/O方式

       1、同步阻塞I/O(银行排队,且不能玩手机干其他事)

  • 程序阻塞直到数据准备好,并将数据从内核复制到用户进程。
  • 优点在于能及时返回数据,无延迟。缺点在于等待需要付出性能代价

       2、同步非阻塞I/O(银行排队,但是可以玩手机,但也需要不断观察自己的排队状态,轮询)

  • 每隔一段时间查询一次的主动轮训方式。将大阻塞分割成N多份阻塞,数据准备好时,拷贝过程依旧处于阻塞状态。
  • 优点在于可以利用部分等待时间,缺点在于响应延迟增大,效率低下,需要在不同操作之间切换

       3、I/O多路复用(银行排队,但增加了一个显示屏显式号码,玩会手机或出去喝口水了回来看看屏幕即可select、poll、epoll,从而避免轮询,显示屏的信息交给内核去做)

  • 多路复用的特点是通过一种机制使得一个进程能同时等待多个I/O文件描述符,基本原理就是select,poll,epoll这个function会不断的轮询I_O状态,任何一个I_O可读写便调用用户进程来处理
  • 当用户进程调用了select、poll、epoll,那么整个进程会被block,由kernel“监视”所有相关的socket,当任何一个socket中的数据准备好了,select、poll、epoll就会返回。这个时候用户进程再调用例如read操作,将数据从kernel拷贝到用户进程
  • IO多路复用是阻塞在select、epoll这样的系统调用之上,而没有阻塞在真正的I/O系统调用如recvfrom之上,实际上可以归为同步阻塞模式

       4、信号驱动I/O

  • 建立SIGIO信号处理程序,进程继续运行并不阻塞。当数据准备好时,进程会收到一个SIGIO信号,可以在信号处理函数中调用I/O操作函数处理数据。

       5、异步非阻塞I/O(银行拿号,可以离开银行去干其他的,等通知到号即可,注册一个回调函数)

  • 用户进行aio_read系统调用之后,无论内核数据是否准备好,都会直接返回给用户进程。等待数据准备好,内核直接复制数据给进程,然后从内核向进程发送一个signal或执行一个基于线程的回调函数来完成这次IO处理过程,告诉它read操作完成了。两个阶段都是非阻塞的。
  • 不会被缓存或缓冲

四、Select、Poll、Epoll 的区别

       1、Select、Poll

  • 每次调用select,先注册回调函数,并需要把fd集合从用户态拷贝到内核态,并在内核态遍历传递进来的所有fd(无可用则睡眠等待)
  • select支持的文件描述符数量默认1024
  • poll的实现和select非常相似,只是描述fd集合的方式不同,poll使用pollfd结构而不是select的fd_set结构
  • select,poll每次调用都要把fd集合从用户态往内核态拷贝一次,并且要把current往设备等待队列中挂一次

       3、Epoll        

  • epoll使用“事件”的就绪通知方式,通过epoll_ctl注册fd,一旦该fd就绪,内核就会采用类似callback的回调机制来激活该fd
  • epoll提供了三个函数,epoll_create,epoll_ctl()注册要监听的事件类型、epoll_wait()等待事件的产生、epoll_create()是创建一个epoll句柄

       4、Epoll的优势(参考链接

  • sepoll在每次注册新的事件到epoll句柄中时(在epoll_ctl中指定EPOLL_CTL_ADD),会把所有的fd拷贝进内核,而不是在epoll_wait的时候重复拷贝,保证仅拷贝一次
  • epoll不像select/poll每次都把current轮流加入fd对应的设备等待队列中,而只在epoll_ctl时把current挂一遍(这一遍必不可少)并为每个fd指定一个回调函数,当设备就绪唤醒等待队列上的等待者时,就会调用这个回调函数,而这个回调函数会把就绪的fd加入一个就绪链表)。epoll_wait的工作实际上就是在这个就绪链表中查看有没有就绪的fd(利用schedule_timeout()实现睡一会,判断一会的效果,和select实现中的第7步是类似的)
  • epoll没有fd的数量限制,上限是最大可以打开的文件数目
  • elect,poll实现需要自己不断轮询所有fd集合,直到设备就绪,期间可能要睡眠和唤醒多次交替。而epoll其实也需要调用epoll_wait不断轮询就绪链表,期间也可能多次睡眠和唤醒交替,但是它是设备就绪时,调用回调函数,把就绪fd放入就绪链表中,并唤醒在epoll_wait中进入睡眠的进程。虽然都要睡眠和交替,但是select和poll在“醒着”的时候要遍历整个fd集合,而epoll在“醒着”的时候只要判断一下就绪链表是否为空就行了,这节省了大量的CPU时间。这就是回调机制带来的性能提升
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值