注:本文内容引用自张洋老师的知乎文章 https://zhuanlan.zhihu.com/p/...,他是一位存储研发专家。
问题现象和背景
近期,zStorage分布式块存储系统在海光+麒麟+E810网卡环境下,使用FIO工具测试4K单并发随机读/写IO时,延迟达到4ms。而这里唯一的变量是E810网卡;如果采用Mellanox网卡,在同样的测试用例下,IO延迟约为100us左右。那么问题出在网卡吗?还是另有原因?以下是分析和探索的过程分享。
问题定界
要分析上述高时延问题,首先需要对问题进行定界:是zStorage存储软件的问题,还是Host端的问题?针对这一问题,我们做了以下几件事情:
- 时延分析:zStorage FrontEnd从收到IO到应答IO给Host,耗时 < 100us,符合预期。这个实验表明问题大概率不在存储端,但仍不能完全断定问题在Host端。
- 更换HOST:重新使用一个内核版本为5.10的系统,进行相同的测试用例,时延恢复正常。这个结果至少说明,更换内核可以解决问题,问题大概率与内核有关。
- Ftrace跟踪分析:在出问题的HOST上开启Ftrace,跟踪nvmf和rdma相关函数。发现4ms的时延确实出现在Host端的处理上。如下图所示:在58225.458457这个时间点,Host的IB驱动已经收到了存储节点的ACK应答,但直到58225.462423这个时间点才调用
nvme_rdma_send_done
。这个实验表明,确实是Host端的内核存在一个4ms的延迟。
4ms延迟流程深究
根据Ftrace的信息,可以看到在4ms后,由smp_apic_timer_interrupt
触发,完成了消息的发送。
在 Linux 内核中,smp_apic_timer_interrupt 是处理APIC(Advanced Programmable Interrupt Controller)定时器中断的中断处理程序。当该中断触发时,它可以调用定时器相关的处理程序,通常与调度器、进程切换、软中断等内核机制相关。APIC 定时器是每个 CPU 的本地定时器,用于周期性地触发中断,从而执行某些周期性的任务。
具体来说,这个APIC定时器的频率较低,通常在1000Hz以下,而当前出问题的Host为250Hz,因此每4ms调度一次,这与IO延迟4ms是相吻合的。然而,这个频率是在内核编译时确定的,修改起来并不容易,且直觉上问题不太可能出在这里。毕竟,为了解决这个问题,不可能将频率改为100万Hz。那么,为什么IO会在smp_apic_timer_interrupt
的调度流程中完成呢?IO真正的完成应该是在什么流程中呢?
继续探究IO未及时处理的原因
继续使用Ftrace跟踪更多详细的信息。
在ib_cq_completion_softirq
的下一个函数调用是__raise_softirq_irqoff
。翻阅内核源码后,发现irq_poll_sched
这个函数至关重要。它负责调度软中断处理,并与IO完成的路径密切相关。进一步分析irq_poll_sched
的调用时机和作用,可能揭示出为何IO在软中断处理链中被延迟触发。
这意味着,irq_poll_sched
函数并不希望在当前流程中立即处理I/O,而是将当前I/O加入到CPU的延迟任务处理队列中,等待延迟处理。然而,实际情况是处理延迟了4ms,这个延迟确实过长了。注意这一行代码:__raise_softirq_irqoff(IRQ_POLL_SOFTIRQ);
。__raise_softirq_irqoff()
触发了指定类型的软中断,这里是IRQ_POLL_SOFTIRQ
,用于调度一个软中断处理函数来处理加入轮询队列的I/O任务。
接下来,进一步查看内核源码,以分析IRQ_POLL_SOFTIRQ
如何调度以及为什么延迟长达4ms?
IRQ_POLL_SOFTIRQ
这个软中断是与irq_poll_softirq
函数绑定在一起的。这意味着,一旦使用__raise_softirq_irqoff
触发了软中断,应该在短时间内调用irq_poll_softirq
,以处理刚才的I/O完成。然而,在实际情况中,I/O处理延迟了4ms,这表明在软中断的调度和处理之间存在瓶颈或延迟。
对IO处理流程稍作总结
ROCE网卡的中断处理流程已经梳理清楚:
irdma
发送数据到存储节点,使用 RDMA 协议传递数据。- 存储节点的网卡收到数据后,在大约 10 微秒内返回 ACK,表明数据接收完成。
irdma
通过网卡中断收到 ACK 后,开始处理当前的 CQE。它将 CQE 传递给 CPU 的irq_poll
机制,并通过__raise_softirq_irqoff(IRQ_POLL_SOFTIRQ)
触发另外一个软中断。- 这个时候,
irq_poll
的软中断机制应该被立即处理,快速响应以完成 CQE 处理。 - 实际上,软中断并未在触发后立即被处理,而是等到 4 毫秒后由
smp_apic_timer_interrupt
触发。这导致了预期外的延迟。
__raise_softirq_irqoff为什么未及时触发处理软中断?
经过一番查找,终于找到了答案:https://github.com/torvalds/l...
这段描述解释了为何在特定场景中,需要使用raise_softirq_irqoff()
而不是__raise_softirq_irqoff()
来触发软中断,特别是在非中断上下文中(如工作队列或内核线程中的软中断调度)。
简单来说,某些场景下__raise_softirq_irqoff
是不生效的。而raise_softirq_irqoff
函数是对__raise_softirq_irqoff
的封装,如果当前不在中断上下文,则会调用一个wakeup_softirqd
函数,唤醒ksoftirqd
进程以立即处理软中断。
结论
至此,问题原因已经很清晰了,就是将__raise_softirq_irqoff
改成raise_softirq_irqoff
即可,删除两个下划线。然而,在不修改内核驱动的情况下,应该如何解决这个问题呢?毕竟在同样的HOST上,Mellanox卡没有出现问题。
尝试修改irdma驱动源代码
IRDMA:这是Intel特定的RDMA驱动,专门处理支持IRDMA的Intel网卡,如E810。它处理RDMA连接的管理、数据传输的加速,并与IBCORE模块协作完成数据的高效传输。
实际上还是有一些思路可行,例如:
- 可否在irdma驱动中增加一行代码,使用不带下划线的
raise_softirq_irqoff
?编译驱动时却报错:WARNING: "raise_softirq" [/root/irdma-1.15.11/src/irdma/irdma.ko] undefined! 未再继续深究。 - 使用Tasklet来触发延迟任务。
Tasklet 是一种基于 SoftIRQ 的延迟执行任务机制,专门为简单、短时间内完成的任务设计。它本质上是一个简化的 SoftIRQ,开发者可以通过 tasklet 将不紧急的工作推迟到稍后执行。TASKLET_SOFTIRQ 是 Linux 内核中的软中断(SoftIRQ)机制之一,专门用于延迟执行轻量级任务的框架。它通过一种称为tasklet的机制来处理可延迟执行的任务,适用于需要在较短时间内完成的工作,而不需要立即执行,并且不需要中断整个系统调度。
经过测试,该方法有效,时延为130us,解决了4ms延迟的问题。
- 可否在irdma驱动中增加
wakeup_softirqd
,以触发软中断处理?——未尝试。 - 其他实验中,导致了内核崩溃,或者RDMA连接不上等问题。——未采用。
扩展阅读:企业存储技术》文章分类索引更新(微信公众号合集标签)
注:本文只代表作者个人观点,与任何组织机构无关,如有错误和不足之处欢迎在留言中批评指正。进一步交流,可以加我的微信:490834312。如果您想在这个公众号上分享自己的技术干货,也欢迎联系我:)
END
作者:张洋
原文:企业存储技术
推荐阅读
欢迎关注企业存储技术极术专栏,欢迎添加极术小姐姐微信(id:aijishu20)加入技术交流群,请备注研究方向。