Epoll原理及实现
为什么会有Epoll?
- 讲Epoll可能就不得不对早期的poll和select函数进行比较了。他们同属于多路IO并发监听,个人理解就是可以同时监听多个文件描述符的状态,如果有数据到来就返回对应的文件描述符,这样避免了读取一个文件描述符被阻塞,如果轮询又造成CPU浪费的情况。
同属于多路并非监听,select函数在监听到IO事件后,只是设置了标志位,返回了整个监听描述符的数组,在这种情况下,如果需要知道是哪个文件描述符有数据了,则需要遍历整个数组查看标志位,读完数据后,需要重新设置数组,然后再进行轮询操作,这种情况在文件描述符过多的情况下会造成cpu的数组拷贝(将整个数组拷贝到用户态,然后又拷贝到内核态)和遍历数组的消耗(遍历数组找到设置标志位的文件描述符),遍历复杂的为o(n),明显随着文件描述符的数量增加而变复杂。
为了解决这一消耗,便出现了epoll这个函数,epoll在添加完监听描述符后,不需要再次添加,同时在有IO事件时,也只返回有IO变化的描述符,这样提高了效率和并发的性能。
Epoll create
- 对于epoll的创建,调用系统提供的系统函数即可:
/**
* @param size 废弃,但是不能小于0
* *
@returns 返回一个epoll句柄(即一个文件描述符)
*/
int epoll_create(int size);
系统函数调用值得注意的是,参数size不再是指定监听的文件描述符个数,它已经被忽视了,但是为了保持兼容,不能小于0。当然我们更关心的是内核态是怎么做的,在Linux源码目录fs/eventpoll.c文件中定义了对应的系统调用:
SYSCALL_DEFINE1(epoll_create, int, size)
{
if (size <= 0)
return -EINVAL;
return do_epoll_create(0);
}
可以看到函数传递的size并没有被最终传递下去,但是如小于0则会被返回错误。继续往下看函数调用:
/*****************************************************************************
Prototype : do_epoll_create
Description : 创建epoll 文件描述符
Input : int flags
Output : None
Return Value : static
Calls :
Called By :
History :
1.Date : 2019/5/3
Author :
Modification : Created function
*****************************************************************************/
static int do_epoll_create(int flags)
{
int error, fd;
struct eventpoll *ep = NULL;
struct file *file;
if (flags & ~EPOLL_CLOEXEC)
re