内核epoll的实现原理及用户态epoll的设计思路

本文详细探讨了TCP编程接口中socket等操作的回调机制,epoll模块的使用时机,为何自定义用户态epoll,以及EPOLL的红黑树与就绪队列实现。重点讲解了ET和LT模式在实际应用中的区别,涉及线程安全设计和开源项目中的模式选择。

1.思考 

在tcp的编程接口socket,bind,connect,listen,accpect,recv,send,close这些中,
哪些动作对方能感知到可以产生回调?
connect,send,close,recv

2.何时回调epoll模块 

1.三次握手成功时,会有一次回调;
2.接受到客户端发来数据的时候,且要返回ACK给客户端的时候,会有一次回调;
3.服务器端给客户端发消息之后服务器端收到来自客户端的ACK消息,服务器端会把服务器端的send_buf
的空间里面的客户端已经确认收到的消息对应的空间清空,这里也会产生回调;
4.当客户端向服务器端发送close请求的时候,服务器端也会产生回调.


协议栈返回fd,status和epoll_fd(红黑树的代号)就可以解决协议栈与epoll之间的关系.

 

3.为什么要自己做一个用户态的epoll?

在操作系统的epoll是被内核的协议栈调用的,我们如果想要对它有特点的需求,需要自己实现一个.

4.epoll是怎么实现的?

理解的关键就是红黑树与就绪队列,红黑树里的结点和就绪队列里的结点是一样的.
红黑树里存储的是所有需要监控的fd,就绪队列里面存储的是所有待处理的fd.
红黑树里的小结点的内容包含了fd和event.

epoll_create(1);--创建红黑树
中的参数最早的时候是要创建这个长度的就绪队列有多长,代表一次最多检测多少个,
之后把这个队列变成链式存储了,这个长度自然也就没有必要了.也就是说之前epoll中对应的就绪队
列是顺序存储的队列,现在的就绪队列改成是链式存储的队列了.
为了兼容之前的版本,我们现在也加上了这个参数,哪个版本改的呢?应该是3.0版本以后改的,可以
查实一下.
经查实,这个说法是错误的,内核在2.6.9版本中更改了eventpoll的存储结构,将其由原来的的hash
表存储改成现在的红黑树的存储,原来的那个size的存储是哈希表的长度.

epoll_ctl(epfd,op,fd,ev);--增删改查红黑树上的结点
1.epfd-->eventpoll;
2.

epoll_wait(epfd,events,length,time)循环loop,把就绪队列里面的数据copy出来(select是拷贝就
绪的).
time的作用是预防就绪队列为空,等待time时间不为空.
0 ---不阻塞
-1---一直阻塞
就绪队列是怎么管理的?如何进行增删改查的?
通过回调函数将对绪队列中的结点做相关操作,epoll_wait则去将队列里面的结点拷贝到红黑树中,
回调和epoll是两个流程,回调是协议栈实现的,回调函数操作的就绪队列.

epoll快是因为拷贝的是就绪队列,但select拷贝的是全部队列。
有个问题:
拷贝的队列用的内存是哪里的内存?用户空间还是内核空间的?


2.6.1版本
struct eventpoll {
	/* Protect the this structure access */
	rwlock_t lock;

	/*
	 * This semaphore is used to ensure that files are not removed
	 * while epoll is using them. This is read-held during the event
	 * collection loop and it is write-held during the file cleanup
	 * path, the epoll file exit code and the ctl operations.
	 */
	struct rw_semaphore sem;

	/* Wait queue used by sys_epoll_wait() */
	wait_queue_head_t wq;

	/* Wait queue used by file->poll() */
	wait_queue_head_t poll_wait;

	/* List of ready file descriptors */
	struct list_head rdllist;

	/* Size of the hash */
	unsigned int hashbits;

	/* Pages for the "struct epitem" hash */
	char *hpages[EP_MAX_HPAGES];
};


在2.6.9版本中做了修改


2.6.11.10版本
struct eventpoll {
	/* Protect the this structure access */
	rwlock_t lock;

	/*
	 * This semaphore is used to ensure that files are not removed
	 * while epoll is using them. This is read-held during the event
	 * collection loop and it is write-held during the file cleanup
	 * path, the epoll file exit code and the ctl operations.
	 */
	struct rw_semaphore sem;

	/* Wait queue used by sys_epoll_wait() */
	wait_queue_head_t wq;

	/* Wait queue used by file->poll() */
	wait_queue_head_t poll_wait;

	/* List of ready file descriptors */
	struct list_head rdllist;

	/* RB-Tree root used to store monitored fd structs */
	struct rb_root rbr;
};

 

5. et和lt如何实现的?

et(边沿触发)模式下:
触发完一次后,设置一个flag,即使recvbuf不为空,也不会被触发;

lt(水平触发)模式下:
触发完一次后,如果recvbuf不为空,就会一直触发;

et和lt是通过回调函数次数实现的:
if(触发过一次)
{
  if(buf不为空 && lt模式 )
  {
      继续触发;
  }
}
如何做到一直回调?只能在回调的时候判断.

当send的时候,
et模式下,如果sendbuf中有剩余空间只触发一次,即使后面有剩余空间,也不会触发.
lt模式下,如果sendbuf中有剩余空间,就会一直触发.



epoll 对文件的描述符的操作有两种模式 : LT(Level Trigger, 电平触发)模式 和 ET(Edge 
Trigger ,边沿触发)模式。LT模式是默认的工作模式,这个模式下epoll相当于一个效率较高的poll。
当往epoll中内核事件表中注册EPOLLET事件时,epoll将以ET模式来操作该文件描述符。ET是epoll的
高效模式。

        对于采用LT工作的文件描述符,当epolll_wait检测到其上有事件发生并将此事件通知应用程
序后,应用程序可以不立即处理该事件。这样当应用程序下次调用epoll_wait时,epolll_wait还会再
次向应用程序通知此事件,直到有该事件被处理。而对于采用ET模式的文件描述符,当epoll_wait检
测当其上有事件发生时并将此事件通知应用程序后,应用程序必须立即处理该事件,因为后序的
epolll_wait调用不再讲此事件通知应用程序,可见,ET模式在很大程度上降底了同一个epoll事件被
重复触发的次数,因此效率要比LT模式高。

6.epoll的线程安全设计

1.对rbtree-->加锁;
2.对queue -->spinlock
3.epoll_wati,cond,mutex

7.开源项目中的ET和LT的选择

开源项目中的ET和LT的选择

ET和LT

系统调用中断(EINTR)与SIGCHLD信号的处理

ET模式
因为ET模式只有从unavailable到available才会触发,所以

读事件:需要使用while循环读取完,一般是读到EAGAIN,也可以读到返回值小于缓冲区大小;
如果应用层读缓冲区满:那就需要应用层自行标记,解决OS不再通知可读的问题

写事件:需要使用while循环写到EAGAIN,也可以写到返回值小于缓冲区大小
如果应用层写缓冲区空(无内容可写):那就需要应用层自行标记,解决OS不再通知可写的问题。

LT模式
因为LT模式只要available就会触发,所以:

读事件:因为一般应用层的逻辑是“来了就能读”,所以一般没有问题,无需while循环读取到EAGAIN;
如果应用层读缓冲区满:就会经常触发,解决方式如下;

写事件:如果没有内容要写,就会经常触发,解决方式如下。
LT经常触发读写事件的解决办法:修改fd的注册事件,或者把fd移出epollfd。

总结
目前好像还是LT方式应用较多,包括redis,libuv.

LT模式的优点在于:事件循环处理比较简单,无需关注应用层是否有缓冲或缓冲区是否满,只管上报事件
缺点是:可能经常上报,可能影响性能。


在我目前阅读的开源项目中:
TARS用的是ET,Redis用的是LT,Nginx可配置(开启NGX_HAVE_CLEAR_EVENT--ET,开启NGX_USE_LEVEL_EVENT则为LT).

高群凯《深入剖析Nginx-IO多路复用模型》

ET在处理链接的时候注意的问题 

8.ET和LT的测试

menwen的ET和LT测试,搜索【LT和ET的服务端和客户端代码】

 

 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值