SYSCALL_DEFINE4(epoll_wait, int, epfd, struct epoll_event __user *, events,
int, maxevents, int, timeout)
{
struct timespec64 to;
// 将 timeout 转换为 timespec64 结构体
// do_epoll_wait [[具体实现见下面]]
return do_epoll_wait(epfd, events, maxevents, ep_timeout_to_timespec(&to, timeout));
}
static int do_epoll_wait(int epfd, struct epoll_event __user *events,
int maxevents, struct timespec64 *to)
{
int error;
struct fd f;
struct eventpoll *ep;
// 这里的 EP_MAX_EVENTS = INT_MAX / sizeof(struct epoll_event)
if (maxevents <= 0 || maxevents > EP_MAX_EVENTS) // maxevents 必须大于 0 且小于 EP_MAX_EVENTS
return -EINVAL;
// 验证用户传递的区域是否可写
if (!access_ok(events, maxevents * sizeof(struct epoll_event)))
return -EFAULT;
f = fdget(epfd); // 获取 epfd 对应的文件描述符
if (!f.file)
return -EBADF;
error = -EINVAL;
if (!is_file_epoll(f.file)) // 确保 epfd 是 epoll 文件描述符
goto error_fput;
ep = f.file->private_data; // 取出 eventpoll 结构体
error = ep_poll(ep, events, maxevents, to); // 调用 ep_poll 函数,[[具体实现见下面]]
error_fput:
fdput(f);
return error;
}
static int ep_poll(struct eventpoll *ep, struct epoll_event __user *events,
int maxevents, struct timespec64 *timeout)
{
int res, eavail, timed_out = 0;
u64 slack = 0;
wait_queue_entry_t wait;
ktime_t expires, *to = NULL;
lockdep_assert_irqs_enabled();
// 计算超时时间
if (timeout && (timeout->tv_sec | timeout->tv_nsec)) {
// 这里是用户指定了超时时间
slack = select_estimate_accuracy(timeout);
to = &expires;
*to = timespec64_to_ktime(*timeout); // 将时间转换为 ktime_t 结构体
} else if (timeout) {
// 这里是用户没有指定超时时间,将会在检查一次事件后返回
timed_out = 1;
}
eavail = ep_events_available(ep); // 检查是否有事件可用 [[具体实现见下面]]
while (1) {
if (eavail) { // 有事件可用
// 尝试将事件传递给用户空间
res = ep_send_events(ep, events, maxevents);
if (res) // 传递成功
return res; // 这里是有事件情况的函数出口
}
if (timed_out)
return 0; // 这里是超时情况的函数出口
// 这里省略了 busy loop 的代码
if (signal_pending(current)) // 如果有信号挂起,直接返回
return -EINTR;
init_wait(&wait); // 初始化等待队列项,这里就和 ep.wq 有关了
// 设置回调函数,在唤醒进程后自动删除该进程在 ep.wq 中对应的项
wait.func = ep_autoremove_wake_function;
write_lock_irq(&ep->lock);
__set_current_state(TASK_INTERRUPTIBLE); // 设置当前进程状态为 可中断睡眠
eavail = ep_events_available(ep); // 再次检查是否有事件可用
if (!eavail) // 没有事件可用,将当前进程加入到等待队列
__add_wait_queue_exclusive(&ep->wq, &wait);
write_unlock_irq(&ep->lock);
if (!eavail)
// 没事件,将当前进程挂起,如果有中断信号,则会被唤醒,超时也会被唤醒。
// 如果是被有事件的中断信号唤醒的,
// 则先会调用 epitem 的回调函数,即在 epoll_ctl 中注册的 ep_poll_callback 函数,
// 将就绪的 fd 放入 rdllist 就绪队列,再唤醒刚刚加入到 ep->wq 的线程。
// 然后再返回。
timed_out = !schedule_hrtimeout_range(to, slack, HRTIMER_MODE_ABS);
__set_current_state(TASK_RUNNING); // 设置当前进程状态为运行态
eavail = 1;
if (!list_empty_careful(&wait.entry)) {
write_lock_irq(&ep->lock);
if (timed_out)
eavail = list_empty(&wait.entry);
__remove_wait_queue(&ep->wq, &wait);
write_unlock_irq(&ep->lock);
}
}
}
static inline int ep_events_available(struct eventpoll *ep)
{
return !list_empty_careful(&ep->rdllist) ||
READ_ONCE(ep->ovflist) != EP_UNACTIVE_PTR;
}
static int ep_send_events(struct eventpoll *ep, struct epoll_event __user *events, int maxevents)
{
struct epitem *epi, *tmp;
LIST_HEAD(txlist); // txlist 是用来向用户空间拷贝数据的链表
poll_table pt;
int res = 0;
if (fatal_signal_pending(current))
return -EINTR;
init_poll_funcptr(&pt, NULL);
mutex_lock(&ep->mtx);
ep_start_scan(ep, &txlist); // 这个函数用于将 ep->rdllist 拷贝到 txlist
list_for_each_entry_safe(epi, tmp, &txlist, rdllink) { // 遍历 txlist
struct wakeup_source *ws;
__poll_t revents;
if (res >= maxevents)
break;
// 确保进程唤醒
ws = ep_wakeup_source(epi);
if (ws) {
if (ws->active)
__pm_stay_awake(ep->ws);
__pm_relax(ws);
}
list_del_init(&epi->rdllink); // 从就绪队列中删除 epi
// 注意!rdllist 里的 epi 有事件就绪,但不一定是我们感兴趣的事件!
// 所以这里查询 socket 的具体事件,是否为我们感兴趣的事件!
// 也就是说,rdllist 只能告诉我们可能有任何事件发生,
// 但具体是什么事件,是否是我们感兴趣的事件,需要通过 ep_item_poll 函数来判断
revents = ep_item_poll(epi, &pt, 1);
if (!revents)
continue; // 没有我们感兴趣事件,遍历下一个 epi
// 有我们感兴趣的事件,将 revents 和 epi->event.data 其拷贝到用户空间的 events 里
events = epoll_put_uevent(revents, epi->event.data, events); // [[具体实现见下面]]
if (!events) {
list_add(&epi->rdllink, &txlist); // 拷贝失败,将 epi 放回 txlist
ep_pm_stay_awake(epi);
if (!res)
res = -EFAULT;
break;
}
res++;
if (epi->event.events & EPOLLONESHOT) // 处理 EPOLLONESHOT 的情况
epi->event.events &= EP_PRIVATE_BITS;
else if (!(epi->event.events & EPOLLET)) { // 处理 EPOLLET 的情况
// 如果是 水平触发 模式,将 epi 放回就绪队列,等待下一次事件
list_add_tail(&epi->rdllink, &ep->rdllist);
ep_pm_stay_awake(epi);
}
}
// 要理解这个函数,我们得知道,如果在扫描过程中有 fd 要加入就绪队列,
// 由于 rdllist 已经被锁住了,所以会将这个 fd 放入 ovflist 中,
// 这个函数就是将 ovflist 中的 fd 放回入 rdllist 中。
ep_done_scan(ep, &txlist);
mutex_unlock(&ep->mtx);
return res;
}
epoll_put_uevent(__poll_t revents, __u64 data, struct epoll_event __user *uevent)
{
// 注意这里依然使用的是 __put_user 函数,即从内核空间拷贝到用户空间,并没有使用共享内存!
if (__put_user(revents, &uevent->events) || __put_user(data, &uevent->data))
return NULL;
return uevent+1;
}