Linux epoll机制初识
文章仅仅用于个人的学习记录,基本上内容都是网上各个大神的杰作,此处摘录过来以自己的理解学习方式记录一下。参考文献: http://www.zhihu.com/question/32163005 http://www.zhihu.com/question/28594409 http://blog.csdn.net/zhang_shuai_201
·
文章仅仅用于个人的学习记录,基本上内容都是网上各个大神的杰作,此处摘录过来以自己的理解学习方式记录一下。
参考文献:
1、前言。
a、IO相关认识。
I/O操作比如最常见的read和write,通常来说I/O操作是阻塞I/O的,也就是说当你调用read时,如果没有收到数据,那么线程或者进程就会被挂起直到收到数据(再往下执行).这样当服务器处理很多链接的时候,对内存的消耗(线程太多)太大和CPU的时间占用(线程之间切换)很多。后来就引入了非阻塞I/O概念。Linux通过调用系统的函数ioctl把其设置为非阻塞模式即可。这时当你调用read时,如果有数据收到就返回,如果没有数据收到,就立刻返回一个错误。这样就不会阻塞线程了,但是还是需要不断的轮训来读取或者写入。此时就引来了I/O多路复用概念。调用如:select、poll等函数,传入多个文件描述符,如果有描述符就绪则返回(去做自己的处理),否则阻塞直到超时。这样在处理很多连接的时候,只需要一个线程监控就绪状态,然后对相应状态的每个连接再单独开线程处理即可。这样大大减少内存开销。
b、IO多路复用概念。
传统的并发处理模型:没有一个新的I/O流我就新建一个进程管理去处理它,随时跟踪它的状态。这样就会导致,资源浪费、管理混乱等等问题。
epoll机制是一种I/O多路复用技术。I/O多路复用。其实这个复用是指的复用某个线程的意思。
整体的意思:单个线程通过记录跟踪每个I/O流的状态,来管理多个I/O流.一般来说这些机制都是监控的多个描述符fd的状态(如时候准备读、写等)。
当它处于某种状态时候去顺序的做相应的处理。(配合Socket的时候需要是非阻塞的,只有当系统通知谁有有效数据才取处理)
好处:提高服务器的吞吐能力,一个进程就可以管理多个任务要求等等。
select、poll、epoll都是这种模型。(按照这个顺序依次出现改进的)
个人认为网上比较形象的例子:
一个酒吧服务员(一个线程),前面趴了一群醉汉,突然一个吼一声“倒酒”(事
件),你小跑过去给他倒一杯,然后随他去吧,突然又一个要倒酒,
你又过去倒上,就这样一个服务员服务好多人,有时没人喝酒,服务
员处于空闲状态,可以干点别的玩玩手机。至于epoll与select,poll的区别在于后
两者的场景中醉汉不说话,你要挨个问要不要酒,没
时间玩手机了。io多路复用大概就是指这几个醉汉共用一个服务员。这样来看epoll除了可以复用服
务
员,还能有效避免很多无用的操作。
2、select、poll、epoll优缺点。
epoll可以说是select和poll的增强版。暂未查找学习select、poll的具体用法和实现,此处先做个概念上的理解记录。
select的缺点:
a、每次调用select都需要把用户空间的fd集合拷贝到内核空间。这样当fd很多事内存开销较大。
b、每次调用select时当有任何一个I/O流出现了数据,都需要在内核中遍历传进来所有的fd。也会导致开销很大。
c、select支持的文件描述符只有默认的1024.
d、select不是线程安全的,无法回收已经加入但后来不需要的socket.
由于当时硬件比较弱,select已经很长一段时间能满足需求了。后来就该进程了poll。
poll优缺点:
a、poll改进了select的最多1024个连接的限制。可以自己设定。
b、改进来fd描述的方式。
c、但是仍然线程不安全,且有时间发生时需要循环全部的fd。
然后就出来了epoll,修复了绝大部分上面的两种实现的问题。最主要的如:
a、epoll现在不仅告诉我们监控的I/O流里面有数据,还会告诉我们具体实那个I/O流,不需要重复的循环去找了。
大概是每个fd注册一个回调,fd就绪时调用这个回调函数,把fd假如导游一个就绪列表中。epoll_wait其实是在查询这个链表。
b、解决了查询时重复赋值fd到内核这个问题,每次注册新事件到epoll句柄时,会把所有的fd拷贝到内核,这样保证只拷贝一次。
大概是采用了mmap方式。
c、epoll现在是线程安全的。
但是epoll好像只有linux支持。
总的来说:
select、poll实现需要不断地轮训所有的fd,直到设备就绪。期间可能要睡眠唤醒多次。而epoll也需要调用epoll_wait不断轮询就绪链表,期间
也可能多次睡眠和唤醒,但是它是当设备就绪时,调用回调函数把就绪的fd放入就绪列表中,并唤醒在epoll_wait中进入睡眠的进程。相比如另外两种
这个只需要遍历就绪列表。
select、poll每次都要调用把fd集合从用户态往内核态拷贝一次,并且把current往设备等待队列挂一次。而epoll只需要拷贝一次、挂载一次。
3、epoll的基本使用。
3.1、int epoll_create(int size)
创建一个epoll的句柄,自从Linux2.6.8以后size是忽略的。 当创建好epoll句柄之后,它就会占用一个fd值。所以在使用完epoll之后,调用close
关闭,否则可能导致fd不够用。
3.2、int epoll_ctl(int epfd,int op,int fd,struct epoll_event *event)
epoll的事件注册函数,要先注册需要监听的事件。
epfd: epoll_create()的返回值。
op:表示动作,有三个宏
EPOLL_CTL_ADD :注册新的fd到epfd当中。
EPOLL_CTL_MOD :修改已经注册的fd的监听事件。
EPOLL_CTL_DEL :从epfd中删除一个fd。
fd :需要监听的I/O流的fd。
event:告诉内核需要监听的事件。
//保存触发事件的某个文件描述符相关的数据(与具体使用方式有关)
typedef union epoll_data {
void *ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
} epoll_data_t;
//感兴趣的事件和被触发的事件
struct epoll_event {
__uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
对于epoll_event结构体的 events变量有以下几个宏
EPOLLIN :表示对应的文件描述符可读。
EPOLLOUT :表示对应的文件描述符可写。
EPOLLPRI :表示对应的文件描述符有紧急数据可读。
EPOLLERR :表示对应的文件描述符发生错误。
EPOLLHUP :表示对应的文件描述符被挂断。
EPOLLLET :将EPOLL设为边缘触发模式Edge Triggered,这是相对于水平触发Level Triggered来说的。
EPOLLONESHOT :只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个fd的话,需要再次把它 加入到EPOLL队列里面。
3.3、int epoll_wait(int epfd,struct epoll_event* events,int maxevents,int timeout)
收集在epoll监控的时间中已经发生的事件。
epfd:创建的epoll的fd。
events: 分配好的epoll_event结构体数组,epoll将会把发生的时间赋值到events数组中。
maxevents: 告诉内核这个events有多大,这个大小不能超过epoll_create()时传入的size。
timeout: 超时设置单位ms.可设置如下三种
0: 会立即返回。
-1:将会阻塞。
timeout
: 阻塞
timeout个时长,如果在这期间发生则返回,如果超过时间则返回。
如果函数调用成功就返回对应I/O上已经准备好的文件描述符的数目。返回0表示已超时。
3.4、epoll的两种工作方式。
LT level triggered 水平触发模式,
同时支持阻塞和非阻塞的socket。在这种模式中,内核告诉你一个文件描述符是否就绪了,然后你可以对这
个就绪 的fd进行I/O操作,如果你不
做任何操作,讷河还是会继续通知你。(没处理这个流还是一直通知你)
ET edge triggered 边缘触发模式
只支持非阻塞的socket。效率比LT高。这种工作模式下,当从epoll_wait调用获取到事件后,如果没有把这次事件 对应的套接字处理完,那么
在这
个套接字中没有心的时间再次到来时,ET模式下是无法再次从epoll_wait调用中获取这 个事件的。而LT只要有数据就总可以获取。
文章开头处连接里面的大神的图,个人觉得很清晰明了:
更多推荐
已为社区贡献2条内容
所有评论(0)