1. 前言

最近在研究基于Java的高性能异步非阻塞I/O框架Netty,因为最近做的RDMAChannel要用到其中的思想。Netty底层是通过大量的NIO实现的,通过分析底层NIO源码,发现NIO底层调用的是poll系统调用。所以本博客就来细谈select、poll、epoll系统调用。

2. NIO Selector底层源码分析

protected int doSelect(long timeout) throws IOException {if (channelArray == null)throw new ClosedSelectorException();this.timeout = timeout; // set selector timeoutprocessDeregisterQueue();if (interruptTriggered) {resetWakeupSocket();return 0;}// Calculate number of helper threads needed for poll. If necessary// threads are created here and start waiting on startLockadjustThreadsCount();finishLock.reset(); // reset finishLock// Wakeup helper threads, waiting on startLock, so they start polling.// Redundant threads will exit here after wakeup.startLock.startThreads();// do polling in the main thread. Main thread is responsible for// first MAX_SELECTABLE_FDS entries in pollArray.try {begin();try {subSelector.poll();} catch (IOException e) {finishLock.setException(e); // Save this exception}// Main thread is out of poll(). Wakeup others and wait for themif (threads.size() > 0)finishLock.waitForHelperThreads();} finally {end();}// Done with poll(). Set wakeupSocket to nonsignaled  for the next run.finishLock.checkForException();processDeregisterQueue();int updated = updateSelectedKeys();// Done with poll(). Set wakeupSocket to nonsignaled  for the next run.resetWakeupSocket();return updated;}

其中 subSelector.poll() 是select的核心,由native函数poll0实现,readFds、writeFds 和exceptFds数组用来保存底层select的结果,数组的第一个位置都是存放发生事件的socket的总数,其余位置存放发生事件的socket句柄fd。

在早期的JDK1.4和1.5 update10版本之前,Selector基于select/poll模型实现,是基于IO复用技术的非阻塞IO,不是异步IO。在JDK1.5 update10和linux core2.6以上版本,sun优化了Selctor的实现,底层使用epoll替换了select/poll。

2. I/O多路复用模型(I/O Multiplexing)

解决问题:如果一个I/O流进来,我们就开启一个进程处理这个I/O流。那么假设现在有一百万个I/O流进来,那我们就需要开启一百万个进程一一对应处理这些I/O流(——这就是传统意义下的多进程并发处理)。思考一下,一百万个进程,你的CPU占有率会多高,这个实现方式及其的不合理。所以人们提出了I/O多路复用这个模型,一个线程,通过记录I/O流的状态来同时管理多个I/O,可以提高服务器的吞吐能力。

3. select、poll、epoll简介

select,poll,epoll都是IO多路复用的机制。I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。但select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。

3.1 select实现

在这种模式中,首先不是进行read系统调动,而是进行select系统调用。当然,这里有一个前提,需要将目标网络连接,提前注册到select的可查询socket列表中。然后,才可以开启整个的IO多路复用模型的读流程。(1)进行select系统调用,查询可以读的连接。kernel会查询所有select的可查询socket列表,当任何一个socket中的数据准备好了,select就会返回。当用户进程调用了select,那么整个线程会被block(阻塞掉)。(2)用户线程获得了目标连接后,发起read系统调用,用户线程阻塞。内核开始复制数据。它就会将数据从kernel内核缓冲区,拷贝到用户缓冲区(用户内存),然后kernel返回结果。(3)用户线程才解除block的状态,用户线程终于真正读取到数据,继续执行。

select的调用过程如下所示:

  1. 使用copy_from_user从用户空间拷贝fd_set到内核空间。fd_set是需要查询的文件描述符(可以理解为Socket)。
  2. 程序注册注册回调函数__pollwait,进入阻塞状态。
  3. 遍历所有fd,调用其对应的poll方法(对于socket,这个poll方法是sock_poll,sock_poll根据情况会调用到tcp_poll,udp_poll或者datagram_poll)。
  4. 把fd_set从内核空间拷贝到用户空间。

__pollwait的主要工作就是把current(当前进程)挂到设备的等待队列中,不同的设备有不同的等待队列,对于tcp_poll来说,其等待队列是sk->sk_sleep(注意把进程挂到等待队列中并不代表进程已经睡眠了)。一旦设备收到一条消息(网络设备)或填写完文件数据(磁盘设备)后,会唤醒设备等待队列上睡眠的进程,这时current便被唤醒了。用户线程可以通过相应的fd读取到相应的可读socket中的数据了。

poll方法返回时会返回一个描述读写操作是否就绪的mask掩码,根据这个mask掩码给fd_set赋值。如果遍历完所有的fd,还没有返回一个可读写的mask掩码,则会调用schedule_timeout是调用select的进程(也就是current)进入睡眠。当设备驱动发生自身资源可读写后,会唤醒其等待队列上睡眠的进程,然后进程就会通过相应的fd读取到相应的可读的socket中的数据了。如果超过一定的超时时间(schedule_timeout指定),还是没人唤醒,则调用select的进程会重新被唤醒获得CPU,进而重新遍历fd,判断有没有就绪的fd。

总结:

select的几大缺点:

  • 每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大
  • 同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大
  • select支持的文件描述符数量太小了,默认是1024

3.2 poll实现

poll的实现和select非常相似,只是描述fd集合的方式不同,poll使用pollfd结构而不是select的fd_set结构,其他的都差不多。但是它没有最大连接数的限制,原因是它是基于链表来存储的.

3.3 epoll实现

epoll提供了三个函数,epoll_create,epoll_ctl和epoll_wait。

  • epoll_create是创建一个epoll句柄;
  • epoll_ctl是注册要监听的事件类型;
  • epoll_wait则是等待事件的产生。

对于第一个缺点,epoll的解决方案在epoll_ctl函数中。每次注册新的事件到epoll句柄中时(在epoll_ctl中指定EPOLL_CTL_ADD),会把所有的fd拷贝进内核,而不是在epoll_wait的时候重复拷贝。epoll保证了每个fd在整个过程中只会拷贝一次。

对于第二个缺点,epoll的解决方案不像select或poll一样每次都把current轮流加入fd对应的设备等待队列中,而只在epoll_ctl时把current挂一遍(这一遍必不可少)并为每个fd指定一个回调函数,当设备就绪,唤醒进程等待队列上的等待者时,然后调用这个回调函数,而这个回调函数会把就绪的fd加入一个就绪链表)。epoll_wait的工作实际上就是在这个就绪链表中查看有没有就绪的fd(利用schedule_timeout()实现睡一会,判断一会的效果,和select实现中的是类似的)

对于第三个缺点,epoll没有这个限制,它所支持的FD上限是最大可以打开文件的数目,这个数字一般远大于2048,举个例子,在1GB内存的机器上大约是10万左右,具体数目可以cat /proc/sys/fs/file-max察看,一般来说这个数目和系统内存关系很大。

总结:

(1)epoll其实也需要调用epoll_wait不断轮询就绪链表,期间也可能多次睡眠和唤醒交替,但是它是设备就绪时,调用回调函数,把就绪fd放入就绪链表中,并唤醒在epoll_wait中进入睡眠的进程。虽然都要睡眠和交替,但是select和poll在“醒着”的时候要遍历整个fd集合,而epoll在“醒着”的时候只要判断一下就绪链表是否为空就行了,这节省了大量的CPU时间。这就是回调机制(Callback)带来的性能提升。

(2)select,poll每次调用都要把fd集合从用户态往内核态拷贝一次,并且要把current往设备等待队列中挂一次,而epoll只要一次拷贝,而且把current往等待队列上挂也只挂一次(在epoll_wait的开始,注意这里的等待队列并不是设备等待队列,只是一个epoll内部定义的等待队列)。这也能节省不少的开销。

NIO详解(三):IO多路复用模型之select、poll、epoll相关推荐

  1. 详解磁盘IO、网络IO、零拷贝IO、BIO、NIO、AIO、IO多路复用(select、poll、epoll)

    文章很长,但是很用心! 文章目录 1. 什么是I/O 2. 磁盘IO 3. 网络IO 4. IO中断与DMA 5. 零拷贝IO 6. BIO 7. NIO 8. IO多路复用 8.1 select 8 ...

  2. NIO详解(一):java网络编程IO总结(BIO、NIO、AIO)

    1.基本概念 在Java网络通信中,最基本的概念就是Socket编程了.Socket又称"套接字" 向网络发出请求或者应答网络请求. Socket 和ServerSocket类库位 ...

  3. 后端开发—一文详解网络IO模型

    网络IO: 网络IO会涉及到两个系统对象 一个是 用户空间 调用 IO 的进程或者线程,另一个是 内核 空间的 内核系统, 比如 发生 IO 操作 read 时,它会经历两个阶段 1. 等待数据准备就 ...

  4. IO多路复用select/poll/epoll详解以及在Python中的应用

    IO multiplexing(IO多路复用) IO多路复用,有些地方称之为event driven IO(事件驱动IO). 它的好处在于单个进程可以处理多个网络IO请求.select/epoll这两 ...

  5. Java基础——Java NIO详解(一)

    一.基本概念 1.I/0简介 I/O即输入输出,是计算机与外界世界的一个借口.IO操作的实际主题是操作系统.在java编程中,一般使用流的方式来处理IO,所有的IO都被视作是单个字节的移动,通过str ...

  6. 【C/C++服务器开发】文件,文件描述符,I/O多路复用,select / poll / epoll 详解

    文章目录 一.前言 1.文件的概念 2.文件描述符和文件指针 文件描述符 文件描述符和文件指针的区别 文件描述符太多了怎么办 二.I/O多路复用 1.I/O多路复用的由来 不要打电话给我,有需要我会打 ...

  7. P2P技术详解(三):P2P技术之STUN、TURN、ICE详解

    本文是<P2P理论详解>系列文章中的第2篇,总目录如下: <P2P技术详解(一):NAT详解--详细原理.P2P简介> <P2P技术详解(二):P2P中的NAT穿越(打洞 ...

  8. Java网络编程和NIO详解开篇:Java网络编程基础

    老曹眼中的网络编程基础 转自:https://mp.weixin.qq.com/s/XXMz5uAFSsPdg38bth2jAA 我们是幸运的,因为我们拥有网络.网络是一个神奇的东西,它改变了你和我的 ...

  9. Java基础——Java NIO详解(二)

    一.简介 在我的上一篇文章Java NIO详解(一)中介绍了关于标准输入输出NIO相关知识, 本篇将重点介绍基于网络编程NIO(异步IO). 二.异步IO 异步 I/O 是一种没有阻塞地读写数据的方法 ...

最新文章

  1. elasticsearch 查询(match和term)
  2. 骗子防不胜防,我们该如何安全上网?
  3. 数组中只出现1次的2个数
  4. Android Studio环境搭建
  5. 【解析】UVA-548 Tree
  6. 分布式 - 分布式系统的特点
  7. pcre-7.9编译安装出错[pcrecpp.lo] Error 1
  8. 基于软件工程的Qt播放器探索(一) 概述
  9. javaweb项目电子商城购物系统课程设计任务书及成绩评定和毕业设计与实现
  10. 轻松转换矢量图的小工具Vector Magic
  11. C语言学习阶段性总结!(内附超详知识导图+重点知识详解)
  12. 网络流量分析系统的价值
  13. 幸福的小故事,愿岁月安好
  14. C# 关于递归算法 具象化
  15. d3d9.dll丢失怎么办
  16. 鹏业安装算量软件项目管理功能的操作步骤
  17. 软件工程 软件质量模型(ISO/IEC 9126)
  18. 这一季绿色智能制造“英雄联盟”已集结, 有哪些新鲜点?
  19. 锐速ServerCheck序列号生成原理
  20. ELF文件格式的详解

热门文章

  1. mySQL:两表更新(用一个表更新另一个表)的SQL语句
  2. react服务端/客户端,同构代码心得
  3. 剑指Offer - 九度1511 - 从尾到头打印链表
  4. 函数要多小才够好——谈小函数之道
  5. 137.三网?哪三网?139.网络协议三要素?141.网络安全有哪些方面?
  6. 54.什么是页面?什么是块或物理块?
  7. Matplotlib常见问题总结
  8. 全球首个AI设计药物诞生,淘宝新增垃圾识别功能……
  9. STL sort()函数详解
  10. 计算机辅助药物合成投什么期刊,中国药物化学杂志审稿快吗