io多路复用有很多种实现,自Linux 2.6内核正式引入epoll以来,epoll已经成为了目前实现高性能网络服务器端的必备技术。相比较select、poll而言,在查询、复制、监听数量上,epoll都有极大优势。
相比select来说,底层用链表来维护监听的文件描述符,数量没有限制,但是还是采用轮询的方式检测是否准备就绪,存在性能问题。
#include <sys/epoll.h>
//新建epoll描述符
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 );
struct epitem
{
struct rb_node rbn; //用于主结构管理的红黑树
struct list_head rdllink; //事件就绪队列
struct epitem *next; //用于主结构体中的链表
struct epoll_filefd ffd; //每个fd生成的一个结构
int nwait;
struct list_head pwqlist; //poll等待队列
struct eventpoll *ep; //该项属于哪个主结构体
struct list_head fllink; //链接fd对应的file链表
struct epoll_event event; //注册的感兴趣的事件,也就是用户空间的epoll_event
}
struct eventpoll
{
spin_lock_t lock; //对本数据结构的访问
struct mutex mtx; //防止使用时被删除
wait_queue_head_t wq; //sys_epoll_wait() 使用的等待队列
wait_queue_head_t poll_wait; //file->poll()使用的等待队列
struct list_head rdllist; //事件满足条件的链表
struct rb_root rbr; //用于管理所有fd的红黑树
struct epitem *ovflist; //将事件到达的fd进行链接起来发送至用户空间
}
struct epoll_event
{
__unit32_t events; // epoll事件
epoll_data_t data; // 用户数据
};
typedef union epoll_data
{
void* ptr; //指定与fd相关的用户数据
int fd; //指定事件所从属的目标文件描述符
uint32_t u32;
uint64_t u64;
} epoll_data_t;
与select相比,epoll分清了频繁调用和不频繁调用的操作。例如,epoll_ctl()是不太频繁调用的,而epoll_wait是非常频繁调用的。
epoll中包含红黑树、就绪链表。
红黑树存储监听的套接字,当添加和删除套接字时,都在红黑树上处理。
使用流程:
如果数据没有读完或者写满,那么这个事件每次都会返回。效率低于ET,尤其是在大流量情况,但是编码简单,不容易出现问题,因为数据没读完,内核会一直通知,不用担心漏掉。
如果数据没有读完或者写满,下次不会返回,除非有新数据进入。select、poll只有水平触发。在某一刻有多个连接同时到达,服务器的TCP就绪队列瞬间积累多个就绪链接,边缘模式只会通知一次,如果accept只处理一个连接,导致TCP剩余连接得不到处理。
边缘模式效率高,尤其是大流量下,会比LT少很多系统调用,但需要考虑数据没读完。
回调函数是谁执行的?tcp协议内核线程。首先我们看下tcp数据包从网卡驱动到kernel内部tcp协议处理调用链:
当数据到达网卡缓冲后,网卡会发起中断,CPU响应中断,执行设备驱动程序。
CPU响应中断网卡中断后,最后会执行tcp协议栈,将数据包写入到socket buffer,并调用注册的ep_poll_callback。
next_rx_action
|-process_backlog
......
|->packet_type->func 在这里我们考虑ip_rcv
|->ipprot->handler 在这里ipprot重载为tcp_protocol
(handler 即为tcp_v4_rcv)
tcp_v4_rcv
|->tcp_v4_do_rcv
|->tcp_rcv_state_process
|->tcp_data_queue
|-> sk->sk_data_ready(sock_def_readable)
|->wake_up_interruptible_sync_poll(sk->sleep,...)
|->__wake_up
|->__wake_up_common
|->curr->func
/* 这里已经被ep_insert添加为ep_poll_callback,而且设定了排它标识WQ_FLAG_EXCLUSIVE*/
|->ep_poll_callback
wake_up_locked
|->__wake_up_common
|->default_wake_function
|->try_wake_up (wake up a thread)
|->activate_task
|->enqueue_task running
回调函数中会从红黑树中找到注册事件,添加到就绪队列,然后唤醒因epoll_wait()阻塞的线程。
static int ep_poll_callback(wait_queue_t *wait, unsigned mode, int sync, void *key)
{
// 获取wait对应的epitem
struct epitem *epi = ep_item_from_wait(wait);
// epitem对应的eventpoll结构体
struct eventpoll *ep = epi->ep;
// 获取自旋锁,保护ready_list等结构
spin_lock_irqsave(&ep->lock, flags);
// 如果当前epi没有被链入ep的ready list,则链入
// 这样,就把当前的可用事件加入到epoll的可用列表了
if (!ep_is_linked(&epi->rdllink))
list_add_tail(&epi->rdllink, &ep->rdllist);
// 如果有epoll_wait在等待的话,则唤醒这个epoll_wait进程
// 对应的&ep->wq是在epoll_wait调用的时候通过init_waitqueue_entry(&wait, current)而生成的
// 其中的current即是对应调用epoll_wait的进程信息task_struct
if (waitqueue_active(&ep->wq))
wake_up_locked(&ep->wq);
}
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。