epoll原理学习
转自:https://zhuanlan.zhihu.com/p/119400472
https://zhuanlan.zhihu.com/p/187463036
1.相关函数
#include <sys/epoll.h> int epoll_create(int size); int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
2.介绍
转自:https://zhuanlan.zhihu.com/p/116901360
epoll_create的做法是创建出一个内核事件表,实际上就是创建文件,这其中包括文件描述符的分配、文件实体的分配等等。
进程中的文件描述符指向文件结构体file,其中有元素指向epoll文件描述符,epoll中有指针指向eventpoll结构体,其中有等待队列,就绪队列,所有感兴趣的socket描述符存放在红黑树中。
那么简单来说,在epoll wait时,过程如下:
asmlinkage long sys_epoll_wait(int epfd, struct epoll_event __user *events, int maxevents, int timeout) { ... error = ep_poll(ep, events, maxevents, timeout); return error; } static int ep_poll(struct eventpoll *ep, struct epoll_event __user *events, int maxevents, long timeout) { if (list_empty(&ep->rdllist)) { // 加入阻塞队列 init_waitqueue_entry(&wait, current); add_wait_queue(&ep->wq, &wait);// 用于将等待元素wait插入到等待队列的头部 for (;;) { // 挂起 set_current_state(TASK_INTERRUPTIBLE); // 超时或者有就绪事件了,则跳出返回 if (!list_empty(&ep->rdllist) || !jtimeout) break; // 被信号唤醒返回EINTR if (signal_pending(current)) { res = -EINTR; break; } // 设置定时器,然后进程挂起,等待超时唤醒(超时或者信号唤醒) jtimeout = schedule_timeout(jtimeout); } // 移出阻塞队列 remove_wait_queue(&ep->wq, &wait); // 设置就绪 set_current_state(TASK_RUNNING); }
本人认为的过程:调用epoll_wait将所有事件加入等待队列,如果有就绪事件,即就绪队列上不为空了,那么就返回。那么这个线程被阻塞在检查是否有就绪事件,就绪事件又是谁负责放上去的呢?答:是向epoll事件表中加入文件描述符时设置的回调函数:
static int ep_poll_callback(wait_queue_t *wait, unsigned mode, int sync, void *key) { int pwake = 0; unsigned long flags; struct epitem *epi = EP_ITEM_FROM_WAIT(wait); struct eventpoll *ep = epi->ep; // 插入就绪队列 //它负责将当前文件描述符加入就绪队列 list_add_tail(&epi->rdllink, &ep->rdllist); // 唤醒因epoll_wait而阻塞的进程 //紧接着唤醒epollwaite的进程/线程 if (waitqueue_active(&ep->wq)) wake_up(&ep->wq); if (waitqueue_active(&ep->poll_wait)) pwake++; return 1; }
在文件对应的inode上注册一个回调。当文件满足条件的时候,就会唤醒因为epoll_wait而阻塞的进程。接着epoll_wait会收集事件返回给用户。