第二章epoll

epoll_create:函数实现分析

/*
 * Open an eventpoll file descriptor.
 */
SYSCALL_DEFINE1(epoll_create1, int, flags)
{
    int error, fd;
    struct eventpoll *ep = NULL;
    struct file *file;

    /* Check the EPOLL_* constant for consistency.  */
    BUILD_BUG_ON(EPOLL_CLOEXEC != O_CLOEXEC);

    if (flags & ~EPOLL_CLOEXEC)
        return -EINVAL;
    /*
     * Create the internal data structure ("struct eventpoll").
     */ //创建存储所有监听事件所需要的eventpoll文件结构,为ep分配内存并进行初始化
    error = ep_alloc(&ep);
    if (error < 0)
        return error;
    //epollfd本身并不存在一个真正的文件与之对应, 所以内核需要创建一个
    //"虚拟"的文件, 并为之分配真正的struct file结构,而且有真正的fd. 及innode节点
    //这里2个参数比较关键:
    //eventpoll_fops, fops就是file operations, 就是当你对这个文件(这里是虚拟的)进行操作(比如读)时,
    //fops里面的函数指针指向真正的操作实现, epoll只实现了poll和release(就是close)操作,其它文件系统操作都有VFS全权处理了.
    //ep, ep就是struct epollevent, 它会作为一个私有数据保存在struct file的private指针里面.(sys_epoll_ctl会取用)
    //其实, 就是为了能通过fd找到struct file, 通过struct file能找到eventpoll结构.
    /*
     * Creates all the items needed to setup an eventpoll file. That is,
     * a file structure and a free file descriptor.
     */
    fd = get_unused_fd_flags(O_RDWR | (flags & O_CLOEXEC));
    if (fd < 0) {
        error = fd;
        goto out_free_ep;
    }
     //创建file实例,以及匿名inode节点和dentry等数据结构,epoll可以看成一个文件
    //(匿名文件)因此我们可以看到epoll_create会返回一个fd。
    //epoll所管理的所有的fd都是放在一个大的结构(红黑树)中,
 
    //简要说一下file/dentry/inode,当进程打开一个文件时,内核就会为该进程分配一个file结构,
    //表示打开的文件在进程的上下文,然后应用程序会通过一个int类型的文件描述符来访问这个结构,
    //实际上内核的进程里面维护一个file结构的数组,而文件描述符就是相应的file结构在数组中的下标。
    //dentry结构(称之为“目录项”)记录着文件的各种属性,比如文件名、访问权限等,每个文件都只有一个dentry结构,
    //然后一个进程可以多次打开一个文件,多个进程也可以打开同一个文件,
    //这些情况,内核都会申请多个file结构,建立多个文件上下文。但是,对同一个文件来说,
    //无论打开多少次,内核只会为该文件分配一个dentry。所以,file结构与dentry结构的关系是多对一的。
    //同时,每个文件除了有一个dentry目录项结构外,还有一个索引节点inode结构,
    //里面记录文件在存储介质上的位置和分布等信息,每个文件在内核中只分配一个inode。
    //dentry与inode描述的目标是不同的,一个文件可能会有好几个文件名(比如链接文件),
    //通过不同文件名访问同一个文件的权限也可能不同。dentry文件所代表的是逻辑意义上的文件,
    //记录的是其逻辑上的属性,而inode结构所代表的是其物理意义上的文件,记录的是其物理上的属性。
    //dentry与inode结构的关系是多对一的关系。

     /*/epoll_create为什么会返回一个新的fd?因为它就是在这个
     叫做"eventpollfs"的文件系统里创建了一个新文件
/*/
     file = anon_inode_getfile("[eventpoll]", &eventpoll_fops, ep,
                 O_RDWR | (flags & O_CLOEXEC));
    if (IS_ERR(file)) {
        error = PTR_ERR(file);
        goto out_free_fd;
    }
    ep->file = file;
    //建立fd和file的关联关系
    fd_install(fd, file);
    return fd;

out_free_fd:
    put_unused_fd(fd);
out_free_ep:
    ep_free(ep);
    return error;
}

 

 

epoll_ctrl:分析

/*
 * The following function implements the controller interface for
 * the eventpoll file that enables the insertion/removal/change of
 * file descriptors inside the interest set.
 */
SYSCALL_DEFINE4(epoll_ctl, int, epfd, int, op, int, fd,
        struct epoll_event __user *, event)
{
    int error;
    int full_check = 0;
    struct fd f, tf;
    struct eventpoll *ep;
    struct epitem *epi;
    struct epoll_event epds;
    struct eventpoll *tep = NULL;

    error = -EFAULT;
     //判断参数的合法性,将 __user *event 复制给 epds。
     //OP 为add mod时 必须copy 数据成功
    if (ep_op_has_event(op) &&
        copy_from_user(&epds, event, sizeof(struct epoll_event)))
        goto error_return;

    error = -EBADF;
    f = fdget(epfd);
    if (!f.file)
        goto error_return;

    /* Get the "struct file *" for the target file */
    tf = fdget(fd);
    if (!tf.file)
        goto error_fput;

    /* The target file descriptor must support poll
        必须有poll 接口获取事件mask
         //...epoll就是封装poll机制,select poll也都是封装poll机制
    */
    error = -EPERM;
    if (!tf.file->f_op->poll)
        goto error_tgt_fput;

    /* Check if EPOLLWAKEUP is allowed */
    if (ep_op_has_event(op))
        ep_take_care_of_epollwakeup(&epds);

    /*
     * We have to check that the file structure underneath the file descriptor
     * the user passed to us _is_ an eventpoll file. And also we do not permit
     * adding an epoll file descriptor inside itself.
      //epoll不能自己监听自己,可以监听别的epoll,但有条件:不支持独占的监听别的epoll
    //判断传入的fd是不是自身,判断epfd是不是epoll文件
     */
    error = -EINVAL;
    if (f.file == tf.file || !is_file_epoll(f.file))
        goto error_tgt_fput;

    /*
     * epoll adds to the wakeup queue at EPOLL_CTL_ADD time only,
     * so EPOLLEXCLUSIVE is not allowed for a EPOLL_CTL_MOD operation.
     * Also, we do not currently supported nested exclusive wakeups.
     */
     //EPOLLEXCLUSIVE标识会保证一个事件发生时候只有一个线程会被唤醒,以避免多侦听下的“惊群”问题,
    //多线程,以及epoll_create在fork之前的多进程可以使用EPOLLEXCLUSIVE避免惊群
    //如果是epoll_create在fork之后的多进程,则要用户自己解决惊群问题
    //epoll仅在EPOLL_CTL_ADD时添加到唤醒队列,因此不允许有EPOLLEXCLUSIVE事件时进行EPOLL_CTL_MOD操作。
    if (ep_op_has_event(op) && (epds.events & EPOLLEXCLUSIVE)) {
        if (op == EPOLL_CTL_MOD)
            goto error_tgt_fput;
        //目前不支持嵌套epoll的独占唤醒。
        if (op == EPOLL_CTL_ADD && (is_file_epoll(tf.file) ||
                (epds.events & ~EPOLLEXCLUSIVE_OK_BITS)))
            goto error_tgt_fput;
    }

    /*
     * At this point it is safe to assume that the "private_data" contains
     * our own data structure.
       //在create时存入进去的(anon_inode_getfd),现在取用。
     */
    ep = f.file->private_data;

    /*
     * When we insert an epoll file descriptor, inside another epoll file
     * descriptor, there is the change of creating closed loops, which are
     * better be handled here, than in more critical paths. While we are
     * checking for loops we also determine the list of files reachable
     * and hang them on the tfile_check_list, so we can check that we
     * haven't created too many possible wakeup paths.
     *
     * We do not need to take the global 'epumutex' on EPOLL_CTL_ADD when
     * the epoll file descriptor is attaching directly to a wakeup source,
     * unless the epoll file descriptor is nested. The purpose of taking the
     * 'epmutex' on add is to prevent complex toplogies such as loops and
     * deep wakeup paths from forming in parallel through multiple
     * EPOLL_CTL_ADD operations.
     */
    mutex_lock_nested(&ep->mtx, 0);
    if (op == EPOLL_CTL_ADD) {//即f是不是被嵌套的epoll。
        if (!list_empty(&f.file->f_ep_links) ||//f这个文件有没有被epoll监听
                        is_file_epoll(tf.file)) {//tfd 是 epoll  fd
            full_check = 1;
            mutex_unlock(&ep->mtx);
            mutex_lock(&epmutex);
            if (is_file_epoll(tf.file)) {
                error = -ELOOP;//检测epfd是否构成闭环或者连续epfd的深度超过5,对应宏EP_MAX_NESTS[4]
                if (ep_loop_check(ep, tf.file) != 0) {
                    clear_tfile_check_list();
                    goto error_tgt_fput;
                }
            } else
                list_add(&tf.file->f_tfile_llink,
                            &tfile_check_list);
            mutex_lock_nested(&ep->mtx, 0);
            if (is_file_epoll(tf.file)) {
                tep = tf.file->private_data;
                mutex_lock_nested(&tep->mtx, 1);
            }
        }
    }

    /*
     * Try to lookup the file inside our RB tree, Since we grabbed "mtx"
     * above, we can be sure to be able to use the item looked up by
     * ep_find() till we release the mutex.
     //上面已经枷锁
    //epoll不允许重复添加fd(在ep的红黑树中查找是否已经存在这个fd)O(lgn)的时间复杂度.
     */
    epi = ep_find(ep, tf.file, fd);
/*
原来就是在一个“大的结构”(struct eventpoll)里先ep_find,
如果找到了struct epitem,而根据用户操作是ADD、DEL、MOD调用相应的函数,
这些函数在epitem组成红黑树中增加、删除、修改相应节点(每一个监听fd对应一个节点)。
很直白。那这个“大结构”是什么呢?看ep_find的调用方式,ep参数应该是指向这个“大结构”
的指针,再看ep = file->private_data,我们才明白,原来这个“大结构”就是那个在epoll_create时创建的
struct eventpoll,具体再看看ep_find的实现,发现原来是struct eventpoll的rbr成员(struct rb_root),
原来这是一个红黑树的根!而红黑树上挂的都是struct epitem。
*/
    error = -EINVAL;
    switch (op) {
    case EPOLL_CTL_ADD:
        if (!epi) {  //EPOLLERR该文件描述符发生错误
            //EPOLLHUP该文件描述符被挂断
            //默认包含POLLERR和POLLHUP事件
            epds.events |= POLLERR | POLLHUP;
            //在ep的红黑树中插入这个fd对应的epitm结构体,以及一系列的操作。
            error = ep_insert(ep, &epds, tf.file, fd, full_check);
        } else
            error = -EEXIST; //已经存在
        if (full_check)
            clear_tfile_check_list();
        break;
    case EPOLL_CTL_DEL:
        if (epi)  //在ep的红黑树中删除这个fd对应的epitm结构体,以及一系列的操作。
            error = ep_remove(ep, epi);
        else
            error = -ENOENT;
        break;
    case EPOLL_CTL_MOD:/// 修改已经注册的fd的监听事件
        if (epi) {
            if (!(epi->event.events & EPOLLEXCLUSIVE)) {
                epds.events |= POLLERR | POLLHUP;
                error = ep_modify(ep, epi, &epds);
            }
        } else
            error = -ENOENT;
        break;
    }
    if (tep != NULL)
        mutex_unlock(&tep->mtx);
    mutex_unlock(&ep->mtx);

error_tgt_fput:
    if (full_check)
        mutex_unlock(&epmutex);

    fdput(tf);
error_fput:
    fdput(f);
error_return:

    return error;
}

主要分析ep_insert:

/*
 * Must be called with "mtx" held.
 */
 //ep_insert()在epoll_ctl()中被调用, 完成往epollfd里面添加一个监听fd的工作
//tfile是fd的struct file结构
 
//在ep_insert()函数中,epoll会定义一个类型为ep_pqueue的对象,
//该对象包括了epitem成员,以及一个类型为poll_table的对象成员pt。
//在ep_insert()函数中我们会将pt的_qproc这个回调函数成员设置为ep_ptable_queue_proc(),
//这会会将epitem对象对应的eppoll_entry对象加入到被监控的目标文件的等待队列中,
//并在ep_item_poll()函数中将pt的_key成员设置为用户态应用程序感兴趣的事件类型,
//然后调用被监控的目标文件的poll回调函数。
 
//以socket为例,因为socket有多种类型,如tcp、udp等,所以socket层会实现一个通用的poll回调函数,
//这个函数就是sock_poll()。
//在sock_poll()函数中通常会调用某一具体类型协议对应的poll回调函数,
//以tcp为例,那么这个poll()回调函数就是tcp_poll()。
//当socket有事件就绪时,比如读事件就绪,就会调用sock->sk_data_ready这个回调函数,
//即sock_def_readable(),在这个回调函数中则会遍历socket 文件中的等待队列,
//然后依次调用队列节点的回调函数,在epoll中对应的回调函数就是ep_poll_callback(),
//这个函数会将就绪事件对应的epitem对象加入到epoll对象eventpoll的就绪链表rdllist中,
//这样用户态程序调用epoll_wait()的时候就能获取到该事件,
static int ep_insert(struct eventpoll *ep, struct epoll_event *event,
             struct file *tfile, int fd, int full_check)
{
    int error, revents, pwake = 0;
    unsigned long flags;
    long user_watches;
    struct epitem *epi;
    struct ep_pqueue epq;
    //获取当前用户已经加入到epoll中监控的文件描述符数量,如果超过了上限,那么本次不加入

    user_watches = atomic_long_read(&ep->user->epoll_watches);
    if (unlikely(user_watches >= max_user_watches))
        return -ENOSPC; //从slab中分配一个epitem来保存加入的fd
    if (!(epi = kmem_cache_alloc(epi_cache, GFP_KERNEL)))
        return -ENOMEM;

    /* Item initialization follow here ... */
    INIT_LIST_HEAD(&epi->rdllink);
    INIT_LIST_HEAD(&epi->fllink);
    INIT_LIST_HEAD(&epi->pwqlist);
    //将要监听的fd加入到刚创建的epitem
    epi->ep = ep; //将epoll对象挂载到该fd的epitem结构的ep成员中(用来找到对应的epoll对象)
    ep_set_ffd(&epi->ffd, tfile, fd);  //设置被监控的文件描述符及其对应的文件对象到epitem的ffd成员中 
    //也就是设置 file  以及 fd
    epi->event = *event; //保存fd感兴趣的事件对象
    epi->nwait = 0;
    epi->next = EP_UNACTIVE_PTR; //这个指针的初值不是NULL
    if (epi->event.events & EPOLLWAKEUP) {
        error = ep_create_wakeup_source(epi); //根据EPOLLWAKEUP标志注册wake up
        if (error)
            goto error_create_wakeup_source;
    } else {
        RCU_INIT_POINTER(epi->ws, NULL);
    }

    /* Initialize the poll table using the queue callback */
    epq.epi = epi; // 安装poll回调函数
    //将ep_ptable_queue_proc注册到epq.pt中的qproc。
    //    pt->_qproc = ep_ptable_queue_proc;
    //下面一句等价于&(epq.pt)->qproc = ep_ptable_queue_proc;
    init_poll_funcptr(&epq.pt, ep_ptable_queue_proc);

    /*
     * Attach the item to the poll hooks and get current event bits.
     * We can safely use the file* here because its usage count has
     * been increased by the caller of this function. Note that after
     * this operation completes, the poll callback can start hitting
     * the new item.
     */ //调用poll函数来获取当前事件位,其实是利用它来调用注册函数ep_ptable_queue_proc(poll_wait中调用)。
    //如果fd是套接字,f_op为socket_file_ops,poll函数是sock_poll()。如果是TCP套接字的话,进而会调用
    //到tcp_poll()函数。此处调用poll函数查看当前文件描述符的状态,存储在revents中。
    //在poll的处理函数(tcp_poll())中,会调用sock_poll_wait(),
    //在sock_poll_wait()中会调用到epq.pt.qproc指向的函数,
    //也就是ep_ptable_queue_proc()。
 
    //返回值表示该文件描述符感兴趣的事件。如果感兴趣的事件没有发生,则为0
    //通过ep_item_poll把epitem添加到poll钩子中,并获取当前revents。
    //最终会通过ep_ptable_queue_proc函数把eppoll_entry添加到sk->sk_wq->wait的头部,
    //并通过pwq->llink添加到epi->pwqlist的尾部。
    revents = ep_item_poll(epi, &epq.pt);//epi代表target file,即被监听的文件,poll()返回就绪事件的掩码,赋给revents.
/*
epi->ffd.file->f_op->poll(epi->ffd.file, pt) & epi->event.events; 其实就是调用被监控文件(epoll里叫“target file”)的poll方法,
而这个poll其实就是调用poll_wait(还记得poll_wait吗?每个支持poll的设备驱动程序都要调用的),
最后就是调用ep_ptable_queue_proc。(注:f_op->poll()一般来说只是个wrapper, 它会调用真正的poll实现, 
拿UDP的socket来举例, 这里就是这样的调用流程: f_op->poll(), sock_poll(), udp_poll(), datagram_poll(), sock_poll_wait()。)
这是比较难解的一个调用关系,因为不是语言级的直接调用。
ep_insert还把struct epitem放到struct file里的f_ep_links连表里,以方便查找,
struct epitem里的fllink就是担负这个使命的。

 /* 调用poll函数来获取当前事件位,其实是利用它来调用注册函数ep_ptable_queue_proc(poll_wait中调用)。

如果fd是套接字,f_op为socket_file_ops,poll函数是 sock_poll()。如果是TCP套接字的话,进而会调用 到tcp_poll()函数。

此处调用poll函数查看当前 文件描述符的状态,存储在revents中。 在poll的处理函数(tcp_poll())中,会调用sock_poll_wait(),

在sock_poll_wait()中会调用到epq.pt.qproc指向的函数,  也就是ep_ptable_queue_proc()。  **/

    /*
     * We have to check if something went wrong during the poll wait queue
     * install process. Namely an allocation for a wait queue failed due
     * high memory pressure.
     */
    error = -ENOMEM;
    if (epi->nwait < 0)
        goto error_unregister;

    /* Add the current item to the list of active epoll hook for this file */
    spin_lock(&tfile->f_lock); //把epitem插入到f_ep_links链表的尾部
    // 每个文件会将所有监听自己的epitem链起来
    list_add_tail_rcu(&epi->fllink, &tfile->f_ep_links);
    spin_unlock(&tfile->f_lock);

    /*
     * Add the current item to the RB tree. All RB tree operations are
     * protected by "mtx", and ep_insert() is called with "mtx" held.
     将epitem插入到对应的eventpoll中去
     */    //将epitem插入到对应的eventpoll中
    ep_rbtree_insert(ep, epi);

    /* now check if we've created too many backpaths */
    error = -EINVAL; //通过reverse_path_check进行反向检查
    if (full_check && reverse_path_check())
        goto error_remove_epi;

    /* We have to drop the new item inside our item list to keep track of it */
    spin_lock_irqsave(&ep->lock, flags);

    /* record NAPI ID of new item if present */
    ep_set_busy_poll_napi_id(epi);
    // revents & event->events: 刚才fop->poll的返回值中标识的事件有用户event关心的事件发生
       //如果要监视的文件状态已经就绪并且还没有加入到就绪队列中,则将当前的epitem加入到就绪
       //队列中。如果有进程正在等待该文件的状态就绪,则尝试唤醒epoll_wait进程wake_up_locked(&ep->wq);
       //以及file->poll()等待进程ep_poll_safewake(&ep->poll_wait)

    /* If the file is already "ready" we drop it inside the ready list */
    if ((revents & event->events) && !ep_is_linked(&epi->rdllink)) {
         //将当前的epitem加入到rdllist中去
        list_add_tail(&epi->rdllink, &ep->rdllist);
        ep_pm_stay_awake(epi);

        /* Notify waiting tasks that events are available 
          //waitqueue_active(q) 等待队列q中有等待的进程返回1,否则返回0,
/*

如果有进程正在等待文件的状态就绪,也就是调用epoll_wait睡眠的进程正在等待则唤醒一个等待进程。waitqueue_active(q) 等待队列q中有等待的进程返回1,否则返回0

*/

        if (waitqueue_active(&ep->wq))
            wake_up_locked(&ep->wq);
         //如果有进程等待eventpoll文件本身的事件就绪(该eventpoll也被其他eventpoll poll住了),则增加临时变量pwake的值,
        //pwake的值不为0时,在释放lock后,会唤醒等待进程

/*  如果有进程等待eventpoll文件本身()的事件就绪,则增加临时变量pwake的值,pwake的值不为0时,在释放lock后,会唤醒等待进程。 */

        if (waitqueue_active(&ep->poll_wait))
            pwake++;
    }

    spin_unlock_irqrestore(&ep->lock, flags);
    //增加当前用户加入epoll中的文件描述符个数
       //原子long类型自增

    atomic_long_inc(&ep->user->epoll_watches);

    /* We have to call this outside the lock */
    if (pwake) //唤醒等待eventpoll文件状态就绪的进程
        ep_poll_safewake(&ep->poll_wait);

    return 0;

error_remove_epi:
    spin_lock(&tfile->f_lock);
    list_del_rcu(&epi->fllink);
    spin_unlock(&tfile->f_lock);

    rb_erase(&epi->rbn, &ep->rbr);

error_unregister:
    ep_unregister_pollwait(ep, epi);

    /*
     * We need to do this because an event could have been arrived on some
     * allocated wait queue. Note that we don't care about the ep->ovflist
     * list, since that is used/cleaned only inside a section bound by "mtx".
     * And ep_insert() is called with "mtx" held.
     */
    spin_lock_irqsave(&ep->lock, flags);
    if (ep_is_linked(&epi->rdllink))
        list_del_init(&epi->rdllink);
    spin_unlock_irqrestore(&ep->lock, flags);

    wakeup_source_unregister(ep_wakeup_source(epi));

error_create_wakeup_source:
    kmem_cache_free(epi_cache, epi);

    return error;
}
/*
 * This is the callback that is used to add our wait queue to the
 * target file wakeup lists.
 //ep_ptable_queue_proc函数在调用f_op->poll()时会被调用.
//当epoll主动poll某个fd时, 用来将epitem与指定的fd关联起来(将epitem加入到指定文件的wait队列).
//关联的办法就是使用等待队列(waitqueue)
 
//在ep_ptable_queue_proc函数中,引入了另外一个非常重要的数据结构eppoll_entry。ep
//poll_entry主要完成epitem和epitem事件发生时的callback(ep_poll_callback)函数之
//间的关联并挂载到目标文件file的waithead中。首先将eppoll_entry的whead指向目标文件的设备等待队列(waitlist),
//然后初始化eppoll_entry的base变量指向epitem,最后根据EPOLLEXCLUSIVE标识
//如果要求事件发生时只有一个线程被唤醒则调用add_wait_queue_exclusive将epoll_entry挂载到fd的设备等待队列上,
//否则通过add_wait_queue将epoll_entry挂载到fd的设备等待队列上。完成这个动作后,
//epoll_entry已经被挂载到fd的设备等待队列。
 
//由于ep_ptable_queue_proc函数设置了等待队列的ep_poll_callback回调函数。所以在设
//备硬件数据到来时,硬件中断处理函数中会唤醒该等待队列上等待的进程时,会调用唤
//醒函数ep_poll_callback。
/*  在文件操作中的poll函数中调用,将epoll的回调函数加入到目标文件的唤醒队列中。 如果监视的文件是套接字,参数whead则是sock结构的sk_sleep成员的地址。  */
*/
static void ep_ptable_queue_proc(struct file *file, wait_queue_head_t *whead,
                 poll_table *pt)
{
    struct epitem *epi = ep_item_from_epqueue(pt);
    struct eppoll_entry *pwq;

    if (epi->nwait >= 0 && (pwq = kmem_cache_alloc(pwq_cache, GFP_KERNEL))) {
         //初始化等待队列, 指定ep_poll_callback为唤醒时的回调函数,
        //当我们监听的fd发生状态改变时, 也就是队列头被唤醒时,
        //指定的回调函数将会被调用.(ep_poll_callback)
        init_waitqueue_func_entry(&pwq->wait, ep_poll_callback);
        pwq->whead = whead;
        pwq->base = epi;
        if (epi->event.events & EPOLLEXCLUSIVE)
            add_wait_queue_exclusive(whead, &pwq->wait);
        else
            add_wait_queue(whead, &pwq->wait);
        list_add_tail(&pwq->llink, &epi->pwqlist);
        //nwait记录了当前epitem加入到了多少个等待队列中
        //epitem有可能加入到多个等待队列中。一个file不止一个等待队列。不同事件可能有不同等待队列
        epi->nwait++;
    } else {
        /* We have to signal that an error occurred */
        epi->nwait = -1;
    }
}

/*
上面的代码就是ep_insert中要做的最重要的事:创建struct eppoll_entry,设置其唤醒回调函数为ep_poll_callback,
然后加入设备等待队列(注意这里的whead就是上一章所说的每个设备驱动都要带的等待队列)。
只有这样,当设备就绪,唤醒等待队列上的等待进程时,ep_poll_callback就会被调用。
每次调用poll系统调用,操作系统都要把current(当前进程)挂到fd对应的所有设备的
等待队列上,可以想象,fd多到上千的时候,这样“挂”法很费事;而每次调用
epoll_wait则没有这么罗嗦,epoll只在epoll_ctl时把current挂一遍(这第一遍是免不了的)
并给每个fd一个命令“好了就调回调函数”,如果设备有事件了,通过回调函数,
会把fd放入rdllist,而每次调用epoll_wait就只是收集rdllist里的fd就可以了——epoll巧妙的利用回调函数,
实现了更高效的事件驱动模型。
现在我们猜也能猜出来ep_poll_callback会干什么了——肯定是把红黑树(ep->rbr)上的收到event的epitem
(代表每个fd)插入ep->rdllist中,这样,当epoll_wait返回时,rdllist里就都是就绪的fd了!
*/
/*
 * This is the callback that is passed to the wait queue wakeup
 * mechanism. It is called by the stored file descriptors when they
 * have events to report.
 //这个是关键性的回调函数, 当我们监听的fd发生状态改变时, 它会被调用,
//参数key被当作一个unsigned long整数使用, 携带的是events.
//主要的功能是将被监视文件的event就绪时,将文件对应的epitem添加到rdlist中并唤醒调用epoll_wait进程。
 
//前面提到eppoll_entry完成一个epitem和ep_poll_callback的关联,同时eppoll_entry会被插入目标文件file的(private_data)waithead中。
//以scoket为例,当socket数据ready,终端会调用相应的接口函数比如rawv6_rcv_skb,
//此函数会调用sock_def_readable然后,通过sk_has_sleeper判断sk_sleep上是否有等待的进程,
//如果有那么通过wake_up_interruptible_sync_poll函数调用ep_poll_callback。
 
//文件fd状态改变(buffer由不可读变为可读或由不可写变为可写),导致相应fd上的回调函数ep_poll_callback()被调用。
//ep_poll_callback函数首先会判断是否rdlist正在被使用(通过ovflist是否等于EP_UNACTIVE_PTR),
//如果是那么将epitem插入ovflist。如果不是那么将epitem插入rdlist。
//然后调用wake_up函数唤醒epitem上wq的进程。这样就可以返回到epoll_wait的调用者,将他唤醒。
 */
static int ep_poll_callback(wait_queue_t *wait, unsigned mode, int sync, void *key)
{
    int pwake = 0;
    unsigned long flags;
    struct epitem *epi = ep_item_from_wait(wait);
    struct eventpoll *ep = epi->ep;
    int ewake = 0;

    if ((unsigned long)key & POLLFREE) {
        ep_pwq_from_wait(wait)->whead = NULL;
        /*
         * whead = NULL above can race with ep_remove_wait_queue()
         * which can do another remove_wait_queue() after us, so we
         * can't use __remove_wait_queue(). whead->lock is held by
         * the caller.
         */
        list_del_init(&wait->task_list);
    }

    spin_lock_irqsave(&ep->lock, flags);

    ep_set_busy_poll_napi_id(epi);

    /*
     * If the event mask does not contain any poll(2) event, we consider the
     * descriptor to be disabled. This condition is likely the effect of the
     * EPOLLONESHOT bit that disables the descriptor when an event is received,
     * until the next EPOLL_CTL_MOD will be issued.

       //如果事件掩码不包含任何poll(2)事件,我们认为该描述符被禁用。
    //这种情况很可能是EPOLLONESHOT位在收到事件时禁用描述符的效果,
    //直到发出下一个EPOLL_CTL_MOD解除。
    //EPOLLONESHOT标志位
     */
    if (!(epi->event.events & ~EP_PRIVATE_BITS))
        goto out_unlock;

    /*
     * Check the events coming with the callback. At this stage, not
     * every device reports the events in the "key" parameter of the
     * callback. We need to be able to handle both cases here, hence the
     * test for "key" != NULL before the event match test.
     */
    if (key && !((unsigned long) key & epi->event.events))
        goto out_unlock;

    /*
     * If we are transferring events to userspace, we can hold no locks
     * (because we're accessing user memory, and because of linux f_op->poll()
     * semantics). All the events that happen during that period of time are
     * chained in ep->ovflist and requeued later on.
      //如果ep->ovflist != EP_UNACTIVE_PTR说明此时正在扫描rdllist链表,
    //这个时候会将就绪事件对应的epitem对象加入到ovflist链表暂存起来,
    //等rdllist链表扫描完之后在将ovflist链表中的内容移动到rdllist链表中
    //在下一次epoll_wait时返回给用户.
    //新事件触发的epi插入到ep->ovflist的头部
如果此时就绪链表rdllist没有被其他进程访问,则直接将当前文件描述符添加到rdllist链表中,否则的话添加到ovflist链表中。
ovflist默认值是EP_UNACTIVE_PTR,epoll_wait()遍历rdllist之前会把ovflist设置为NULL,遍历完再恢复为EP_UNACTIVE_PTR,
因此通过判断ovflist的值是不是EP_UNACTIVE_PTR可知此时rdllist是不是正在被访问。
*/ if (unlikely(ep->ovflist != EP_UNACTIVE_PTR)) { if (epi->next == EP_UNACTIVE_PTR) { epi->next = ep->ovflist; ep->ovflist = epi; if (epi->ws) { /* * Activate ep->ws since epi->ws may get * deactivated at any time. */ __pm_stay_awake(ep->ws); } } goto out_unlock; } /* If this file is already in the ready list we exit soon */ if (!ep_is_linked(&epi->rdllink)) { //将该epitem加入到epoll的rdllist就绪链表中 list_add_tail(&epi->rdllink, &ep->rdllist); ep_pm_stay_awake_rcu(epi); } /* * Wake up ( if active ) both the eventpoll wait list and the ->poll() * wait list. */ //如果等待进程队列不为空的话,唤醒在该epoll上的等待进程 if (waitqueue_active(&ep->wq)) { if ((epi->event.events & EPOLLEXCLUSIVE) && !((unsigned long)key & POLLFREE)) { switch ((unsigned long)key & EPOLLINOUT_BITS) { case POLLIN: if (epi->event.events & POLLIN) ewake = 1; break; case POLLOUT: if (epi->event.events & POLLOUT) ewake = 1; break; case 0: ewake = 1; break; } } wake_up_locked(&ep->wq); } //如果该epoll也被poll(即其他epoll添加了该epoll fd,嵌套epoll), 那就唤醒poll在该epoll上的等待epoll队列 if (waitqueue_active(&ep->poll_wait)) pwake++; out_unlock: spin_unlock_irqrestore(&ep->lock, flags); /* We have to call this outside the lock */ if (pwake) ep_poll_safewake(&ep->poll_wait); if (epi->event.events & EPOLLEXCLUSIVE) return ewake; return 1; }

 Currently, epoll file descriptors or epfds (the fd returned from epoll_create[1]()) that are added to a shared wakeup source are always added in a non-exclusive manner.

This means that when we have multiple epfds attached to a shared fd source they are all woken up. This creates thundering herd type behavior. Introduce a new 'EPOLLEXCLUSIVE' flag

that can be passed as part of the 'event' argument during an epoll_ctl() EPOLL_CTL_ADD operation. This new flag allows for exclusive wakeups when there are multiple epfds attached to

a shared fd event source. The implementation walks the list of exclusive waiters, and queues an event to each epfd, until it finds the first waiter that has threads blocked on it via epoll_wait().

The idea is to search for threads which are idle and ready to process the wakeup events. Thus, we queue an event to at least 1 epfd,

but may still potentially queue an event to all epfds that are attached to the shared fd source.

https://lwn.net/Articles/667087/

 

 

 

 

 

 

 

 

epoll_wait:简述

/*
 * Implement the event wait interface for the eventpoll file. It is the kernel
 * part of the user space epoll_wait(2).
 */
SYSCALL_DEFINE4(epoll_wait, int, epfd, struct epoll_event __user *, events,
        int, maxevents, int, timeout)
{
    int error;
    struct fd f;
    struct eventpoll *ep;

    /* The maximum number of event must be greater than zero */
    if (maxevents <= 0 || maxevents > EP_MAX_EVENTS)
        return -EINVAL;

    /* Verify that the area passed by the user is writeable 
    校验用户空间的 buff 是否可以写
    /内核用户传数据都是要复制的,不能直接弄个指针、引用进来
    */
    if (!access_ok(VERIFY_WRITE, events, maxevents * sizeof(struct epoll_event)))
        return -EFAULT;

    /* Get the "struct file *" for the eventpoll file */
    f = fdget(epfd);
    if (!f.file)
        return -EBADF;

    /*
     * We have to check that the file structure underneath the fd
     * the user passed to us _is_ an eventpoll file.
     判断用户态传进来的fd是不是epfd,即通过判断文件对象的操作回调是不是eventpoll_fops
     */
    error = -EINVAL;//确认是eventpoll 文件
    if (!is_file_epoll(f.file))
        goto error_fput;

    /*
     * At this point it is safe to assume that the "private_data" contains
     * our own data structure.
     */
    ep = f.file->private_data;

    /* Time to fish for events ... */
    error = ep_poll(ep, events, maxevents, timeout);

error_fput:
    fdput(f);
    return error;
}

 

 ep_poll:

/**
 * ep_poll - Retrieves ready events, and delivers them to the caller supplied
 *           event buffer.
 *
 * @ep: Pointer to the eventpoll context.
 * @events: Pointer to the userspace buffer where the ready events should be
 *          stored.
 * @maxevents: Size (in terms of number of events) of the caller event buffer.
 * @timeout: Maximum timeout for the ready events fetch operation, in
 *           milliseconds. If the @timeout is zero, the function will not block,
 *           while if the @timeout is less than zero, the function will block
 *           until at least one event has been retrieved (or an error
 *           occurred).
 *
 * Returns: Returns the number of ready events which have been fetched, or an
 *          error code, in case of error.
 //如果epoll_wait入参定时时间为0, 那么直接通过ep_events_available判断当前是否有用户感兴趣的事件发生,如果有则通过ep_send_events进行处理
 //如果定时时间大于0,并且当前没有用户关注的事件发生,则进行休眠,并添加到ep->wq等待队列的头部。 对等待事件描述符设置WQ_FLAG_EXCLUSIVE标志
 //ep_poll被事件唤醒后会重新检查是否有关注事件,如果对应的事件已经被抢走,那么ep_poll会继续休眠等待。

 */
static int ep_poll(struct eventpoll *ep, struct epoll_event __user *events,
           int maxevents, long timeout)
{
    int res = 0, eavail, timed_out = 0;
    unsigned long flags;
    u64 slack = 0;
    wait_queue_t wait;
    ktime_t expires, *to = NULL;

    if (timeout > 0) { //如果就绪链表为空则阻塞直到timeout
        struct timespec64 end_time = ep_set_mstimeout(timeout);

        slack = select_estimate_accuracy(&end_time);
        to = &expires;
        *to = timespec64_to_ktime(end_time);
    } else if (timeout == 0) {//非阻塞
        /*
         * Avoid the unnecessary trip to the wait queue loop, if the
         * caller specified a non blocking operation.
         */
        timed_out = 1;
        spin_lock_irqsave(&ep->lock, flags);
        goto check_events;
    }

fetch_events:
    

    if (!ep_events_available(ep))
        ep_busy_loop(ep, timed_out);

    spin_lock_irqsave(&ep->lock, flags);
    //是否有就绪事件,或正在扫描处理eventpoll中的rdllist链表
    /* 没有事件,所以需要睡眠。当有事件到来时,睡眠会被ep_poll_callback函数唤醒。*/

    if (!ep_events_available(ep)) {
        /*
         * Busy poll timed out.  Drop NAPI ID for now, we can add
         * it back in when we have moved a socket with a valid NAPI
         * ID onto the ready list.
          NAPI是linux新的网卡数据处理API,
        简单来说,NAPI是综合中断方式与轮询方式的技术。
        中断的好处是响应及时,如果数据量较小,则不会占用太多的CPU事件;缺点是数据量大时,
        (每个传入数据包发出中断请求(IRQ)来中断内核。)会产生过多中断,
        而每个中断上下文 进程上下文切换都要消耗不少的CPU时间,从而导致效率反而不如轮询高。
 
        轮询是基于中断的处理的替代方法。内核可以定期检查传入网络数据包的到达而不会中断,从而消除了中断处理的开销。
        但是,建立最佳轮询频率很重要。轮询过于频繁会反复检查尚未到达的传入数据包,从而浪费CPU资源。
        另一方面,轮询过于频繁地通过降低系统对传入数据包的反应性来引入延迟,
        并且如果传入数据包缓冲区在处理之前填满,则可能导致数据包丢失。
 
        NAPI是两者的结合,数据量低时采用中断,数据量高时采用轮询。平时是中断方式,当有数据到达时,会触发中断
        处理函数执行,中断处理函数关闭中断开始处理。如果此时有数据到达,则没必要再触发中断了,因为中断处理函
        数中会轮询处理数据,直到没有新数据时才打开中断。
        很明显,数据量很低与很高时,NAPI可以发挥中断与轮询方式的优点,性能较好。如果数据量不稳定,且说高不高
        说低不低,则NAPI则会在两种方式切换上消耗不少时间,效率反而较低一些
        实现
        来看下NAPI和非NAPI的区别:
        (1) 支持NAPI的网卡驱动必须提供轮询方法poll()。
        (2) 非NAPI的内核接口为netif_rx(),NAPI的内核接口为napi_schedule()。
        (3) 非NAPI使用共享的CPU队列softnet_data->input_pkt_queue,NAPI使用设备内存(或者
        设备驱动程序的接收环)。
         */
        ep_reset_busy_poll_napi_id(ep);

        /*
         * We don't have any available event to return to the caller.
         * We need to sleep here, and we will be wake up by
         * ep_poll_callback() when events will become available.
           //OK, 初始化一个等待队列, 准备直接把自己挂起,
        //注意current是一个宏, 返回的是一个thread_info结构task字段(我们称之为进程描述符)的变量,
        task正好指向与thread_info结构关联的那个进程描述符
        //代表当前进程进程打开的文件资源保存在进程描述符的files成员里面,
        所以current->files返回的当前进程打开的文件资源。
        //这里把调用了epoll_wait()系统调用等待epoll事件发生的进程(current)加入到ep->wq等待队列中。
        并设置了默认的回调函数用于唤醒应用程序。
        //初始化等待队列节点wait,current表示当前进程。把wait和current绑定在一起。
        挂载到eventpoll的等待队列,等待文件状态就绪或直到超时, 或被信号中断
         */
        init_waitqueue_entry(&wait, current);
        __add_wait_queue_exclusive(&ep->wq, &wait);

        for (;;) {
            /*
             * We don't want to sleep if the ep_poll_callback() sends us
             * a wakeup in between. That's why we set the task state
             * to TASK_INTERRUPTIBLE before doing the checks.
             TASK_INTERRUPTIBLE:进程处于睡眠状态,正在等待某些事件发生。进程可以被信号中断。
 
            //执行ep_poll_callback()唤醒时应当需要将当前进程唤醒,
            //这就是我们将任务状态设置为TASK_INTERRUPTIBLE的原因。
            //将当前进程设置位睡眠, 但是可以被信号唤醒的状态,
            //注意这个设置是"将来时", 此刻还没睡眠
             */
      /* 执行ep_poll_callback()唤醒时应当需要将当前进程唤醒,所以当前进程状态应该为“可唤醒”TASK_INTERRUPTIBLE  */
            set_current_state(TASK_INTERRUPTIBLE);
              /* 如果就绪队列不为空,也就是说已经有文件的状态就绪或者超时,则退出循环。*/
              //有就绪事件,或者超时则退出循环,唤醒
            if (ep_events_available(ep) || timed_out)
                break;
            if (signal_pending(current)) { ///有信号产生, 也退出循环,唤醒
                res = -EINTR; /* 如果当前进程接收到信号,则退出循环,返回EINTR错误 */
                break;
            }
            //jtimeout这个时间后, 会被唤醒,
        //ep_poll_callback()如果此时被调用,
            //那么我们就会直接被唤醒, 不用等时间了...
            //ep_poll_callback()的调用时机是由被监听的fd的具体实现(就绪事件), 比如socket或者某个设备驱动来决定的,
            //因为等待队列头是他们持有的, epoll和当前进程只是单纯的等待

            spin_unlock_irqrestore(&ep->lock, flags); //什么都没有,解锁, 睡眠
              //睡眠让出处理器
               /* 主动让出处理器,等待ep_poll_callback()将当前进程唤醒或者超时,返回值是剩余的时间。

从这里开始当前进程会进入睡眠状态,直到某些文件的状态就绪或者超时。

当文件状态就绪时,eventpoll的回调函数ep_poll_callback()会唤醒在ep->wq指向的等待队列中的进程。*/
            if (!schedule_hrtimeout_range(to, slack, HRTIMER_MODE_ABS))
                timed_out = 1;

            spin_lock_irqsave(&ep->lock, flags);
        }
        //从ep->wq等待队列中将调用了epoll_wait()的进程对应的节点移除

        __remove_wait_queue(&ep->wq, &wait);
        __set_current_state(TASK_RUNNING); //设置当前进程的状态为RUNNING
    }
check_events:
    /* Is it worth to try to dig for events ?
    判断epoll对象的rdllist链表和ovflist链表是否为空,如果不为空,说明有就绪
    事件发生,那么该函数返回1,否则返回0*/
     /* ep->ovflist链表存储的向用户传递事件时暂存就绪的文件。

    * 所以不管是就绪队列ep->rdllist不为空,或者ep->ovflist不等于

    * EP_UNACTIVE_PTR,都有可能现在已经有文件的状态就绪。

    * ep->ovflist不等于EP_UNACTIVE_PTR有两种情况,一种是NULL,此时

    * 可能正在向用户传递事件,不一定就有文件状态就绪,

    * 一种情况时不为NULL,此时可以肯定有文件状态就绪,

    * 参见ep_send_events()。

    */
    eavail = ep_events_available(ep);

    spin_unlock_irqrestore(&ep->lock, flags);

    /*
     * Try to transfer events to user space. In case we get 0 events and
     * there's still timeout left over, we go trying again in search of
     * more luck.
     //如果一切正常, 有event发生, 就开始准备数据copy给用户空间了
 
    //如果有就绪的事件发生,那么就调用ep_send_events()将就绪的事件存放到用户态
    //内存中,然后返回到用户态,否则判断是否超时,如果没有超时就继续等待就绪事
    //件发生,如果超时就返回用户态。
    //从ep_poll()函数的实现可以看到,如果有就绪事件发生,则调用ep_send_events()函数做进一步处理,
     */
     /* 如果没有被信号中断,并且有事件就绪,但是没有获取到事件(有可能被其他进程获取到了),并且没有超时,则跳转到retry标签处,重新等待文件状态就绪。 */
    if (!res && eavail &&
        !(res = ep_send_events(ep, events, maxevents)) && !timed_out)
        goto fetch_events;  //ep_send_events函数向用户空间发送就绪事件。

    return res;
}

ep_send_events函数向用户空间发送就绪事件。

ep_send_events()函数将用户传入的内存简单封装到ep_send_events_data结构中,然后调用ep_scan_ready_list() 将就绪队列中的事件传入用户空间的内存。

//ep_send_events()函数将用户传入的内存简单封装到ep_send_events_data结构中,然后调
//用ep_scan_ready_list()将就绪队列中的事件传入用户空间的内存。用户空间访问这个结
//果,进行处理。

static int ep_send_events(struct eventpoll *ep,
              struct epoll_event __user *events, int maxevents)
{
    struct ep_send_events_data esed;

    esed.maxevents = maxevents;
    esed.events = events;

    return ep_scan_ready_list(ep, ep_send_events_proc, &esed, 0, false);
}

 scanlist:

/**
 * ep_scan_ready_list - Scans the ready list in a way that makes possible for
 *                      the scan code, to call f_op->poll(). Also allows for
 *                      O(NumReady) performance.
 *
 * @ep: Pointer to the epoll private data structure.
 * @sproc: Pointer to the scan callback.
 * @priv: Private opaque data passed to the @sproc callback.
 * @depth: The current depth of recursive f_op->poll calls.
 * @ep_locked: caller already holds ep->mtx
 *
 * Returns: The same integer error code returned by the @sproc callback.--->ep_send_events_proc

 //处理就绪链表
//epoll_wait的时候传递函数指针ep_send_events_proc给ep_scan_ready_list,
epfd进行poll的时候则传递函数指针ep_read_events_proc??
 */
static int ep_scan_ready_list(struct eventpoll *ep,
                  int (*sproc)(struct eventpoll *,
                       struct list_head *, void *),
                  void *priv, int depth, bool ep_locked)
{
    int error, pwake = 0;
    unsigned long flags;
    struct epitem *epi, *nepi;
    LIST_HEAD(txlist);

    /*
     * We need to lock this because we could be hit by
     * eventpoll_release_file() and epoll_ctl().
     */

    if (!ep_locked)
        mutex_lock_nested(&ep->mtx, depth);

    /*
     * Steal the ready list, and re-init the original one to the
     * empty list. Also, set ep->ovflist to NULL so that events
     * happening while looping w/out locks, are not lost. We cannot
     * have the poll callback to queue directly on ep->rdllist,
     * because we want the "sproc" callback to be able to do it
     * in a lockless way.
     //所有监听到events的epitem都已经链到rdllist上了,
    //获得rdllist列表,把rdllist上所有的epitem都拷贝到了txlist上,并将rdllist列表重新初始化为空列表。
    //此外,将ep-> ovflist设置为NULL,以便在循环w/out锁定时发生的事件不会丢失。
    //我们不能让poll回调直接在ep-> rdllist上排队,因
    //为我们希望“sproc”回调能够以无锁方式进行。
     */
    spin_lock_irqsave(&ep->lock, flags);
    list_splice_init(&ep->rdllist, &txlist);
    ep->ovflist = NULL;
    spin_unlock_irqrestore(&ep->lock, flags);

    /*
     * Now call the callback function.
     */ //在这个回调函数里面处理每个epitem *sproc 就是 ep_send_events_proc或者ep_read_events_proc
    error = (*sproc)(ep, &txlist, priv);

    spin_lock_irqsave(&ep->lock, flags);
    /*
     * During the time we spent inside the "sproc" callback, some
     * other events might have been queued by the poll callback.
     * We re-insert them inside the main ready-list here.
     //由于在我们扫描处理eventpoll中的rdllist链表的时候可能同时会有就绪事件发生,
    //这个时候为了保证数据的一致性,在这个时间段内发生的就绪事件会临时存放在eventpoll对象的ovflist链表成员中,
    //待rdllist处理完毕之后,再将ovflist中的内容移动到rdllist链表中,等待下次epoll_wait()的调用。
     */
    for (nepi = ep->ovflist; (epi = nepi) != NULL;
         nepi = epi->next, epi->next = EP_UNACTIVE_PTR) {
        /*
         * We need to check if the item is already in the list.
         * During the "sproc" callback execution time, items are
         * queued into ->ovflist but the "txlist" might already
         * contain them, and the list_splice() below takes care of them.
          //检测ovflist中的epitem是否真的就绪,如果就绪则将该epitem加到epoll的rdllist链表中
         */
        if (!ep_is_linked(&epi->rdllink)) {
            list_add_tail(&epi->rdllink, &ep->rdllist);
            ep_pm_stay_awake(epi);
        }
    }
    /*
     * We need to set back ep->ovflist to EP_UNACTIVE_PTR, so that after
     * releasing the lock, events will be queued in the normal way inside
     * ep->rdllist.
     */  //ovflist处理完毕,置为EP_UNACTIVE_PTR
    ep->ovflist = EP_UNACTIVE_PTR;

    /*
     * Quickly re-inject items left on "txlist".
     */ //上一次没有处理完的epitem, 重新插入到ready list,可能是因为用户空间只取了一部分走
    list_splice(&txlist, &ep->rdllist);
    __pm_relax(ep->ws);
    //如果rdllist链表非空,尝试唤醒ep->wq和ep->poll_wait等待队列

    if (!list_empty(&ep->rdllist)) {
        /*
         * Wake up (if active) both the eventpoll wait list and
         * the ->poll() wait list (delayed after we release the lock).
         */
        if (waitqueue_active(&ep->wq))
            wake_up_locked(&ep->wq);
        if (waitqueue_active(&ep->poll_wait))
            pwake++;
    }
    spin_unlock_irqrestore(&ep->lock, flags);

    if (!ep_locked)
        mutex_unlock(&ep->mtx);

    /* We have to call this outside the lock */
    if (pwake)
        ep_poll_safewake(&ep->poll_wait);

    return error;
}

poll event:

/*
//通过ep_item_poll把epitem添加到poll钩子中,并获取当前revents。
 
//ep_item_poll()在epoll_ctl()和epoll_wait()的处理流程中都会调用
//两个流程中的区别在于epoll_ctl(ADD)(具体的就是ep_insert()函数)
处理流程中调用ep_item_poll()函数的时候会设置poll_table的_qproc成员为ep_ptable_queue_proc();
//而epoll_ctl(MOD)和epoll_wait()处理流程中则设置为NULL,
//所以epoll_ctl(MOD)和epoll_wait()只会获取就绪事件的掩码。
//而ep_insert()会将epitem对象对应的eppoll_entry对象加入到被监控的目标文件的等待队列中,
//并设置感兴趣事件发生后的回调函数为ep_poll_callback()。
 
//目标文件的poll回调函数调用完poll_wait()之后会获取对应的就绪事件掩码。
//如果pt的回调函数成员_qproc没有设置,那么目标文件的poll回调函数一般
就只会返回对应的就绪事件掩码。
//如果设置了就会执行相应的函数。

*//*tcp_poll-->sock_poll_wait(file, sk_sleep(sk), wait);
--->poll_wait(filp, wait_address, p);-->p->_qproc(filp, wait_address, p);*/
static inline unsigned int ep_item_poll(struct epitem *epi, poll_table *pt)
{//将pt的_key成员设置为用户态应用程序感兴趣的事件类型,
    pt->_key = epi->event.events;
//执行tcp poll   ep_ptable_queue_proc
    return epi->ffd.file->f_op->poll(epi->ffd.file, pt) & epi->event.events;
}

   

/*
//如果是ep_send_events_proc作为函数指针则会调用ep_send_events_proc()进行扫描处理,
//即遍历txlist链表中的epitem对象,
//针对每一个epitem对象调用ep_item_poll()函数去获取就绪事件的掩码,此时对poll的调用仅仅是取得fd上较新的events(防止之前events被更新),
//如果掩码不为0,说明该epitem对象对应的事件发生了,那么就将其对应的struct epoll_event类型的对象拷贝到用户态指定的内存中(封装在struct epoll_event,从epoll_wait返回)。
//如果掩码为0,则直接处理下一个epitem。
//注意此时在调用ep_item_poll()函数的时候没有设置poll_table的_qproc回调函数成员,所以只会尝试去获取就绪事件的掩码,
//通过ep_item_poll获取revents,相比ep_insert差异在于并不会调用ep_ptable_queue_proc重新注册

*/
static int ep_send_events_proc(struct eventpoll *ep, struct list_head *head,
                   void *priv)
{
    struct ep_send_events_data *esed = priv;
    int eventcnt;
    unsigned int revents;
    struct epitem *epi;
    struct epoll_event __user *uevent;
    struct wakeup_source *ws;
    poll_table pt;

    init_poll_funcptr(&pt, NULL);

    /*
     * We can loop without lock because we are passed a task private list.
     * Items cannot vanish during the loop because ep_scan_ready_list() is
     * holding "mtx" during this call.
     //这里会循环遍历就绪事件对应的item对象组成的链表,依次将链表中item对象
    //对应的就绪事件拷贝到用户态,最多拷贝用户态程序指定的就绪事件数目。
     */
    for (eventcnt = 0, uevent = esed->events;
         !list_empty(head) && eventcnt < esed->maxevents;) {
        epi = list_first_entry(head, struct epitem, rdllink);

        /*
         * Activate ep->ws before deactivating epi->ws to prevent
         * triggering auto-suspend here (in case we reactive epi->ws
         * below).
         *
         * This could be rearranged to delay the deactivation of epi->ws
         * instead, but then epi->ws would temporarily be out of sync
         * with ep_is_linked().
         */
        ws = ep_wakeup_source(epi);
        if (ws) {
            if (ws->active)
                __pm_stay_awake(ep->ws);
            __pm_relax(ws);
        }
        //然后从链表里面移除
//list_del_init(entry) 的作用是从双链表中删除entry节点,并将entry节点的前继节点和后继节点都指向entry本身。

        list_del_init(&epi->rdllink);
        //读取events,
 //注意events我们ep_poll_callback()里面已经取过一次了, 为啥还要再取?
//1. 我们当然希望能拿到此刻的最新数据, events是会变的~
 //2. 不是所有的poll实现, 都通过等待队列传递了events, 有可能某些驱动压根没传必须主动去读取.
        
 //虽然这里也会调用ep_item_poll(),但是pt->_qproc这个回调函数并没有设置,
//这种情况下文件对象的poll回调函数就只会去获取就绪事件对应的掩码值,
 //因为当pt->_qproc会空时,poll回调函数中调用的poll_wait()什么事情都不做
//就返回,所以poll回调函数就只会去获取事件掩码值。
//通过ep_item_poll获取revents,相比ep_insert差异在于并不会调用ep_ptable_queue_proc重新注册

        revents = ep_item_poll(epi, &pt);
        /* epi->ffd.file->f_op->poll(epi->ffd.file, NULL);//调用每个监听文件的poll方法获取就绪事件(掩码),并赋值*/

        /*
         * If the event mask intersect the caller-requested one,
         * deliver the event to userspace. Again, ep_scan_ready_list()
         * is holding "mtx", so no operations coming from userspace
         * can change the item. //如果revents不为0,说明确实有就绪事件发生,
         那么就将就绪事件拷贝到用户态内存中
         */
        if (revents) {
            //将当前的事件和用户传入的数据都copy给用户空间,
            //就是epoll_wait()后应用程序能读到的那一堆数据.
            if (__put_user(revents, &uevent->events) ||
                __put_user(epi->event.data, &uevent->data)) {
                 //复制失败则把epi重新插入到ready链表
                list_add(&epi->rdllink, head);
                ep_pm_stay_awake(epi);
                return eventcnt ? eventcnt : -EFAULT;
            }
            eventcnt++;
            uevent++;
             //如果设置了EPOLLONESHOT标志位,则设置epi->event.events &= EP_PRIVATE_BITS,
            //后续根据EP_PRIVATE_BITS判断不再加入ep->rdllist或者ep->ovflist。
            //注意设置了EPOLLONESHOT触发一次后并没有删除epi,因而通过epoll_ctl进行ADD操作后会提示File exists错误。
            if (epi->event.events & EPOLLONESHOT)
                epi->event.events &= EP_PRIVATE_BITS;

             /*/这个地方就是epoll中特有的EPOLLET边缘触发逻辑的实现,即当一个
            就绪事件拷贝到用户态内存后判断这个事件类型是否包含了EPOLLET位,
            如果没有,则将该事件对应的epitem对象重新加入到epoll的rdllist链
            表中,用户态程序下次调用epoll_wait()返回时就又能获取该epitem了
            下一次epoll_wait时, 会立即返回, 并通知给用户空间.
            当然如果这个被监听的fds确实没事件也没数据了, epoll_wait会返回一个0,空转一次.
举个例子。假设一个socket,只是connect,还没有收发数据,那么它的poll事件掩码总是有POLLOUT的(参见上面的驱动示例),
每次调用epoll_wait总是返回POLLOUT事件(比较烦),因为它的fd就总是被放回rdllist;假如此时有人往这个socket里写了一大堆数据,
造成socket塞住(不可写了),那么标蓝色的判断就不成立了(没有POLLOUT了),fd不会放回rdllist,epoll_wait将不会再返回用户POLLOUT事件。
现在我们给这个socket加上EPOLLET,然后connect,没有收发数据,此时,标红的判断又不成立了,
所以epoll_wait只会返回一次POLLOUT通知给用户(因为此fd不会再回到rdllist了),接下来的epoll_wait都不会有任何事件通知了。
*/ else if (!(epi->event.events & EPOLLET)) { /* * If this file has been added with Level * Trigger mode, we need to insert back inside * the ready list, so that the next call to * epoll_wait() will check again the events * availability. At this point, no one can insert * into ep->rdllist besides us. The epoll_ctl() * callers are locked out by * ep_scan_ready_list() holding "mtx" and the * poll callback will queue them in ep->ovflist. */ list_add_tail(&epi->rdllink, &ep->rdllist); ep_pm_stay_awake(epi); } } } return eventcnt; }

 

 

epi->ffd.file->f_op->poll(epi->ffd.file, NULL)这个拷贝实现其实没什么可看的,

但是请注意 epi->ffd.file->f_op->poll(epi->ffd.file, NULL);,这个poll很狡猾,它把第二个参数置为NULL来调用。

我们先看一下设备驱动通常是怎么实现poll的:

static unsigned int scull_p_poll(struct file *filp, poll_table *wait)
{
struct scull_pipe *dev = filp->private_data;
unsigned int mask = 0;
poll_wait(filp, &dev->inq, wait);
poll_wait(filp, &dev->outq, wait);
if (dev->rp != dev->wp)
mask |= POLLIN | POLLRDNORM; /* readable */
if (spacefree(dev))
mask |= POLLOUT | POLLWRNORM; /* writable */
return mask;
}

设备先要把current(当前进程)挂在inq和outq两个队列上(这个“挂”操作是wait回调函数指针做的),然后等设备来唤醒,

唤醒后就能通过mask拿到事件掩码了(注意那个mask参数,它就是负责拿事件掩码的)。那如果wait为NULL,poll_wait会做些什么呢?

4.4 poll_wait
[include/linux/poll.h->poll_wait]
 static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address,poll_table *p)
 {
    if (p && wait_address)
    p->qproc(filp, wait_address, p);
 }
喏,看见了,如果poll_table为空,什么也不做。我们倒回ep_send_events,那句标红的poll,实际上就是“我不想休眠,我只想拿到事件掩码”的意思

 

 

 

 

 

 参考转载:http://blog.chinaunix.net/uid-28541347-id-4238524.html

http://blog.chinaunix.net/uid-20687780-id-2105159.html

https://www.cnblogs.com/wsw-seu/p/8274195.html

 

ep->poll_wait 的作用-

答案:ep->poll_wait 是 epoll 实例中另一个等待队列。当被监视的文件是一个 epoll 类型时,需要用这个等待队列来处理递归唤醒

 

  •  epollfd1 监视了 2 个“非 epoll”类型的 fd
  • epollfd2 监视了 epollfd1 和 2 个“非 epoll”类型的 fd

  epollfd1 所监视的 2 个 fd 中有可读事件触发,fd 的 ep_poll_callback 回调函数会触发将 fd 放到 epollfd1 的 rdllist 中。此时 epollfd1 本身的可读事件也会触发,就需要从 epollfd1 的 poll_wait 等待队列中找到 epollfd2,调用 epollfd1 的 ep_poll_callback(将 epollfd1 放到 epollfd2 的 rdllist 中)。因此 ep->poll_wait 是用来处理 epoll 间嵌套递归

 

posted @ 2019-11-13 21:38  codestacklinuxer  阅读(465)  评论(0编辑  收藏  举报