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;
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM