目录
0.往期文章
1.epoll的三个接口
定位:只负责进行等,不进行拷贝。
作用:epoll系统调用是用来让我们的程序监视多个文件描述符的状态变化的;程序会停在epoll这里等待, 直到被监视的文件描述符有一个或多个发生了状态改变。1.epoll_create
- 参数:
size
:这是内核用来内部优化 epoll 实例的提示值,表示你预期将要添加到 epoll 实例中的文件描述符的最大数量。然而,这个参数在 Linux 2.6.8 及以后的版本中实际上被忽略了,因为内核能够动态地调整大小。- 返回值:
- 成功时,返回一个非负整数,即新创建的 epoll 实例的文件描述符。
- 出错时,返回 -1,并设置 errno 以指示错误原因。
2.epoll_ctl
参数:
- epfd:这是由
epoll_create
或epoll_create1
返回的 epoll 实例的文件描述符。它指定了要操作的 epoll 实例。- op:这是一个操作码,指定了要对目标文件描述符
fd
执行的操作类型。有效的操作码包括:
EPOLL_CTL_ADD
:向 epoll 实例注册一个新的文件描述符,以便监视其上的事件。EPOLL_CTL_MOD
:修改已经注册到 epoll 实例中的文件描述符的事件类型或用户数据。EPOLL_CTL_DEL
:从 epoll 实例中删除一个文件描述符,停止对其上事件的监视。- fd:这是要操作的目标文件描述符,即要注册、修改或删除的文件描述符。
- event:这是一个指向
struct epoll_event
结构体的指针,它包含了要注册或修改的事件信息。如果操作是EPOLL_CTL_DEL
,则此参数可以为 NULL。返回值:
- 成功时,
epoll_ctl
返回 0。- 出错时,返回 -1,并设置 errno 以指示错误原因。
结构体
epoll_event
typedef union epoll_data { void *ptr; int fd; uint32_t u32; uint64_t u64; } epoll_data_t; struct epoll_event { uint32_t events; /* Epoll 事件类型 */ epoll_data_t data; /* 用户数据 */ };
- events:这是一个位掩码,用于指定要监视的事件类型。常见的事件类型包括
EPOLLIN
(可读事件)、EPOLLOUT
(可写事件)、EPOLLERR
(错误事件)等。此外,epoll 还支持EPOLLET
(边缘触发模式)和EPOLLONESHOT
(只触发一次,就移除该fd)等特殊事件类型。- data:这是一个联合体,包含了用户数据。它可以是一个指针、文件描述符、32位或64位无符号整数。这个数据在事件触发时会原样返回给用户,以便用户识别是哪个文件描述符触发了事件。
3.epoll_wait
参数:
- epfd:这是由
epoll_create
或epoll_create1
返回的 epoll 实例的文件描述符。它指定了要等待事件的 epoll 实例。- events:这是一个指向
struct epoll_event
结构体数组的指针,用于接收准备就绪的事件。在调用epoll_wait
后,该数组会被填充为准备就绪的文件描述符和它们关联的事件(如读就绪、写就绪等)。- maxevents:这个参数指定了
events
数组可以容纳的最大事件数。epoll_wait
最多会返回这个数目的准备就绪事件。如果少于这个数目的事件准备就绪,那么实际返回的事件数会少于maxevents
。- timeout:这个参数指定了
epoll_wait
在没有事件准备就绪时应等待的最长时间(以毫秒为单位)。如果设置为 -1,epoll_wait
将无限期地等待,直到至少有一个事件准备就绪。如果设置为 0,epoll_wait
将立即返回,不等待任何事件发生。如果设置为一个正整数 N,epoll_wait
将等待最多 N 毫秒。返回值:
- 当成功时,
epoll_wait
返回准备就绪的文件描述符数量。如果返回值为 0,表示在指定的超时时间内没有事件发生。- 当出错时,返回 -1,并设置全局变量
errno
以指示错误类型。注意事项:
epoll_wait
是阻塞调用,这意味着在指定的超时时间内如果没有任何事件准备就绪,调用线程将被阻塞。如果超时时间到达并且没有事件准备就绪,epoll_wait
将返回 0。epoll_wait
使用的epoll_event
结构体包含了与事件相关的文件描述符和数据。在调用epoll_wait
后,用户可以通过遍历events
数组来处理所有准备就绪的事件。epoll_wait
是 Linux 下进行高性能网络编程和并发编程的重要工具之一,它能够显著提高处理大量并发连接时的效率和可扩展性。
2. epoll的工作原理,和接口对应
1.理解数据到达主机
数据到达主机的过程是一个复杂的多层封装与解封装过程,涉及到了网络协议栈的各个层次以及网络设备的协同工作。这个过程确保了数据能够准确、可靠地从源主机传输到目的主机。
但是数据到主机,一定先经过网卡的,由网卡将数据交给网络协议栈,OS又如何知道网卡中有数据呢?
答案是,中断机制
当网卡接收到数据时,它会通过中断的方式通知CPU。中断是CPU与硬件设备之间的一种通信方式,用于在硬件事件发生时请求CPU的注意。当网卡接收到一个数据包时,它会触发一个中断信号,该信号被发送到CPU的中断控制器。CPU在接收到中断信号后,会暂停当前正在执行的程序,转而执行一个中断服务例程(ISR),该例程负责处理网卡接收到的数据。
2.epoll的工作原理
当OS创建epoll模型,首先要在底层构建一颗红黑树。每个红黑树的结点一定要包括以下几个字段:int fd; unit32_t events; struct rb_node*left ,*right。
该红黑树用来标识用户让内核关心的fd及其对应的事件。该红黑树由epoll_ctl进行增加,删除,修改操作。 fd就是key值。除了维护红黑树,还要维护一个就绪队列,其中每个结点包括:int fd; unit32_t revents; struct node*next ,*prev。
一旦网卡中有数据了,网卡同个中断交给OS,接着网络协议栈就拿到数据了,所以在输入和输出缓冲区里有没有数据,OS是很清楚的,OS在缓冲区中设置回调方法,该回调方法就是epoll_ctl构建的。底层一旦有数据就绪并且是用户关心的,此时OS就会调用回调方法构建就绪队列的结点,填充清楚,是哪一个fd的什么事件已经就绪了,并连入就绪队列。
上层要知道哪些数据就绪了,就可以直接调用epoll_wait,他会将相关的结点通过events字段返回出去。所以epoll检测有没有就绪事件, 这个过程的事件复杂度就是O(1),因为epoll_wait只需要检测就绪队列是否为空。获取就绪事件只能是O(N),因为只能将结点一个一个拷贝到events字段,在这个过程中,epoll_wait会将就绪事件,依次言给按照顺序放入到我们定义的用户缓冲区数组中。
那么使用epoll_create就能创建一个epoll模型,只要有需要,在OS中是可以同时存在多个epoll模型的,那么OS就要管理存在的epoll模型:
当某一进程调用 epoll_create 方法时, Linux 内核会创建一个 eventpoll 结构体, 这个结构体中有两个成员(红黑树和就绪队列)与 epoll 的使用方式密切相关.
- 每一个 epoll 对象都有一个独立的 eventpoll 结构体, 用于存放通过 epoll_ctl 方法向 epoll 对象中添加进来的事件.
- 这些事件都会挂载在红黑树中, 如此, 重复添加的事件就可以通过红黑树而高效的识别出来(红黑树的插入时间效率是 lgn, 其中 n 为树的高度).
- 而所有添加到 epoll 中的事件都会与设备(网卡)驱动程序建立回调关系, 也就是说, 当