Epoll详解及源码分析【转】
2017-01-11 14:45
204 查看
转自:http://blog.csdn.net/chen19870707/article/details/42525887
[/code]
[/code]
[/quote]
创建一个epoll的句柄。自从linux2.6.8之后,size参数是被忽略的。需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。
epoll的事件注册函数,它不同于select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。
第一个参数是epoll_create()的返回值。
第二个参数表示动作,用三个宏来表示:
EPOLL_CTL_ADD:注册新的fd到epfd中;
EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
EPOLL_CTL_DEL:从epfd中删除一个fd;
第三个参数是需要监听的fd。
第四个参数是告诉内核需要监听什么事,struct epoll_event结构如下:
[/code]
[/code]
events可以是以下几个宏的集合:
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
收集在epoll监控的事件中已经发送的事件。参数events是分配好的epoll_event结构体数组,epoll将会把发生的事件赋值到events数组中(events不可以是空指针,内核只负责把数据复制到这个events数组中,不会去帮助我们在用户态中分配内存)。maxevents告之内核这个events有多大,这个 maxevents的值不能大于创建epoll_create()时的size,参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。如果函数调用成功,返回对应I/O上已准备好的文件描述符数目,如返回0表示已超时。
如下图:
0:表示文件描述符未准备就绪
1:表示文件描述符准备就绪
对于水平触发模式(LT):在1处,如果你不做任何操作,内核依旧会不断的通知进程文件描述符准备就绪。
对于边缘出发模式(ET): 只有在0变化到1处的时候,内核才会通知进程文件描述符准备就绪。之后如果不在发生文件描述符状态变化,内核就不会再通知进程文件描述符已准备就绪。
Nginx 默认采用的就是ET。
[/code]
[/code]
[/code]
[/code]
epoll用kmem_cache_create(slab分配器)分配内存用来存放struct epitem和struct eppoll_entry。
当向系统中添加一个fd时,就创建一个epitem结构体,这是内核管理epoll的基本数据结构:
[/code]
[/code]
而每个epoll fd(epfd)对应的主要数据结构为:
[/code]
[/code]
eventpoll在epoll_create时创建:
[/code]
[/code]
其中,ep_alloc(struct eventpoll **pep)为pep分配内存,并初始化。
其中,上面注册的操作eventpoll_fops定义如下:
[/code]
[/code]
这样说来,内核中维护了一棵红黑树,大致的结构如下:
接着是epoll_ctl函数(省略了出错检查等代码):
[/code]
[/code]
ep_insert的实现如下:
[/code]
[/code]
init_poll_funcptr(&epq.pt, ep_ptable_queue_proc);
revents = tfile->f_op->poll(tfile, &epq.pt);
这两个函数将ep_ptable_queue_proc注册到epq.pt中的qproc。
[/code]
[/code]
执行f_op->poll(tfile, &epq.pt)时,XXX_poll(tfile, &epq.pt)函数会执行poll_wait(),poll_wait()会调用epq.pt.qproc函数,即ep_ptable_queue_proc。
ep_ptable_queue_proc函数如下:
[/code]
[/code]
其中struct eppoll_entry定义如下:
[/code]
[/code]
ep_ptable_queue_proc 函数完成 epitem 加入到特定文件的wait队列任务。
ep_ptable_queue_proc有三个参数:
struct file *file; 该fd对应的文件对象
wait_queue_head_t *whead; 该fd对应的设备等待队列(同select中的mydev->wait_address)
poll_table *pt; f_op->poll(tfile, &epq.pt)中的epq.pt
在ep_ptable_queue_proc函数中,引入了另外一个非常重要的数据结构eppoll_entry。eppoll_entry主要完成epitem和epitem事件发生时的callback(ep_poll_callback)函数之间的关联。首先将eppoll_entry的whead指向fd的设备等待队列(同select中的wait_address),然后初始化eppoll_entry的base变量指向epitem,最后通过add_wait_queue将epoll_entry挂载到fd的设备等待队列上。完成这个动作后,epoll_entry已经被挂载到fd的设备等待队列。
由于ep_ptable_queue_proc函数设置了等待队列的ep_poll_callback回调函数。所以在设备硬件数据到来时,硬件中断处理函数中会唤醒该等待队列上等待的进程时,会调用唤醒函数ep_poll_callback
[/code]
[/code]
所以ep_poll_callback函数主要的功能是将被监视文件的等待事件就绪时,将文件对应的epitem实例添加到就绪队列中,当用户调用epoll_wait()时,内核会将就绪队列中的事件报告给用户。
epoll_wait实现如下:
[/code]
[/code]
ep_send_events函数向用户空间发送就绪事件。
ep_send_events()函数将用户传入的内存简单封装到ep_send_events_data结构中,然后调用ep_scan_ready_list() 将就绪队列中的事件传入用户空间的内存。
用户空间访问这个结果,进行处理。
2.http://www.cnblogs.com/apprentice89/archive/2013/05/06/3063039.html
[code]
[/code]
[/code]
[/quote]
① int epoll_create(int size);
创建一个epoll的句柄。自从linux2.6.8之后,size参数是被忽略的。需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。
②int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epoll的事件注册函数,它不同于select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。
第一个参数是epoll_create()的返回值。
第二个参数表示动作,用三个宏来表示:
EPOLL_CTL_ADD:注册新的fd到epfd中;
EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
EPOLL_CTL_DEL:从epfd中删除一个fd;
第三个参数是需要监听的fd。
第四个参数是告诉内核需要监听什么事,struct epoll_event结构如下:
[code][code]
[/code]
[/code]
events可以是以下几个宏的集合:
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
③ int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
收集在epoll监控的事件中已经发送的事件。参数events是分配好的epoll_event结构体数组,epoll将会把发生的事件赋值到events数组中(events不可以是空指针,内核只负责把数据复制到这个events数组中,不会去帮助我们在用户态中分配内存)。maxevents告之内核这个events有多大,这个 maxevents的值不能大于创建epoll_create()时的size,参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。如果函数调用成功,返回对应I/O上已准备好的文件描述符数目,如返回0表示已超时。
3.Epoll 工作模式
①LT模式:Level Triggered水平触发
这个是缺省的工作模式。同时支持block socket和non-block socket。内核会告诉程序员一个文件描述符是否就绪了。如果程序员不作任何操作,内核仍会通知。②ET模式:Edge Triggered 边缘触发
是一种高速模式。仅当状态发生变化的时候才获得通知。这种模式假定程序员在收到一次通知后能够完整地处理事件,于是内核不再通知这一事件。注意:缓冲区中还有未处理的数据不算状态变化,所以ET模式下程序员只读取了一部分数据就再也得不到通知了,正确的用法是程序员自己确认读完了所有的字节(一直调用read/write直到出错EAGAIN为止)。如下图:
0:表示文件描述符未准备就绪
1:表示文件描述符准备就绪
对于水平触发模式(LT):在1处,如果你不做任何操作,内核依旧会不断的通知进程文件描述符准备就绪。
对于边缘出发模式(ET): 只有在0变化到1处的时候,内核才会通知进程文件描述符准备就绪。之后如果不在发生文件描述符状态变化,内核就不会再通知进程文件描述符已准备就绪。
Nginx 默认采用的就是ET。
4.实例
[code][code]
[/code]
[/code]
[code]
[/code]
[/code]
epoll用kmem_cache_create(slab分配器)分配内存用来存放struct epitem和struct eppoll_entry。
当向系统中添加一个fd时,就创建一个epitem结构体,这是内核管理epoll的基本数据结构:
[code][code]
[/code]
[/code]
而每个epoll fd(epfd)对应的主要数据结构为:
[code][code]
[/code]
[/code]
eventpoll在epoll_create时创建:
[code][code]
[/code]
[/code]
其中,ep_alloc(struct eventpoll **pep)为pep分配内存,并初始化。
其中,上面注册的操作eventpoll_fops定义如下:
[code][code]
[/code]
[/code]
这样说来,内核中维护了一棵红黑树,大致的结构如下:
接着是epoll_ctl函数(省略了出错检查等代码):
[code][code]
[/code]
[/code]
ep_insert的实现如下:
[code][code]
[/code]
[/code]
init_poll_funcptr(&epq.pt, ep_ptable_queue_proc);
revents = tfile->f_op->poll(tfile, &epq.pt);
这两个函数将ep_ptable_queue_proc注册到epq.pt中的qproc。
[code][code]
[/code]
[/code]
执行f_op->poll(tfile, &epq.pt)时,XXX_poll(tfile, &epq.pt)函数会执行poll_wait(),poll_wait()会调用epq.pt.qproc函数,即ep_ptable_queue_proc。
ep_ptable_queue_proc函数如下:
[code][code]
[/code]
[/code]
其中struct eppoll_entry定义如下:
[code][code]
[/code]
[/code]
ep_ptable_queue_proc 函数完成 epitem 加入到特定文件的wait队列任务。
ep_ptable_queue_proc有三个参数:
struct file *file; 该fd对应的文件对象
wait_queue_head_t *whead; 该fd对应的设备等待队列(同select中的mydev->wait_address)
poll_table *pt; f_op->poll(tfile, &epq.pt)中的epq.pt
在ep_ptable_queue_proc函数中,引入了另外一个非常重要的数据结构eppoll_entry。eppoll_entry主要完成epitem和epitem事件发生时的callback(ep_poll_callback)函数之间的关联。首先将eppoll_entry的whead指向fd的设备等待队列(同select中的wait_address),然后初始化eppoll_entry的base变量指向epitem,最后通过add_wait_queue将epoll_entry挂载到fd的设备等待队列上。完成这个动作后,epoll_entry已经被挂载到fd的设备等待队列。
由于ep_ptable_queue_proc函数设置了等待队列的ep_poll_callback回调函数。所以在设备硬件数据到来时,硬件中断处理函数中会唤醒该等待队列上等待的进程时,会调用唤醒函数ep_poll_callback
[code][code]
[/code]
[/code]
所以ep_poll_callback函数主要的功能是将被监视文件的等待事件就绪时,将文件对应的epitem实例添加到就绪队列中,当用户调用epoll_wait()时,内核会将就绪队列中的事件报告给用户。
epoll_wait实现如下:
[code][code]
[/code]
[/code]
ep_send_events函数向用户空间发送就绪事件。
ep_send_events()函数将用户传入的内存简单封装到ep_send_events_data结构中,然后调用ep_scan_ready_list() 将就绪队列中的事件传入用户空间的内存。
用户空间访问这个结果,进行处理。
7.参考
1.http://www.cnblogs.com/apprentice89/p/3234677.html2.http://www.cnblogs.com/apprentice89/archive/2013/05/06/3063039.html
相关文章推荐
- Epoll详解及源码分析
- Epoll详解及源码分析
- Epoll详解及源码分析
- Epoll详解及源码分析
- Epoll详解及源码分析
- Epoll详解及源码分析
- JDK源码分析之Set类详解
- epoll源码分析---sys_epoll_ctl()函数
- (转)epoll源码分析
- U-boot在开发板上移植过程详解(2)---U-boot实现源码分析(第一阶段)
- 解析从源码分析常见的基于Array的数据结构动态扩容机制的详解
- 网络驱动程序 各个函数详解及图解 DM9000A网卡驱动框架源码分析
- MySQL源码分析(3):配置文件详解
- libevent源码分析 以epoll模型为例
- proxy_epoll源代码分析 linux网络编程入门的源码分析资料,附源码
- epoll源码分析---sys_epoll_create()函数
- [置顶] 从零开始学C++之boost库(一):详解 boost 库智能指针(scoped_ptr<T> 、shared_ptr<T> 、weak_ptr<T> 源码分析)
- JDK源码分析之Set类详解——适配器模式的应用
- samba源码分析及配置详解
- live555库的rtsp服务器源码分析总结,流程详解RTSPServer .