#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);
在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(文件句柄)
用户态: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)把此对象插入红黑树中
/*
* 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;
}
}
/* * 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的事件监听.
从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;
}
...
}