select、poll、epoll的介绍请参考:https://blog.csdn.net/zymill/article/details/79998593
epoll详解:https://www.cnblogs.com/lojunren/p/3856290.html
其中select实现中讲的select调用步骤,我的理解如下:
1.将设定好的fd_set从用户态拷贝到内核态。监听fd的操作只有内核才能完成,因而必须有这样一个过程。
2. fd_set里的每个fd中都有一个进程等待队列。当fd准备就绪的时候就会通知这个进程。如果想要fd通知调用select的进程,那么必须将该进程注册到fd的等待队列中。也就是说,只要调用select,就必须先遍历一遍依次注册进程。可以认为是初始化吧。
3. 第二步在向fd注册进程时,如果发现fd已就绪,那么就可以在遍历完所有的fd后,直接返回。如果一个都没有就绪,那么调用select的进程就会进入睡眠,直到有一个fd通知它的所有的等待队列中的进程(含调用select的进程),select再次遍历fd,查看有没有就绪的fd。
4.将就绪的fd_set从内核态复制到用户态。
关于epoll高效的原因:
- epoll_wait调用ep_poll,当rdlist为空(无就绪fd)时挂起当前进程,直到rdlist不空时进程才被唤醒。
- 文件fd状态改变(buffer由不可读变为可读或由不可写变为可写),导致相应fd上的回调函数ep_poll_callback()被调用。
- ep_poll_callback将相应fd对应epitem加入rdlist,导致rdlist不空,进程被唤醒,epoll_wait得以继续执行。
- ep_events_transfer函数将rdlist中的epitem拷贝到txlist中,并将rdlist清空。
- ep_send_events函数(很关键),它扫描txlist中的每个epitem,调用其关联fd对用的poll方法。此时对poll的调用仅仅是取得fd上较新的events(防止之前events被更新),之后将取得的events和相应的fd发送到用户空间(封装在struct epoll_event,从epoll_wait返回)。
- 使用事件回调机制来代替轮询