从linux源码,跟踪epoll的ET/LT模式下,事件如何处理

之前项目中用到的epoll,大部分都是epoll的ET模式。在实际测试中,对于epoll的LT模式,也发现一些坑,近期打算结合linux源码,深入理解一下背后的逻辑。

1、一个异常现象

服务端用epoll LT模式,主函数起10个进程,接收客户端请求。客户端用webbench模拟请求。strace发现有accept异常。

2、epoll事件流的变化

2.1 epoll事件添加到哪里去?添加到fd事件来源对应的socket中

以上面socket为例,epoll_ctl 添加事件的linux关键调用栈为:

linux/fs/eventpoll.c

SYSCALL_DEFINE4(epoll_ctl, int, epfd, int, op, int, fd,
        struct epoll_event __user *, event)
ep_insert
--ep_item_poll
    -- epi->ffd.file->f_op->poll

最后的f_op->poll来自socket创建fd时的文件属性函数socket_file_ops : poll =        sock_poll。在sock_poll中,会继续陷入,到tcp_poll中。

对应内核net/socket.c
:1219
SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)
--sock_map_fd
    --struct file *sock_alloc_file(struct socket *sock, int flags, const char *dname)
其中文件属性为:
file = alloc_file(&path, FMODE_READ | FMODE_WRITE,
		  &socket_file_ops);
&socket_file_ops即socket_fd对应的文件属性函数

所以,epoll_ctl是把待观察的event,添加到tcp_poll中的wait队列。这个队列,只会唤醒关心刚加入的fd的状态。其他fd的状态,与此队列无关。

2.2 epoll事件如何触发

当server, listen完,有客户端发起连接请求,此时内核网卡模块陷入 tcp_prequeue函数,发起一个POLLIN事件,通知到epoll的回调函数

tcp_prequeue发起IN事件:

    wake_up_interruptible_sync_poll(sk_sleep(sk),
                       POLLIN | POLLRDNORM | POLLRDBAND);
        --if (curr->func(curr, mode, wake_flags, key) 

func即eventpoll回调函数:
    static int ep_poll_callback(wait_queue_t *wait, unsigned mode, int sync, void *key)
                --list_add_tail(&epi->rdllink, &ep->rdllist);
        		--wake_up_locked(&ep->wq);//epoll_wait() set ep->wq->func= default_wake_function;

上述回调函数把event加入全局ready列表,list_add_tail(&epi->rdllink, &ep->rdllist);

wake_up 唤醒epoll_wait时,挂载ep->wq上,正在pending的进程。default_wake_function函数中,唤醒一个task_normal(#define TASK_NORMAL        (TASK_INTERRUPTIBLE | TASK_UNINTERRUPTIBLE))的进程。注意内核参数:EPOLLEXCLUSIVE ,进程排他设置。

在2.1和2.2,【socket的epoll接收场景下】内核不关心是LT模式还是ET模式,是IN事件还是OUT事件。只是发现有事件,就通知。

2.3 epoll事件如何处理  

epoll_wait阻塞等待事件,将当前task设置成TASK_INTERRUPTIBLE 。等待wake_up唤醒。当进程被唤醒时,

ep_scan_ready_list
    ep_send_events_proc
        --list_del_init(&epi->rdllink);//把当前事件从ready列表中删除

		revents = ep_item_poll(epi, &pt);//再次陷到tcp_poll执行一次poll操作。为了获取tcp_poll的真实事件,IN/OUT等类型。
        else if (!(epi->event.events & EPOLLET)) {
        list_add_tail(&epi->rdllink, &ep->rdllist);//LT模式时,将当前事件再次添加到ready列表中
        ...
    
    //ep->rdllist如果有ready事件,且有pending的进程,就再唤醒一个进程来处理ready事件。
   	if (!list_empty(&ep->rdllist)) {
		/*
		 * Wake up (if active) both the eventpoll wait list and
		 * the ->poll() wait list (delayed after we release the lock).
		 */
		if (waitqueue_active(&ep->wq))
			wake_up_locked(&ep->wq);//就再唤醒一个进程来处理ready事件

 可以看到,对于LT事件,因为再次添加到ready队列,又唤醒了一个epoll_wait的进程。

3 测试场景

3.0 LT/ET 模式,1个server,多个client, epoll_wait和accept之间sleep 5s

ET:

./epoll 1 112 5 1
server:1  pid:3149
eventnum:1,event:1,num:1   //sleep 5s开始,此时触发3个client请求连接
get client:3149,csd:5,counttmp:0

eventnum:1,event:1,num:1
get client:3149,csd:5,counttmp:0
最终,处理了2个请求。

LT:

./epoll 0 112 5 1
server:1  pid:3149
eventnum:1,event:1,num:1   //sleep 5s开始,此时触发3个client请求连接
get client:3149,csd:5,counttmp:0

eventnum:1,event:1,num:1
get client:3149,csd:5,counttmp:0

eventnum:1,event:1,num:1
get client:3149,csd:5,counttmp:0

eventnum:1,event:1,num:1
get client:3149,csd:5,counttmp:0

最终,处理了4个请求。

详细流程如下:

ET
当有1个进程,多个消息接入,会丢消息,且很严重。
客户端1 event1 wakeup 回调函数ep_poll_callback,将sockevent(event1)添加到rdlist

epoll_wait:
kernel:step1 内核态检查到rdlist有数据(event1),准备发送给用户态ep_send_events_proc,同时删除rdlist元素。此时ep_item_poll(tcp_poll)获取到内核有事件,也不会再添加到rdlist。从内核态返回后,rdlist为空。
           当rdlist为空时,task再次pending在TASK_INTERRUPTIBLE状态
user:step2 
    sleep 3s //在第一次sleep时,又触发多个客户端链接,客户端2触发的ep_poll_callback回调执行,sockevent添加到rdlist(event2)。客户端3再次到达时,就不再添加到rdlist链表了。即,此时无论有多少客户端此时到达,rdlist只有1个事件。
    accept

处理了event1和event2

LT
当有1个进程,多个消息接入,不会丢消息。

客户端1 event1 wakeup epoll_wait
epoll_wait
kernel:step1:epoll_wait内核态检查到rdlist有数据(event3),准备发送给用户态ep_send_events_proc,同时删除rdlist元素。此时ep_item_poll(tcp_poll)获取到内核有事件,再次添加到rdlist,list_add_tail(&epi->rdllink, &ep->rdllist)事件(event3)。从内核态返回后,rdlist不为空。
user:
    step2:
    sleep 3s //在sleep时,又触发多个客户端链接,客户端2/3/4 此时ep_poll_callback回调执行,rdlist因为已经有相同记录,不再添加。
    accept

epoll_wait再次处理rdlist事件。即,无论事件接下来还剩多少,只要当前还有,rdllist就不为空。

处理了event1、event2、event3、event4.

3.1 LT/ET模式下,10个server进程,1个client请求连接,在epoll_wait和accept之间sleep1s

LT:accept异常

get client:3386
handler error:3384
handler error:3387
handler error:3388
handler error:3385
handler error:3383
handler error:3389
handler error:3382
handler error:3381
handler error:3380


ET:

get client:3414

LT出现唤醒风暴,ET正常处理。

3.2 LT/ET模式下,10个server进程,10个client请求,webbench -c 10 -t 10 http://192.168.27.136:112/

两种模式下,error相当。

3.3 ET模式中,accept按照while(1)来接收,error会比LT少一些。

所以,LT模式使用起来,需要特别关注唤醒风暴,也就是惊群现象。在高并发场景,LT/ET都会有唤醒风暴,但是ET使用while处理accept,会增加一些处理能力。

=====================

参考链接:

再谈Linux epoll惊群问题的原因和解决方案 https://blog.csdn.net/dog250/article/details/80837278

Linux网络编程“惊群”问题总结 https://www.cnblogs.com/alantu2018/p/8469520.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值