IO分类与epoll介绍

2016/08/15 Network

1 简介

最近在看Nginx,着实为它的超高并发能力所折服,感觉随着“物联网”的发展,接入的设备越来越多,Nginx发展潜力巨大呀!

看的过程中也暴露了很多问题,比如IO就是其中之一,之前对IO的了解非常少,本篇就从IO分类说起,大致讨论一下Linux下IO多路复用的几种形式,看一下为什么Nginx选择epoll作为IO

2 IO分类及Linux下实现

2.1 IO分类

  • 同步
  • 异步
  • 阻塞
  • 非阻塞

区别:

阻塞/非阻塞:这是对IO的调用端而言的,调用端如果调用之后挂起,直到结果返回,就是阻塞。否则就是非阻塞。 (好比烧水和炒菜,烧水时可以走开做其他的事,但是炒菜要一直看着,不能做其他的事。例子有点牵强,好尴尬⊙﹏⊙‖∣)

同步/异步:这是对数据的传递方式而言,如果数据是调用端自己去取,就是同步,如果是处理端送达,就是异步。 (好比下馆子打包和叫外卖,打包是自己把菜带回家,叫外卖是别人送过来的,哈哈,这个例子还不错吧!)

搭配:

一般情况下,异步与非阻塞搭配,同步和阻塞、非阻塞均有搭配,下面的的内容就是同步的实例呀!

2.1 Linux <非>阻塞

改变Linux下IO的阻塞类型有2种方式:

方式一:打开时候设置
open("/dev/xxxx",O_RDWR);//阻塞
open("/dev/xxxx",O_RDWR|O_NONBLOCK );//非阻塞
//socket的fd目录:/proc/PID/fd目录

方式二:fcntl函数,动态改变
fcntl(fd,F_SETFL,fcntl(fd,F_GETFL,0)|O_NONBLOCK);//阻塞>非阻塞
fcntl(fd,F_SETFL,fcntl(fd,F_GETFL,0)&^O_NONBLOCK);//非阻塞>阻塞

注意: Linux下阻塞与非阻塞的概念,是针对文件而言,并不针对特定的读写函数。 非阻塞情况下,如果(errno==EAGAIN)表示文件读取完毕!

3 select/poll/epoll分析

3.1 优缺点及原理分析

3.1.1 select

每次执行select都需要查询整个句柄数组,先看看代码框架: while true { select()//挂起 foreach(i in a[]) handle a[i] } select的实现模式有3大缺点:

  • fd数目受限,在linux/posix_types.h头文件有这样的声明:#define __FD_SETSIZE 1024
  • 每次select,都需要将fd数组整个拷贝到内核态
  • 每次处理都要遍历整个fd数组,即使存在大量不需要处理的事件

3.1.2 poll

poll在select的基础上改进了:fd的数目有限的问题,fd上限是最大可以打开文件的数目,具体数目可以cat /proc/sys/fs/file-max来看,也可以自定义设置: struct rlimit rt; rt.rlim_max=rt.rlim_cur=MAXEPOLL setrlimit(RLIMIT_NOFILE,&rt)

poll依然使用select的阻塞IO来实现。

3.1.3 epoll

epoll是event poll的缩写,在poll的基础上继续改进,epoll通过3个方法来实现高效性:

//创建一个epoll句柄:
epollfd=epoll_create(MAXEPOLL);
//注册要监听的事件类型;
epoll_ctl(epollfd,EPOLL_CTL_ADD,listenfd,&ev)
//则是等待事件的产生
eventfds = epoll_wait(epollfd,events,MAXEPOLLEVENT,0)

实现原理

1:每次注册新的事件到epoll句柄中时,只会拷贝对应的fd进内核,保证了每个fd在整个过程中只会拷贝一次。

2:epoll内部使用红黑树组织所有fd句柄,确保log(n)的查找效率,当对应事件发生时,通过回调函数的方法将树结点加到链表中。

3:使用双向链表保存所有的触发的事件, epoll_wait只需从链表中查找相应的事件,减少了遍历数组的大小。

epoll代码结构:

epoll_create()
epoll_ctl();
while true
{
   epoll_wait(&b[])
   foreach(i in b[])
       handle b[i]
}

3.2 epoll触发类型

EPOLL触发类型可以分为:

  • 边沿触发(ET)
  • 等级触发(LT)

ET触发:只在缓冲区状态发生改变时触发相应事件(EPOLLIN/EPOLLOUT) 所以ET一次触发需要把所有数据循环读/写完!改变缓冲粗状态,方便下一次触发的识别。ET触发必须使用非阻塞IO

LT触发:只要缓冲区有数据(读)或不满(写)时,均会一直触发(EPOLLIN/EPOLLOUT) LT触发不要求IO类型。

区别:

ET相对高效,但编程难度相对较高,容易丢失数据!(比如我至今还没搞明白设置类型为EPOLLIN EPOLLOUT,在处理EPOLLOUT时,会不会影响EPOLLIN的触发)

LT通过冗余触发,提高了数据的可靠性,但是降低了效率!

结束

select/epoll为同步阻塞式,epoll可以同步阻塞也可以同步非阻塞。 epoll将所有fd保存内核空间中,触发的事件以“快捷方式”的形式进行副本保存,传到用户空间!虽然在同一时刻需要保存2份,但是在其极大地提高效率面前,这些都可以忽略不计啦!

PS:本篇内容,大部分都是看的别人的学习笔记,可能有很多理解不妥的地方,还望各路大神指正!

参考

高并发网络编程 epoll编程实现 epoll源码剖析

Search

    Post Directory