epoll介绍
epoll是一种当文件描述符的内核缓冲区非空的时候,发出可读信号进行通知,当写缓冲区不满的时候,发出可写信号通知的机制。
使用DEMO
#define MAX_EVENTS 10
struct epoll_event ev, events[MAX_EVENTS];
int listen_sock, conn_sock, nfds, epollfd;
epollfd = epoll_create( 1024 );//需要判断
ev.events = EPOLLIN; //
ev.data.fd = listen_sock;
if ( epoll_ctl( epollfd, EPOLL_CTL_ADD, listen_sock, &ev ) == -1 )
{
perror( "epoll_ctl: listen_sock" );
exit( EXIT_FAILURE );
}
while(1)
{
nfds = epoll_wait( epollfd, events, MAX_EVENTS, -1 );
if ( nfds == -1 )
{
perror( "epoll_wait" );
exit( EXIT_FAILURE );
}
for ( n = 0; n < nfds; ++n )
{
if ( events[n].data.fd == listen_sock )
{
conn_sock = accept( listen_sock,
(struct sockaddr *) &local, &addrlen );
if ( conn_sock == -1 )
{
perror( "accept" );
exit( EXIT_FAILURE );
}
setnonblocking( conn_sock );
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = conn_sock;
if ( epoll_ctl( epollfd, EPOLL_CTL_ADD, conn_sock,
&ev ) == -1 )
{
perror( "epoll_ctl: conn_sock" );
exit( EXIT_FAILURE );
}
}
else
{
do_use_fd( events[n].data.fd );
}
}
}
特点:有自己独立的文件系统
启动:
1>.在linux系统启动时候,就会初始化一个eventpolls文件系统:
1.eventpoll_init():创建两块缓存==>红黑树and双链表
2.register_filesystem():在文件系统中注册一个eventpolls文件系统
3.kern_mount():挂载至linux文件系统中
API:
1.epoll_creat(size_t):size_t已经没有作用,只是兼容老版本
创建一个struct eventpoll对象:
struct eventpoll *ep = NULL;
ep_alloc(&ep);
然后分配一个未使用的文件描述符:
fd = get_unused_fd_flags(O_RDWR | (flags & O_CLOEXEC));
struct eventpoll
{
...
struct rb_root rbr; //红黑树节点
struct list_head rdlist; //epoll_wait()返回的事件,双链表
};
@@每一个epoll对象都有一个独立的eventpoll结构体,通过epoll_ctl添加事件,都挂在红黑树上(红黑树插入效率高);所有添加在epoll中的事件都会与设备驱动程序建立回调关系==>ep_poll_callback,把将发生的事件添加到rdlist中;
@@每一个添加到epoll的事件都会建立一个结构体==>
struct epitem
{
struct rb_node rbn; //红黑树节点
struct list_head rdlist; //双链表节点
struct epoll_filed ffd; //事件句柄信息
struct eventpoll *ep; //所属eventpool对象
struct epoll_event event; //期待发生的事件类型
}
2.epoll_ctl()
@ epoll_ctl()首先判断op是不是删除操作,如果不是则将event参数从用户空间拷贝到内核中.
@接下来判断用户是否设置了EPOLLEXCLUSIVE标志,这个标志是4.5版本内核才有的,主要是为了解决同一个文件描述符同时被添加到多个epoll实例中造成的“惊群”问题,详细描述可以看这里。 这个标志的设置有一些限制条件,比如只能是在EPOLL_CTL_ADD操作中设置,而且对应的文件描述符本身不能是一个epoll实例.
3.epoll_wait()
1>sys_epoll_wait():参数检查,查看epfd是否为eventpoll类型,获取eventpoll结构体
2>ep_poll():定义一个局部变量wait(等待队列节点),判断eventpoll中rdlist是否为空,为空的话将wait设为current,并加入等待队列中,将当前进程设为INTERRUPTIBLE(可中断的)之后进入sleep,一段时间唤醒后再检查等待队列空否,直到不为空或者有回调唤醒。
3>ep_insert()
往红黑树中添加节点:
向fd的等待队列上挂载一个节点,注册回调函数ep_poll_callback(),设备就绪就调用回调==>获取fd 和 epfd,将epiteam挂到对应的epfd和eventpoll中的rdlist上
rdlist上的fd ==> events[](用户传的)
epoll高效原因:
epoll的高效就在于,当我们调用epoll_ctl往里塞入百万个句柄时,epoll_wait仍然可以飞快的返回,并有效的将发生事件的句柄给我们用户。这是由于我们在调用epoll_create时,内核除了帮我们在epoll文件系统里建了个file结点,在内核cache里建了个红黑树用于存储以后epoll_ctl传来的socket外,还会再建立一个list链表,用于存储准备就绪的事件,当epoll_wait调用时,仅仅观察这个list链表里有没有数据即可。有数据就返回,没有数据就sleep,等到timeout时间到后即使链表没数据也返回。所以,epoll_wait非常高效。
epoll的LT和ET::
ET高效是因为将rdlist上的fd拷给用户后就清空,而LT模式下还会检查这些fd,如果有未完成的还会拷回rdlist中。