本文原創,原創不易,轉載請注明出處!
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; }