IO复用-epoll基础

IO复用

IO=等待+拷贝

IO复用是一种通过减少等待时间,来提高IO效率的方式。 其原理是通过同时管理多个IO接口(文件描述符),将等待的时间重叠,这样使得在相等的时间内,出现满足条件的文件描述符的概率增大。

在这里插入图片描述

左图是一般的阻塞/非阻塞IO,右图是IO复用

很明显看出,IO复用的单位时间内文件描述符准备好的概率更大,这就代表其单位时间内等待准备的时间更短,所以IO更高效

认识epoll

epoll是一种通过IO复用提高IO效率的方式,是基于select、poll的提升版本,其克服了select、poll的主要缺陷

1234
select同时等待的fd有上限输入输出参数混合,每次都要重新设定内核和用户之间的数据拷贝多底层监视多个fd时,OS会在内部进行遍历检测
pollnullnull内核和用户之间的数据拷贝多底层监视多个fd时,OS会在内部进行遍历检测
epollnullnullnullnull

下图是《Linux高性能服务器编程》中对三种多路转接的方式的比较

在这里插入图片描述

epoll原理

谈论epoll的原理离不开3点:

  1. 红黑树
  2. 就绪队列(双向链表)
  3. 回调机制

在这里插入图片描述

  1. 创建实例

    • 调用epoll_create()创建一个eventpoll实例,其内部存储了红黑树根节点双链表等重要结构。
  2. 事件注册

    • 当使用epoll_ctl函数向epoll实例中添加需要监视的文件描述符时,可以指定对该文件描述符感兴趣的事件类型(如可读、可写等)。

    • 在这个过程中,epoll会在内核中创建一个epitem结构体来表示这个监视关系,并将该结构体插入到红黑树中,以便快速查找和更新。

  3. 事件就绪检测

    • 当文件描述符上的事件发生时(如数据到达socket缓冲区),内核会检测到这一变化,并触发相应的处理逻辑(回调机制)。
    • epoll使用回调函数来处理这些就绪事件。这些回调函数是内核在检测到事件时自动调用的,而不是由用户显式调用的。
  4. 事件通知

    • 当回调函数被调用时,它会将就绪的文件描述符从红黑树中取出,并添加到就绪链表中(实际上并非取出,而是更改其内部的双链表指针,这是Linux独特的双链表结构,使用强转和位偏移可以找到对象任意位置)。
    • 用户进程通过调用epoll_wait函数来等待和接收就绪事件。epoll_wait函数会阻塞用户进程,直到有就绪事件发生或超时(可以设置为非阻塞)。
    • 当epoll_wait返回时,它会将就绪链表中的事件复制到用户提供的数组中,并返回就绪事件的数量

重要结构体

// epoll实例,调用epoll_create创建的结构体
struct eventpoll {
	spinlock_t lock;
	struct mutex mtx;
	wait_queue_head_t wq;
	wait_queue_head_t poll_wait;
	struct list_head rdllist;
	struct rb_root rbr;
	struct epitem *ovflist;
	struct user_struct *user;
};
// 红黑树节点
struct epitem {
	struct rb_node rbn;
	struct list_head rdllink;
	struct epitem *next;
	struct epoll_filefd ffd;
	int nwait;
	struct list_head pwqlist;
	struct eventpoll *ep;
	struct list_head fllink;
	struct epoll_event event;
};
struct rb_node
{
	unsigned long  rb_parent_color;
	struct rb_node *rb_right;
	struct rb_node *rb_left;
} __attribute__((aligned(sizeof(long))));
struct list_head {
	struct list_head *next, *prev;
};

上图是Linux2.6源码中调用epoll_ctl创建新的文件描述符时创建的节点结构体

  • struct rb_node rbn:存储该节点的颜色、左右孩子、以及父亲节点(这里rb_node结构体中虽然没有用rb_node*来存储父节点,但是通过rb_parent_color可以推断出,Linux为了节省存储开销,将父节点的地址和颜色结合在了一起(联合字段))
  • struct epoll_event event:存储该节点对应的事件
  • struct list_head rdllink:指向双链表
  • struct eventpoll *ep:指向其所属的eventpoll对象
  • struct epoll_filefd ffd:事件句柄信息、文件描述符

红黑树的作用

利用红黑树的平衡原理,使得插入、删除、查找变得高效。当一个文件描述符状态发生变化时(硬件能够检测到),可以高效的遍历到该文件描述符所对应的节点,进而对其属性进行修改。

就绪队列

就绪队列采用双链表,将所有准备就绪的文件描述符链入该队列中,确保了用户读取使用的时候,无需进行任何遍历,直接按顺序recv就行。

回调机制

当你向计算机内输入数据的时候(无论键盘还是网络),计算机的中断机制都会将该数据读取到,然后告知上层。在epoll中,可以提前设置回调函数,当网络中来数据时,OS会发现,并调用该回调函数,然后就可以修改红黑树中的节点状态并将该节点链入就绪队列中。

epoll的ET、LT

LT(Level Trigger 水平触发-epoll默认方式) 、ET(Edge Trigger 边沿触发)

LT:当文件描述符准备好后,只要该文件描述符没有进行处理,那么epoll_wait()就会一直响应该文件描述符,告诉用户你要进行处理了,直到你读取了文件描述符内的数据

ET:只有当文件描述符首次准备好、以及后续有新的内容到达时,epoll_wait()才会通知用户读取数据。一旦你没有进行处理,同时后续也没有新的数据到达,那么该文件描述符内的数据也就读不到了。

注意!!ET模式下,所有的文件描述符都需要是非阻塞状态

  1. 避免阻塞在epoll_wait上
  2. 确保数据读取的完整性

由于上述原因,所以处于ET模式下的epoll要求:程序员必须马上把我文件描述符内的数据读完,不然你可能就再也读不到了。当然处理LT模式下的也可以这么做,但是由于LT不是硬性要求赶快读完,所以就有容错空间。

epoll高效的原因

这里只对epoll相较于select、poll的高效作原因分析

  1. select、poll的fd都是用户态设置的,所以会存在多次的用户内核态切换。而epoll的fd是常驻内核的,减少了状态的切换和拷贝。
  2. select、poll只支持LT模式, 而epoll还支持ET模式。在ET模式下,epoll会尽快接收完报文,然后较快的返回TCP应答报文中的窗口字段值,这样发送方就可以更快、更多的发送数据。
  3. select、poll在检测文件描述符状态的时候,都需要遍历fd。而epoll不需要,大大减少了遍历带来的开销。

epoll需要解决的问题

如何保证数据读取的完整性?

这种问题普遍存在于多路转接中:

当某个文件描述符已经准备好了,调用recv读取数据的时候,发现这个文件描述符内的内容并不完全,此时如果再去调用别的文件描述符,那么原先缓冲区的内容就会被覆盖,原先读到的内容也没了

解决方案:Reactor模式

原理:为每一个文件描述符设置一个缓冲区,在上层通过协议来确保读取到的数据是完整的

对于使用epoll的建议

epoll这么好,是不是所有的服务都要使用epoll呢?不是!

  1. epoll虽然好,但是其体量较大,对于一些嵌入式设备,开销和收益(带来的性能提升)不成正比
  2. epoll出来的较晚,并非所有的系统都兼容epoll,而select出现较早,兼容性较好

不管怎样,epoll的性能优势还是很大的,在具体情况下还是需要具体分析来判断使用epoll还是select

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值