Linux eventpoll解析

本文原创,原创不易,转载请注明出处!

Linux eventpoll解析

eventpoll是什么?

eventpoll是一个可以同时监听多个file发生特定event,然后将发生的特定event返回user space,在user space调用此event的回调函数的一种功能,它一个wait可以wait所有注册到这个eventpoll的文件的特定event。

eventpoll框架

1. 被监视文件注册到eventpoll

当一个文件注册到eventpoll时,会创建一个epitem,然后将来自user space的epoll_event复制给此结构体中的event,这个epoll_event表示被监测文件所关心的事件类型以及事件对应的callback,这个新创建的epitem会插入eventpoll.rbr rbtree。然后会调用被监视的文件的poll函数,此poll函数再调用poll_wait,调用poll_wait()会给出一个wait_address(即wait_queue_head_t),这个wait_address来自被监测文件,这个wait_address是一个,之后会创建一个eppoll_entry,并把eppoll_entry.wait成员加入wait_address所表示的wait queue,并指定了wait callback函数为ep_poll_callback(),等被监测文件那边有事件发生时会wake up wait_address从而调用ep_poll_callback()

2. 在eventpoll.wq上等待wake事件发生/将发生event的被监测文件的epoll_event put to user

2.1 调用epoll_wait()系统调用将创建一个wait_queue_entry,并将它加入eventpoll.wq,此时指定的wait callback func是default_wake_function,在eventpoll.wq上没有event发生的条件下将会把自己sched out,等eventpoll.wq上有event了,调用wait callback default_wake_function,这个函数就是将前面因为sched out而sleep的线程wakeup;

2.2 eventpoll.wq被wakeup后,从eventpoll.rdllist中取出epitem,此rdllist表示发生了event的被监视的文件对应的epitem链表。取出epitem后,调用这个epitem对应的被监测的文件的poll函数,如果此函数的返回值和epitem.event.events相与值不为0,表示有被监测文件关系的事件发生,则将此相与的结果(表示事件类型,比如EPOLLIN)以及epitem.event.data put user;如果相与的结果等于0,则检查rdllist中的下一个epitem,如果相与结果没有一个不为0的,表示没有一个event是被监测文件所关心的事件,则会返回到2.1再创建wait queue entry并加入eventpoll.wq继续等待event发生

3. 被监测的文件产生事件将1中的wait queue wake up

当被监测的文件产生事件时,将1中的wait queue wake up,所以会调用ep_poll_callback(),这个函数得到此发生事件的被监测文件对应的epitem,然后将此epitem.rdllink插入到eventpoll.rdllist,然后将2中的eventpoll.wq wake up


* eventpoll.rdllist中可能会存在多个node,因为被监测的文件可能会有多个,当多个文件同时发生event时,多个文件同时wake up各自的wait_address,从而调用ep_poll_callback()将各自的epitem插入eventpoll.rdllist链表,从而rdllist里node不止一个(同时wake eventpoll.wq时需要将已经sleep被sched out的线程wake到执行这一过程需要一定的时间,在这段时间内,被监测的文件也可能发生event从而往rdllist里插入另外的epitem)

* 如果rdllist里node不止一个,并且epoll_wait系统调用想获取的epoll_event数目小于rdllist里的有效数量(有些event可能并不是被监测文件所关心的event,这不会被put user),则只会将epoll_wait想要的数量put user,然后epoll_wait系统调用会返回,user space再处理这里获取到的epoll_event,rdllist如果还有剩余epitem将仍然在此链表里,等待下一次epoll_wait系统调用再从此rdllist链表中取出epitem再将epoll_event put user直到此链表为空。

eventpoll重要数据结构

eventpoll有两个比较重要的数据结构,一个是epitem,一个是eventpoll。一个eventpoll会有很多个epitem,这些epitem会用eventpoll.rbr rbtree组织起来。一个epitem代表eventpoll所监视的一个文件的特定event。

1. struct epitem

struct epoll_event event表示此epitem所关联的被监测文件所关心的事件,这个struct里的events表示具体的event类型,data成员表示此event发生时的回调函数;

struct eventpoll *ep表示epitem所在的eventpoll;

struct epoll_filefd ffd表示所监视文件的file struct以及fd;

struct list_head rdllink表示epitem所代表的被监视的文件发生event时以此成员将epitem插入eventpoll.rdllist链表;

struct rb_node rbn表示以此成员将epitem插入eventpoll.rbr rbtree;

2. struct eventpoll

wait_queue_head_t wq:eventpoll的wait queue head,epoll_wait系统调用时创建wait_queue_entry_t并将它加入此wait queue,等待被监测文件事件发生时wake此wait queue;

struct list_head rdllist:ready list,此链表保存了已发生事件的被监视文件对应的epitem;

struct rb_root_cached rbr:管理在此eventpoll里的所有epitem的rbtree

 

eventpoll framework diagram

 

 

 

eventpoll相关的系统调用

1. epoll_create1()

分配一个eventpoll struct,然后创建一个eventpoll的anon file,这个anon file的file_operations(f_op成员)是eventpoll_fops,file结构体的private_data指向eventpoll struct

2. epoll_ctl()

SYSCALL_DEFINE4(epoll_ctl, int, epfd, int, op, int, fd, struct epoll_event __user *, event)

对想要监视的file做增删改的操作,比如EPOLL_CTL_ADD,表示将想要监测的file fd加入到eventpoll,event表示想要监测的文件所关心的event类型(比如EPOLLIN),以及此event对应的callback函数;

3. epoll_wait()

SYSCALL_DEFINE4(epoll_wait, int, epfd, struct epoll_event __user *, events, int, maxevents, int, timeout)

 这个一个阻塞调用,如果没有wait到event,会导致调用线程sleep并sched out。如果等到了event,会返回这些等到的event,这里等到的event可以不止一个,数量由maxevents指定,如果不止一个,则可能会得到多个epoll_event。得到epoll_event后,一般会调用这个epoll_event里的callback函数(data成员)

 

Result<Success> Epoll::Wait(std::optional<std::chrono::milliseconds> timeout) {
    int timeout_ms = -1;
    if (timeout && timeout->count() < INT_MAX) {
        timeout_ms = timeout->count();
    }
    epoll_event ev;
    auto nr = TEMP_FAILURE_RETRY(epoll_wait(epoll_fd_, &ev, 1, timeout_ms));
    if (nr == -1) {
        return ErrnoError() << "epoll_wait failed";
    } else if (nr == 1) {
        std::invoke(*reinterpret_cast<std::function<void()>*>(ev.data.ptr));
    }
    return Success();
}

 

通过对同一个epfd注册多个想要监视的file分别调用epoll_ctl(EPOLL_CTL_ADD)即可对多个file进行同时监测,被监测的file任意一个有event发生,epoll_wait()将会从sleep状态唤醒,如果此event是被监测file所关心的event类型,将此epoll_event put user,epoll_wait系统调用结束,此时epoll_wait系统调用获取了所关心的event类型的epoll_event

 

pollevent实例解析

以一个实例来说明,android init进程的fd 4是一个eventpoll anon file,有3个file注册到此eventpoll了,fd分别为6、7、8,6是一个signalfd anon file,7是一个socket file,8是/proc/1/mounts。所以fd 4 eventpoll file同时监视了fd 6、7、8 file epoll_event的产生。

console:/proc/1 # cat fdinfo/4
pos: 0
flags: 02000002
mnt_id: 13
tfd: 7 events: 19 data: 7f8a2a9130 pos:0 ino:4ab sdev:9
tfd: 6 events: 19 data: 7f8a2a90d0 pos:0 ino:21f9 sdev:d
tfd: 8 events: 1a data: 7f8a2a9190 pos:4122 ino:3043 sdev:4

 

lrwx------ 1 root root 64 2021-10-29 09:48 4 -> anon_inode:[eventpoll]
lrwx------ 1 root root 64 2021-10-29 09:48 5 -> socket:[1194]
lrwx------ 1 root root 64 2021-10-29 09:48 6 -> anon_inode:[signalfd]
lrwx------ 1 root root 64 2021-10-29 09:48 7 -> socket:[1195]
lr-x------ 1 root root 64 2021-10-29 09:48 8 -> /proc/1/mounts

 

 

上述fd 6为一个signalfd,这个同eventpoll一样,也是anon file,它的file_operations(file.f_op成员)为signalfd_fops。

看下signalfd file的poll函数:

epoll_ctl(EPOLL_CTL_ADD)时,这个函数提供的wait_address为current->sighand->signalfd_wqh;

当signalfd所属的进程有signal产生时,在__send_signal()里将会执行signalfd_notify(),而此函数调用了wake_up(&tsk->sighand->signalfd_wqh);

当处理eventpoll.rdllist上的epitem时,signalfd_poll()检查当前pending signal中是否有signalfd所关注的signal pending,如果有,则events将或上EPOLLIN,同时在调用epoll_ctl(EPOLL_CTL_ADD)注册signalfd file到eventpoll时epoll_event.events等于EPOLLIN,所以表示有signalfd所关心的event发生,所以将会把此epoll_event put user,epoll_wait系统调用返回,user space获取到此epoll_event后将会执行它的callback函数HandleSignalFd(epoll_event.data)

fs/signalfd.c

static __poll_t signalfd_poll(struct file *file, poll_table *wait)
{
    struct signalfd_ctx *ctx = file->private_data;
    __poll_t events = 0;

    poll_wait(file, &current->sighand->signalfd_wqh, wait);

    spin_lock_irq(&current->sighand->siglock);
    if (next_signal(&current->pending, &ctx->sigmask) ||
        next_signal(&current->signal->shared_pending,
            &ctx->sigmask))
        events |= EPOLLIN;
    spin_unlock_irq(&current->sighand->siglock);

    return events;
}

 

*上述ctx->sigmask,如果是signalfd所关心的signal,对应bit是0,否则是1,0表示没有被mask,如果pending signal集合里有对应bit为1,则获取到该signal,next_signal()返回该signal nr(非0),此时表示有signalfd所关心的event发生

 

另外两个被监视的file fd 7、8和fd 6类似,但是wait_address各自都不一样,比如fd 8对应的poll函数如下,其wait_address是p->ns->poll:

static __poll_t mounts_poll(struct file *file, poll_table *wait)
{
    struct seq_file *m = file->private_data;
    struct proc_mounts *p = m->private;
    struct mnt_namespace *ns = p->ns;
    __poll_t res = EPOLLIN | EPOLLRDNORM;
    int event;

    poll_wait(file, &p->ns->poll, wait);

    event = READ_ONCE(ns->event);
    if (m->poll_event != event) {
        m->poll_event = event;
        res |= EPOLLERR | EPOLLPRI;
    }

    return res;
}

 

posted @ 2022-02-03 17:49  aspirs  阅读(2243)  评论(0编辑  收藏  举报