1. 相关数据结构
- <span style="font-size:10px;">#define EPOLLIN 0x00000001
- #define EPOLLPRI 0x00000002
- #define EPOLLOUT 0x00000004
- #define EPOLLERR 0x00000008
- #define EPOLLHUP 0x00000010
- #define EPOLLRDNORM 0x00000040
- #define EPOLLRDBAND 0x00000080
- #define EPOLLWRNORM 0x00000100
- #define EPOLLWRBAND 0x00000200
- #define EPOLLMSG 0x00000400
- #define EPOLLET 0x80000000
- #define EPOLL_CTL_ADD 1
- #define EPOLL_CTL_DEL 2
- #define EPOLL_CTL_MOD 3
- typedef union epoll_data
- {
- void *ptr;
- int fd;
- unsigned int u32;
- unsigned long long u64;
- } epoll_data_t;
- struct epoll_event
- {
- unsigned int events; //如EPOLLIN、EPOLLOUT
- epoll_data_t data;
- };
- int epoll_create(int size);
- int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); //op为:EPOLL_CTL_ADD、EPOLL_CTL_DEL
- int epoll_wait(int epfd, struct epoll_event *events, int max, int timeout);
- </span>
常用的事件类型:
EPOLLIN :表示对应的文件描述符可以读;
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET:表示对应的文件描述符有事件发生;
2. epoll_create
在Bionic中的实现见:epoll_create.S,它直接进行系统调用,系统调用表见kernel:src/include/linux/syscalls.h,根据规则在Kernel中Search字符串“epoll_create”,就能找到对应的实现函数:SYSCALL_DEFINE1(epoll_create, int, size) <在Kernel的实现在文件eventpoll.c中>
- SYSCALL_DEFINE1(epoll_create, int, size)
- {
- if (size <= 0)
- return -EINVAL;
- return sys_epoll_create1(0);
- }
看仔细了, 只要传入的参数大于0即可,它并没有别的用处。此函数功能为:
1)从当前进程的files中寻找一个空闲的fd(文件句柄)
2)创建一个struct file实例(其fops为eventpoll_fops,priv为刚为其创建的struct eventpoll对象)
3)当前进程的files->fdt->fd[fd]为新创建的struct file实例
4)返回给用户态的当然是一个fd(文件句柄)
3. epoll_ctl
用户态:int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
Kernel态:SYSCALL_DEFINE4(epoll_ctl, int, epfd, int, op, int, fd, struct epoll_event __user *, event),它主要实现eventpoll文件的控制接口,用于插入、删除、修改文件集中的文件描述符。其代码处理流程为:<epoll_event用于描述感兴趣的事件和源fd>
1)获取eventpoll文件句柄epfd对应的文件实例(struct file)
2)获取目标文件句柄fd对应的文件实例(struct file)
3)确保目标文件句柄fd对应的文件实例(struct file)支持poll操作(即:(tfile->f_op && tfile->f_op->poll))
4)把eventpoll文件的私有数据转换为eventpoll对象,eventpoll红黑树的key为:epoll_filefd
struct epoll_filefd {
struct file *file;
int fd;
};
5)在红黑树中查找要操作的目标fd和fie实例,从而获取一个struct epitem,红黑树中节点的数据结构
6)根据op,进行对应的INSERT、REMOVE或MODIFY操作,下面说说INSERT(当操作为EPOLL_CTL_ADD时)
7)调用int ep_insert(struct eventpoll *ep, struct epoll_event *event, struct file *tfile, int fd)
7.1)创建struct epitem对象(每一个文件描述符增加到eventpoll接口必须有一个epitem对象,且此对象被插入eventpoll的红黑树中)
7.2)初始化epi中的三个链表,保存eventpoll,目标fd,目标文件实例,epoll_event..
7.3)把回调函数注册给目标文件的f_op->poll,相关代码如下:
struct ep_pqueue epq;
epq.epi = epi;
init_poll_funcptr(&epq.pt, ep_ptable_queue_proc);
revents = tfile->f_op->poll(tfile, &epq.pt); //详情可参考pipe_poll处理方式,它最终还是调用ep_ptable_queue_proc函数来处理
ep_ptable_queue_proc: is used to add our wait queue to the target file wakeup lists
7.4)把此对象插入红黑树中
- <span style="font-size:10px;">/*
- * This is the callback that is used to add our wait queue to the
- * target file wakeup lists.
- */
- 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))) {
- init_waitqueue_func_entry(&pwq->wait, ep_poll_callback);
- pwq->whead = whead;
- pwq->base = epi;
- add_wait_queue(whead, &pwq->wait);
- list_add_tail(&pwq->llink, &epi->pwqlist);
- epi->nwait++;
- } else {
- /* We have to signal that an error occurred */
- epi->nwait = -1;
- }
- }</span>
- /*
- * This is the callback that is passed to the wait queue wakeup
- * mechanism. It is called by the target file descriptors when they
- * have events to report.
- */
- static int ep_poll_callback(wait_queue_t *wait, unsigned mode, int sync, void *key)
- {
- ...
- }
这ep_poll_callback如何被执行的呢?
下面以pipe为例,假设上面的是检测从pipe中读取数据,哪么写数据时将调用此函数。
- init_waitqueue_func_entry(&pwq->wait, ep_poll_callback);
- static inline void init_waitqueue_func_entry(wait_queue_t *q,
- wait_queue_func_t func)
- {
- q->flags = 0;
- q->private = NULL;
- q->func = func;
- }
ep_poll_callback被保存在q->func中。
下面看此q->func的调用流程:
- static ssize_t
- pipe_write(struct kiocb *iocb, const struct iovec *_iov,
- unsigned long nr_segs, loff_t ppos)
- {
- ...
- if (do_wakeup) {
- wake_up_interruptible_sync_poll(&pipe->wait, POLLIN | POLLRDNORM);
- kill_fasync(&pipe->fasync_readers, SIGIO, POLL_IN);
- do_wakeup = 0;
- }
- ...
- }
- #define wake_up_interruptible_sync_poll(x, m) \
- __wake_up_sync_key((x), TASK_INTERRUPTIBLE, 1, (void *) (m))
- /**
- * __wake_up_sync_key - wake up threads blocked on a waitqueue.
- */
- void __wake_up_sync_key(wait_queue_head_t *q, unsigned int mode,
- int nr_exclusive, void *key)
- {
- ...
- __wake_up_common(q, mode, nr_exclusive, wake_flags, key);
- ...
- }
- /*
- * The core wakeup function. Non-exclusive wakeups (nr_exclusive == 0) just
- * wake everything up. If it's an exclusive wakeup (nr_exclusive == small +ve
- * number) then we wake all the non-exclusive tasks and one exclusive task.
- *
- * There are circumstances in which we can try to wake a task which has already
- * started to run but is not in state TASK_RUNNING. try_to_wake_up() returns
- * zero in this (rare) case, and we handle it by continuing to scan the queue.
- */
- static void __wake_up_common(wait_queue_head_t *q, unsigned int mode,
- int nr_exclusive, int wake_flags, void *key)
- {
- wait_queue_t *curr, *next;
- list_for_each_entry_safe(curr, next, &q->task_list, task_list) {
- unsigned flags = curr->flags;
- if (curr->func(curr, mode, wake_flags, key) &&
- (flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)
- break;
- }
- }
总结一下:目标文件以pipefd为例。为新的目标文件生成一个epitem包含 pipe fd和需要监听的事件,并将epitem与函数ep_ptable_queue_proc的地址捆绑成一个ep_pqueue结构,然后用结构中的函数地址字段作为参数执行pipe fd对应的poll函数(pipe_poll),在pipe_poll执行时函数ep_ptable_queue_proc被执行,同时函数体中可以根据传入的函数地址计算偏移来得到epitem指针,函数ep_ptable_queue_proc将epoll回调函数ep_poll_callback函数与epitem指针捆绑成另一个结构eppoll_entry,然后把eppoll_entry中的函数地址生成一个wait_queue_t,插入到目标pipe fd的wait queue中,当pipe由于状态改变而触发激活wait_queue时<在pipe_write中调用wake_up_interruptible_sync_poll(&pipe->wait, POLLIN | POLLRDNORM);它将wake up threads blocked on the waitqueue(pipe->wait)>,包含在队列中的ep_poll_callback函数就会被调用,同时根据其函数地址参数,用偏移量来得到epitem,回调函数在调用时会再执行pipe_poll函数,来明确是不是指定的关注事件发生,若成立则将 epitem插入到eventpoll中的rdlist,并激活在epoll fd上wait的进程,并将事件回传至用户态.这样就能实现对目标fd的事件监听.
4. epoll_wait
从epfd中读取epoll_event并保存到events数组中。
用户态:int epoll_wait(int epfd, struct epoll_event *events, int max, int timeout);
Kernel态:SYSCALL_DEFINE4(epoll_wait, int, epfd, struct epoll_event __user *, events, int, maxevents, int, timeout)
1)获取epfd对应的文件实例
2)把eventpoll文件的私有数据转换为eventpoll对象
3)调用int ep_poll(struct eventpoll *ep, struct epoll_event __user *events,
int maxevents, long timeout)获取epoll_event。此函数获取已经准备好的事件,然后把他们保存到调用都提供的events buffer中。
3.1)ep_poll中调用hrtimer来实现其超时功能
3.2)调用int ep_events_available(struct eventpoll *ep)来check是否有事件
3.3)调用int ep_send_events(struct eventpoll *ep,struct epoll_event __user *events, int maxevents)来真正地获取事件,并copy到用户空间的events buffer中
3.3.1)调用ep_scan_ready_list(ep, ep_send_events_proc, &esed);
3.3.2)在回调函数中,获取epoll_event,epoll_event两个域的数据来源如下:
uevent是用户提供的epoll_event,
revents = epi->ffd.file->f_op->poll(epi->ffd.file, NULL) & epi->event.events;
if (revents) {
if (__put_user(revents, &uevent->events) ||
__put_user(epi->event.data, &uevent->data)) {
list_add(&epi->rdllink, head);
return eventcnt ? eventcnt : -EFAULT;
}
...
}