欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

epoll源码剖析

程序员文章站 2022-06-14 11:20:48
...

前提:Linux内核源码:linux-2.6.11.12

epoll源码剖析

epoll是一个module,它的底层主要是通过一个文件统“eventpolls”来实现的;epoll的过程是通过三个函数来实现的:epoll_create()、epoll_ctl()、epoll_wait();
相关底层比较重要的结构体有struct eventpollstruct epitemstruct epollentrystruct __wait_queue

epoll_create()
epoll_create的底层是通过调用sys_epoll_create,这个系统调用实现的,这里是创建了一个文件系统(eventpoll),并把里面相关的节点关联起来(task_struct 、struct file_struct 、struct file 、struct dentry、struct inode 、struct eventpoll);
下面我们来进入到sys_epoll_create这个函数:

asmlinkage long sys_epoll_create(int size)
{
    int error, fd;
    struct inode *inode;
    struct file *file;

    DNPRINTK(3, (KERN_INFO "[%p] eventpoll: sys_epoll_create(%d)\n",
             current, size));

    /* Sanity check on the size parameter */
    error = -EINVAL;
    if (size <= 0)
        goto eexit_1;

    /*
     * Creates all the items needed to setup an eventpoll file. That is,
     * a file structure, and inode and a free file descriptor.
     */
    error = ep_getfd(&fd, &inode, &file);
    if (error)
        goto eexit_1;

    /* Setup the file internal data structure ( "struct eventpoll" ) */
    error = ep_file_init(file);
    if (error)
        goto eexit_2;


    DNPRINTK(3, (KERN_INFO "[%p] eventpoll: sys_epoll_create(%d) = %d\n",
             current, size, fd));

    return fd;

eexit_2:
    sys_close(fd);
eexit_1:
    DNPRINTK(3, (KERN_INFO "[%p] eventpoll: sys_epoll_create(%d) = %d\n",
             current, size, error));
    return error;
}

<1>首先进入函数我们是进行了一系列的参数检查;
<2>接下来,ep_getfd(&fd, &inode, &file),这个函数它主要的作用是分配了fd,inode,file;节选ep_getfd中的重要函数:

file = get_empty_filp();/*pipefs文件系统中的管道分配一个索引节点    对象并对其进行初始化。*/
inode = ep_eventpoll_inode();//获取一个inode节点并对其进行初始化
error = get_unused_fd();//查找一个文件描述符
dentry = d_alloc(eventpoll_mnt->mnt_sb->s_root, &this);//分配一个struct dentry结构;
d_add(dentry, inode);//让struct dentry结构中的inode*指针指向inode节点;
fd_install(fd, file);//让files_struct里面的fd对应的下标为fd的成员指向file节点;

<3>函数ep_file_init(file),是分配和初始化了一个eventpoll节点,最后让file结构中的private_data指向这个eventpoll,让他们链接起来;

epoll_ctl()
epoll_ctl的底层实现是通过sys_epoll_ctl,它的作用主要是添加新的描述符,或者删除描述符,或者修改描述符所关心的事件;
这个系统调用实现的;以下为sys_poll_ctl中节选部分:

asmlinkage long   sys_epoll_ctl(int epfd, int op, int fd,               struct epoll_event __user *event)
{
file = fget(epfd);//通过epfd找到对应的文件对象的地址;
tfile = fget(fd);//通过fd找到对应的文件对象的地址;
ep = file->private_data;//获取file指向的eventpoll;
epi = ep_find(ep, tfile, fd);//在红黑树中查找有没有一个节点中的ffd中保存着tfile和fd,找到返回ffd所对应的epi,如果没有找到,就返回NULL;
switch (op) {
    case EPOLL_CTL_ADD://添加
            error = ep_insert(ep, &epds, tfile, fd);break;
    case EPOLL_CTL_DEL://删除
            error = ep_remove(ep, epi);break;
    case EPOLL_CTL_MOD://修改关心的事件
            error = ep_modify(ep, epi, &epds);break;
    }
}

其中比较重要的函数为ep_insert,那么我们就分析一下ep_insert:

struct epitem *epi;
init_poll_funcptr(&epq.pt, ep_ptable_queue_proc);
revents = tfile->f_op->poll(tfile, &epq.pt);//执行回调函数ep_ptable_queue_proc;
ep_rbtree_insert(ep, epi);//添加当前的文件到红黑树中;

ep_ptable_queue_proc()回调函数的作用:这个函数是在f_op->poll时被调用,该函数分配一个等待队列节点epoll_entry,它的作用是:1、把它挂载到文件系统的等待队列中;2、把它挂到epitem等待队列中;3、它还注册了一个等待队列的回调函数;
ep_poll_callback(),回调函数ep_poll_callback函数的作用:当文件操作完成,唤醒当前进程之前,会调用ep_poll_callback,把eventpoll放到epitem的完成队列中;

epoll_wait()
epoll_wait的底层实现是系统调用sys_epoll_wait,它的作用主要是等待文件操作完成并返回;
它的主体是ep_poll,该函数在for循环中,检查epitem上有没有事件就绪,如果有那么就返回,如果没有就调用schedule_timeout进入休眠,知道进程被再次唤醒;

asmlinkage long sys_epoll_wait(int epfd, struct epoll_event __user *events,
                   int maxevents, int timeout)
{
    error = ep_poll(ep, events, maxevents, timeout);
}
static int ep_poll(struct eventpoll *ep, struct epoll_event __user *events,
           int maxevents, long timeout)
{
    for (;;) {
        if (!list_empty(&ep->rdllist) || !jtimeout)
                break;
        jtimeout = schedule_timeout(jtimeout);
    }
}

epoll源码剖析

epoll性能优点:
epoll机制是通过select/poll的缺陷设计的,epoll是通过一个eventpolls文件系统,将为每个用户生成的fd直接拷贝到内核,不像select每次都得重新拷贝用户的描述符到内核;通过把操作拆分成epoll_create,epoll_ctl,epoll_wait三个函数来实现,避免了这个重复拷贝的过程;我们为每一个描述符所对应的等待队列节点,都设置了回调函数,只要有事件发生,那么由驱动程序去给我调用这个回调函数,将产生就绪事件的节点插入到rdlist,就绪事件链表中,这样的话,我们就可以以O(1)的方式,直接找到就绪事件,并返回给用户;