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, ¤t->sighand->signalfd_wqh, wait); spin_lock_irq(¤t->sighand->siglock); if (next_signal(¤t->pending, &ctx->sigmask) || next_signal(¤t->signal->shared_pending, &ctx->sigmask)) events |= EPOLLIN; spin_unlock_irq(¤t->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; }
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析