BPF and the realtime patch set
By Jake Edge
7月份的时候,Linus Torvalds在5.3 merge window时期合入patch增加了PREEMPT_RT选项。这意味着realtime(实时性内核) patch set从长久以来的独立代码要正式合入Linux mainline kernel了。随着realtime相关代码的逐渐合入,必定会出现一些摩擦。我们刚刚就看到一例关于BPF子系统的争执。
这个讨论起源于Sebastian Andrzej Siewior发给BPF mailing list的一个patch,其中提到了BPF在打开realtime的环境中运行时碰到的3个问题,因此加了Kconfig规则来确保BPF只在未打开PREEMPT_RT的kernel里面可用:
Disable BPF on PREEMPT_RT because
it allocates and frees memory in atomic context
it uses up_read_non_owner()
BPF_PROG_RUN() expects to be invoked in non-preemptible context
Siewior提到他试过可以解决这个memory allocation问题,不过“我不知道如何解决另外两个问题”。邮件中他也给出了一些建议,如何让大家自己的功能和realtime patch set相处和谐。
Daniel Borkmann回复中指出,Siewior采取的这个简单解决方案实际上会导致BPF整体全部被禁用,而事实上其他一些使用BPF的子系统不会受到realtime patch的影响。Siewior又提出一个方案,想知道大家是否赞成,不过David Miller明确指出他不认为这个新方案有用:“在PREEMPT_RT打开的情况下就要求关掉BPF,这不是一个好的开始,毕竟BPF已经是Linux系统的一个非常重要的功能了。”
不过,Siewior也说得很清楚了,BPF的实现方式和realtime patch set的需求有根本性的兼容问题。Thomas Gleixner也提供了更多信息,期望能找到其他方式来解决:
#1) BPF会无条件的关闭抢占,这样就没法像kernel里面其他部分那样可以利用spinlock等等锁机制来做RT substitution了。
#2) BPF会在atomic context进行内存分配,这个行为其实哪怕是在非RT的情况下也是不靠谱的。这跟 #1 有关。
#3) BPF会用到up_read_non_owner(),这个函数其实创造出来时只是为了解决此前的一些遗留问题,不应该在新的地方也使用。
Miller回复认为systemd和IR驱动已经有对BPF的依赖了,甚至LSM module也很有可能会用BPF来实现。在他之前的邮件里面,他提到关掉BPF会导致所有的packet sniffing(包监听)机制都被关闭,所以tcpdump和Wireshark都没法用了。不过Gleixner提出他的说法可能有点夸大。Gleixner一直在用打了Siewior的patch的Deabin系统进行测试,没有看到什么systemd的问题。此外,哪怕packet sniffing没有利用BPF,也就是需要对每个packet做一次copy到user space的操作,这样也是可行的。因此这并不能证明BPF必须要打开。
尽管如此,他也还是希望能找到技术手段来解决这个问题。在与BPF maintainer Alexei Starovoitov的一些邮件中,确实也出现了一些技术讨论,首先是Starovoitov不希望能关闭抢占,也坦率承认他完全不懂RT。Gleixner给他进行了一些讲解。
简单来说,realtime kernel不可以长时间关闭抢占(disable preemption)或者关闭中断。realtime patch会把spinlock和rwlock等替换成realtime-aware lock,这样避免在这些地方长时间关闭preemption或者interrupt。这类realtime-aware lock可以睡眠,因此他们不能用于那些已经被明确关闭了premption或者interrupt的上下文中。
Starovoitov解释了一下BPF为什么关闭premption,是因为“per-cpu maps和per-cpu数据结构会在bpf program的执行过程以及kernel执行过程中共享”。不过他说,BPF不会调用可能会导致sleep的代码,所以这里应该不会有问题。不过Gleixner指出这只是在非实时的环境下的BPF代码行为,因为在锁被替换之后,很多本来看起来不会sleep的代码其实也会有sleep行为了,因为新换来的realtime lock(例如sleeping spinlocks)会sleep。因此,在realtime context(实时环境)中使用preempt_disable()和local_irq_disable()等函数会导致问题。他说,realtime代码tree中的local_lock()机制可能可以用于BPF里面需要关闭preemption的地方。
但是,他也指出,BPF调用up_read_non_owner()也是不对的。这个行为会导致read-write semaphore(rwsem)被没拿到锁的进程给unlock了。这样就会违背realtime要求,locker和unlocker需要是同一个人才能正常处理优先级继承情况。
Starovoitov认为,BPF在preemption-disable的这部分并不会运行无限长的时间,因为BPF program里能执行的指令数目是有限的。不过最近刚把指令数从4096提升到一百万,这样在Gleixner看来就会导致preemption-disable的时间太长了:“假设instruction/cycle比例是1.0,并且CPU运行在2GHz,那算下来preempt disable的时间就是500us。对很多RT场景来说,这已经超出能容忍的市场的一个数量级了。”
哪怕此前的4096,也会导致2us时长的抢占关闭,对Clark Williams来说已经太长了:“有些客户的场景里面,2us已经是达到他们能容忍的最大latency了。”
local_lock()方案看起来对Starovoitov是可行的,但是他觉得Siewior的patch整体方案来说是一种倒退:“我读了一下你的其他回复,看起来我们现在讨论的逐步推进的方案没法接受?你们都还坚持要在RT环境下关掉bpf,仅仅只是为了合入一些out of tree代码?我感觉这种做法很粗暴,没法接受。如果希望合入RT代码,那么应该是在BPF打开的情况下把RT关掉,而不是反过来操作。”
而Gleixner并没有这个意思。他说会继续调查local lock是不是能解决问题,并且目前没有坚持要采用哪个方案。此外,肯定是可以在BPF打开的情况下关闭realtime的:“如果我没跟你讨论就直接就这么做了,那才算是偷偷摸摸不光彩的。”他也对讨论走到这个地步感到比较失望。
Starovoitov辩护说,他只是觉得不应该为了让realtime patch能容易合入,而关闭一个已经在kernel里正常工作的功能,这应该与RT的初衷是事与愿违的。他建议合入这部分的代码不应该惹恼其他子系统的开发者:“在我看来,应该把RT的核心内容(例如local_locks)合入,而不是先把这个有争议的patch合进去。这样的话,我们大家看到local_lock,就可以自己想清楚该如何利用它。”
在写本文的时候,讨论刚刚走到这个阶段。对用户来说,究竟是哪个子系统导致了其他子系统被关闭,这似乎没有什么区别。目前来说,BPF用户肯定没法用上realtime patch了,反之亦然。争论是谁关闭了谁,肯定不如解决兼容性问题来得重要。总会有一些用户希望用上这两者的。对于想运行mainline kernel的用户来说,在realtime patch正式upstream之前都是不可能两者都用起来的。等到合入之后,应该会开始修复上述的兼容性问题吧。
全文完
LWN文章遵循CC BY-SA 4.0许可协议。
长按下面二维码关注:Linux News搬运工,希望每周的深度文章以及开源社区的各种新近言论,能够让大家满意~