-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
I/O 多路复用之 epoll #5
Comments
epoll 相关的数据结构与方法与
/* 返回 epoll 实例的文件句柄,size 没有实际用途,传入一个大于 0 的数即可。 */
int epoll_create(int size);
/* 让 epoll(epfd)实例 对 目标文件(fd) 执行 `ADD | DEL | MOD` 操作,并指定”关心“的事件类型 */
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); 与 这是 #include <stdio.h>
#include <unistd.h>
#include <sys/epoll.h>
int main(void)
{
int epfd,nfds;
struct epoll_event ev; // ev用于注册事件,表示自己关心的事哪些事件
struct epoll_event events[5]; // events 用于接收从内核返回的就绪事件
epfd = epoll_create(1); // 创建一个 epoll 实例
ev.data.fd = STDIN_FILENO; // 我们关心的是命令行输入
ev.events = EPOLLIN|EPOLLET; //监听读状态同时设置ET模式(这个后面会讲,可以简单理解成:文件内容发生变化时才会触发对应的事件)
epoll_ctl(epfd, EPOLL_CTL_ADD, STDIN_FILENO, &ev); // 注册epoll事件
for(;;)
{
nfds = epoll_wait(epfd, events, 5, -1); // 进入死循环,最后的 -1 表示无限期阻塞,直到有事件发生
// epoll_wait 返回,表示有对应的事件发生,事件的信息存储在 events 数组中。nfds 表示数组的长度。接下来逐个处理事件
for(int i = 0; i < nfds; i++)
{
if(events[i].data.fd==STDIN_FILENO)
printf("welcome to epoll's word!\n");
}
}
} 接下来我们看看 eventpoll/*
* This structure is stored inside the "private_data" member of the file
* structure and represents the main data structure for the eventpoll
* interface.
*/
struct eventpoll {
// 保护 rbr(红黑树) 和 rdllist(等待队列)
struct mutex mtx;
// 等待队列,用来保存对一个 epoll 实例调用 epoll_wait() 的所有进程。
// 当调用 epoll_wait 的进程发现没有就绪的事件需要处理时,就将当前进程添加到此队列中,然后进程睡眠;后续事件发生,就唤醒这个队列中的所有进程(也就是出现了惊群效应)
wait_queue_head_t wq;
// 当被监视的文件是一个 epoll 类型时,需要用这个等待队列来处理递归唤醒。
// epoll 也是一种文件类型,因此一个 epoll 类型的 fd 也是可以被其他 epoll 实例监视的。
// 而 epoll 类型的 fd 只会有“读就绪”的事件。当 epoll 所监视的非 epoll 类型文件有“读就绪”事件时,当前 epoll 也会进入“读就绪”状态。
// 因此如果一个 epoll 实例监视了另一个 epoll 就会出现递归。如 e2 监视了e1,e1 上有读就绪事件发生,e1 就会加入 e2 的 poll_wait 队列中。
wait_queue_head_t poll_wait;
// 就绪列表(双链表),产生了用户注册的 fd读写事件的 epi 链表。
struct list_head rdllist;
// 保护 rdllist 和 ovflist 。
rwlock_t lock;
/* RB tree root used to store monitored fd structs */
// 红黑树根结点,管理所有”关心“的 fd
struct rb_root_cached rbr;
// 单链表,当 rdllist 被锁定遍历向用户空间发送数据时,rdllist 不允许被修改,新触发的就绪 epitem 被 ovflist 串联起来,
// 等待 rdllist 被处理完了,重新将 ovflist 数据写入 rdllist
struct epitem *ovflist;
/* wakeup_source used when ep_scan_ready_list is running */
struct wakeup_source *ws;
/* The user that created the eventpoll descriptor */
// 创建 eventpoll 的用户结构信息。
struct user_struct *user;
// eventpoll 对应的文件结构,Linux 中一切皆文件,epoll 也是一个文件。
struct file *file;
/* used to optimize loop detection check */
u64 gen;
struct hlist_head refs;
}; 如上面 demo 中所示, epitem// 红黑树用于管理所有的要监视的文件描述符 fd。当我们向系统中添加一个 fd 时,就会对应地创建一个 epitem 结构体。
// epitem 可以添加到红黑树,也可以串联成就绪列表或其它列表。
struct epitem {
union {
/* RB tree node links this structure to the eventpoll RB tree */
// 所在的红黑树
struct rb_node rbn;
/* Used to free the struct epitem */
struct rcu_head rcu;
};
/* List header used to link this structure to the eventpoll ready list */
// 所在的 eventpoll 的就绪列表
struct list_head rdllink;
/* Works together "struct eventpoll"->ovflist in keeping the single linked chain of items. */
// 关联的 eventpoll 中的 ovflist
struct epitem *next;
/* The file descriptor information this item refers to */
// 为最开始的 fd 创建 epitem 时的文件描述符信息
struct epoll_filefd ffd;
/* List containing poll wait queues */
// poll 等待队列
struct eppoll_entry *pwqlist;
/* The "container" of this item */
// 所在的 eventpoll
struct eventpoll *ep;
/* List header used to link this item to the "struct file" items list */
struct hlist_node fllink;
/* wakeup_source used when EPOLLWAKEUP is set */
struct wakeup_source __rcu *ws;
/* The structure that describe the interested events and the source fd */
struct epoll_event event;
}; |
epoll 工作流程
通过 |
|
|
|
再谈
|
对比 | select | epoll |
---|---|---|
连接数限制 | 1024 | 理论上无限制 |
内在处理机制 | 现行轮训 | callback |
TODO | TODO | TODO |
再回头看看 select
的 demo:
int main(){
int fds[] = ...; // 关心的 socket 数组
fd_set source_fds; // 将我们关心的 socket 保存到 fd_set 中
fd_set temp_fds; // 临时变量,作为 select 的返回值
// 初始化 source_fds
FD_ZERO(&source_fds);
for (int i=0; i<fds.length; i++) {
FD_SET(fds[i], &source_fds);
}
while(1) {
// select 将一个 fd_set 作为入参,将就绪的 socket 又填充如这个入参中作为出参返回
// 因此,为了快速重置,设置一个临时变量,避免每次都要进行 source_fds 的重置
temp_fds = source_fds;
// select 会阻塞,直到关心的 socket 上有事件发生
int n = select(..., &temp_fds, ...);
// 在用户态遍历 socket,检查是否有我们关心的事件发生
for (int i=0; i < fds.length; i++) {
if (FD_ISSET(fds[i], &temp_fds)) {
// ... 进行对应的逻辑处理
FD_CLR(fds[i], &temp_fds);
}
}
}
return 0;
}
select
主要有两点限制:
- 所能关注的 socket 太少,只能有 1024 个,对于一些大型 web 应用来说有点捉襟见肘;
- 尽管
FD_SET
是O(1)
的操作,但返回后还要在用户态遍历一次整个fd_set
,这是一个线性操作
再回过头来看 epoll
:
int main() {
int fds[] = ...; // 关心的 socket 数组
int epfd = epoll_create(...); // 创建 epoll 实例
// 将关心的 socket 添加到 epoll 中(红黑树等)
for (int i=0; i < fds.length; i++){
epoll_ctl(epfd,EPOLL_CTL_ADD, fds[i], ...);
}
// 定义一个结构,用来接收就绪的事件
struct epoll_event events[MAX_EVENTS];
while(1){
// 如果无事件发生,那么进程将阻塞在这里
// 如果有事件发生,则返回就绪的事件个数,同时事件被存储在 events 中
int n = epoll_wait(epfd, &events,...);
for (int i=0; i < n; i++) {
// 通过下标取到返回的就绪事件,进行对应的逻辑处理
new_event = events[i];
}
}
return 0;
}
- 每次
epoll_wait
返回的都是活跃的 socket,根本不用全部遍历一遍 epoll
底层使用到了 红黑树 来存储所关心的socket
,查找效率有保证;注册的对应的事件通知是通过回调的方式执行的,这种解耦、相互协作的方式更有利于操作系统的调度。
No description provided.
The text was updated successfully, but these errors were encountered: