当前位置: 首页 > 科技观察

谈一谈Linux让实时-高性能任务独占CPU的事

时间:2023-03-19 13:26:52 科技观察

下面说说Linux是如何让实时/高性能任务独占CPU的隔离原理;以及如何在独占垄断的情况下实现最低的延迟抖动,即使系统的定时器滴答不打断独占任务。本文内容:1.工程需求2.用户态隔离3.内核态隔离3.1中断3.2内核线程4.最佳实践指南第1部分工程需求在SMP或NUMA系统中,CPU数量大于1。工程中,我们有时候会有让某个CPU独占CPU的需求。这个CPU什么都不做,只做指定的任务,从而获得低延迟和高实时性的好处。例如在DPDK中,通过设置GRUB_CMDLINE_LINUX_DEFAULT="isolcpus=0-3,5,7"隔离CPU0、3、5、7,这样当DPDK任务运行时,其他任务不会与DPDK任务进行上下文切换.从而保证最佳的网络性能[1]。在Realtime应用场景下,通过isolcpus=2隔离CPU2,然后通过taskset:taskset-c2pn_dev将实时应用绑定到隔离核上,保证低延迟需求[2]。Part2用户态隔离,我们可以看到他们都使用了一个启动参数,比如isolcpus。实践是检验真理的唯一标准。让我们启动一个8核ARM64系统,运行Ubuntu,并指定启动参数isolcpus=2:系统启动后,我们运行下面的简单程序(启动8个进程并运行while无限循环):我们有8个核,现在我们运行的是8个进程,所以理论上负载均衡后,8个进程应该均匀运行在8个核上,但是我们看一下实际的htop结果:我们发现3(也就是CPU2)上的CPU使用率)为0.0%。这确认CPU2已被隔离,并且没有用户空间进程可以在其上运行。当然这时候我们可以通过taskset将a.out中的一个强行绑定到CPU2上:从上面命令的结果可以看出,原来663的affinity列表只有0,1,3-7没有2可以,但是我们强行设置为2,再看htop,CPU2占用100%:通过上面的实验,我们可以清楚的看到isolcpus=2使得CPU2上无法运行用户空间进程(除非手动设置affinity).Part3内核态隔离中断但是CPU2上可以运行的任务不只是用户态任务,还有内核线程、中断等,那么isolcpus=是否可以隔离内核线程和中断呢?对于中断,我们特别好查,就是实际验证每个IRQ的smp_affinity:从上图可以清楚的看出,对于44、47等外设的中断,Linux内核将smp_affinity设置为FB(11111011),这显然是避开了CPU2,所以,实际的外设中断是不会在CPU2发生的,除非我们强行把中断绑定到内核上,比如把第44个中断绑定到CPU2上:echo2>/proc/irq/44/smp_affinity_list之后,我们发现第44个中断是CPU2可以发生的:但是,系统的定时器中断和IPI,因为它们是Linux系统运行的基石,实际上还是需要在CPU2上运行。其中,最容易给任务带来延迟抖动的自然是timertick。接下来,我们将重点关注蜱虫的问题。一般来说,Linux在IDLE状态下已经配置了NO_HZtickless,所以当CPU2没有运行时,真正的定时器中断几乎不会发生。接下来,我们还是在isolcpus=2的条件下运行前面8个进程的a.out。默认情况下,没有任务会占用CPU2。通过运行cat/proc/interrupts|head2几次,我们会看到其他核的定时器中断频繁发生,而CPU2几乎没有变化。这很明显是因为IDLE中的NO_HZ在起到省电的作用:但是,一旦我们把任务放到CPU2上,即使只放一个,你也会发现CPU2上的timerinterrupts开始变多了:这说明即使有只有一个线程在孤立的CPU上运行,定时器滴答将开始运行。当然,这个定时器滴答也会开始运行。这个线程会经常被打断,导致大量的上下文切换。你肯定会认为Linux是如此愚蠢。由于只有一个人,所以不需要时间片分片。无需按时间片划分调度两个或多个任务。为什么你必须运行滴答声?原因其实是我们的内核默认只开启了IDLE的NO_HZ:我们重新编译一个内核,开启NO_HZ_FULL:当我们开启NO_HZ_FULL时,Linux在CPU上只有一个任务的情况下是支持NO_HZ的。但是如果有2个,我就傻眼了,所以这个“FULL”并不是真的FULL[3]。这当然可以理解,因为其中有两个涉及到时间片调度的问题。什么时候应该开启NO_HZ_FULL,内核文档Documentation/timers/no_hz.rst有明确的“说明”,只有实时和HPC场景需要,否则默认的NO_HZ_IDLE是你最好的选择:我们重新编译了Kernel,选择NO_HZ_FULL,下面启动linux,注意启动时在参数中加上nohz_full=2,让CPU2支持NO_HZ_FULL:重新运行CPU2只有一个任务的场景,查看其定时器中断的发生:发现CPU2上的tick稳定在188以上,相信你会更开心,因为你的专属地更彻底!接下来,我们将另一个任务放入CPU2。当有2个任务时,CPU2上的timertick开始增加:不过,这可能不是问题,因为我们约定了“独占”,当一个task独占时,timertick不来打扰,应该已经是非常理想的情况了!内核态线程内核态线程其实和用户态类似,当它们没有被绑定到隔离CPU上时,就不会运行到隔离CPU上运行。接下来使用作者在内核中添加的dma_map_benchmark作为实验[4],开启16个内核线程用于DMAmap和unmap(注意我们只有8个核):./dma_map_benchmark-s120-t16我们看CPU在CPU2上占用也是0:内核中的dma_map_benchmark线程在占用CPU0-1、3-7,但不占用CPU2:但是如果内核线程用类似于kthread_bind_mask的API将线程绑定到一个孤立的CPU(),情况会有所不同,这类似于使用taskset将用户态任务绑定到CPU上。Part4最佳实践指南对于实时性要求高、高性能计算的场景,如果想让一个任务独占CPU,理想的选择是:1、使用isolcpus隔离CPU2。将指定任务绑定到隔离的CPU3。小心AccidentallybindinterruptsandkernelthreadstotheisolatedCPU,找出这些“意外”分子4.启用NO_HZ_FULL,效果更好,因为连timertickinterrupt都不会打扰你。参考文献[1]http://doc.dpdk.org/spp-18.02/setup/performance_opt.html[2]https://rt-labs.com/docs/p-net/linuxtiming.html[3]https://lwn.net/Articles/549580/[4]https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=65789daa80https://git。kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=7679325702本文转载自微信公众号“Linux代码阅读领域”,可通过以下二维码关注代码。转载本文请联系Linux代码阅读领域公众号。