Linux内核是一个被广泛使用的操作系统。从嵌入式家电、航空航天设备到超级计算机,Linux内核无处不在。这得益于Linux内核丰富的配置。极大的灵活性。网络虚拟化和软件定义网络的发展也从另一个方面证实了在网络设备这样一个专用领域,Linux内核同样可以发挥巨大的作用,它给可编程领域带来了极大的方便。网络设备,极大地促进了5G网络堆栈建立在这种范式之上。随着自动驾驶、物联网等实时系统的发展,对时延的要求越来越高。高性能计算(HPC)、实时任务和软件定义网络等需求要求Linux执行对延迟敏感的任务。为实现这些目标,必须针对高性能计算和实时性能配置硬件和软件。硬件需要在吞吐量和延迟确定性之间做出权衡,包括调整处理器的频率、省电模式和系统管理中断。对于内核配置,有必要在系统中隔离一些管家CPU。这些管家任务包括内核线程,如RCU回调线程,内核中的一些延迟任务线程,以及内核和用户态的一些看门狗线程。一些中断也被放置在管家CPU上。因此,在软件定义的网络系统中,特定的隔离CPU用于执行特定的网络功能虚拟化(NFV)。尽管需要确定性的任务被路由到这些特定的隔离CPU,但仍有一些CPU由调度程序分配来执行通用任务。这些CPU不需要高延迟确定性,也不是孤立的。为了提高这类需要延迟判定的系统的实时性,内核往往需要配置PREEMPT_RT来减少唤醒延迟。这些延迟敏感系统的评估是一项非常复杂的任务,评估这些系统的延迟变化是一项非常艰巨但重要的任务。这在高性能计算领域被称为系统噪声,在实时操作系统领域被称为实时。不管叫什么,这件事情的本质是一样的,某个工作是如何受到系统中各种复杂的硬件和软件组件的干扰,导致一个非常复杂的时延分布。如何评估内核的噪声?大致有两种方法:设置适当的负载和基于跟踪的方法。前者是基于任务的宏观测试方法,测试各个任务的时间分布,后者是微观方法,检测同一任务在执行过程中时间分布的根本原因。这两种方法具有不同的观测尺度,各有优缺点。加载法可以给出具体任务的时间分布特征,可以分析出影响该任务时间特征的宏观因素。基于跟踪的微观方法可以分析出每个软件或硬件进程的时间延迟,但很难还原出整个系统中这些微观延迟的来源。因此,在很多情况下,需要结合基于负载的方法和基于跟踪的方法来接近真相。In-kernelosnoise结合了基于负载和基于跟踪的方法来评估和归因于内核中的噪声。该工具专为高性能计算而开发,可追踪隔离CPU系统中的细微噪声。通过对内核的实时基础设施、调度和跟踪子系统的一系列修改,sosnoise终于在内核的5.14版本正式进入Linux内核。在内核5.17中,可以通过rtla(实时Linux分析)工具集在用户模式下使用此功能,内核开发人员和系统管理员使用这些工具集,他们可以使用这些工具轻松测试系统的噪音,或扩展这些工具的功能工具。要了解osnoise的作用,我们首先要了解噪音的来源。我们介绍了几种在Linux中可以称为任务的上下文。一般程序有四种执行上下文:不可屏蔽中断(NMI)、可屏蔽中断(IRQ)、软中断和线程。当PREEMPT_RT打开时,软中断被线程化。我们不需要区分这些不同的执行流程或上下文,我们可以将它们统称为任务。这些类型的任务在Linux中遵循以下规则:每个CPU的NMI窃取IRQ、软中断和线程。它将被另一个IRQ拦截。Softirqs可以拦截线程。Softirqs不能被另一个软中断拦截。线程无法拦截NMI、IRQ和sofirq。让我们介绍一下Linux调度器。Linux有5个分层调度器来调度所有线程。不管这些线程是内核线程还是用户态线程,调度器都一视同仁,没有区别。五个调度程序以固定顺序运行以选择下一个要运行的线程。这些调度器的执行顺序是:第一个调度器是停机调度器,用于在多CPU系统中实现负载均衡、热插拔等内核功能。第二个调度器是SCHED_DEADLINE,这是一个基于EarliestDeadlineFirst的截止时间实时调度器。第三个是与POSIX兼容的固定优先级实时调度程序。使用此调度程序的线程可以是SCHED_RR或SCHED_FIFO类型的线程。SCHED_RR是一个时间片旋转线程。SCHED_FIFO线程只有被挂起,执行结束,或者被抢占时才会释放CPU占用率。第四个调度器是通用调度器,即CFS调度器,这个调度器调度的线程标记为SCHED_OTHER。第五个调度器是IDLE调度器。当前面四个调度器都没有线程调度到的时候,就调度到空闲线程thread。Linux有一套丰富的跟踪功能。可以跟踪许多核函数。这些trace函数的大量使用并没有带来太大的性能损失,反而为那些关心内核运行情况的人提供了一个很好的窗口来观察内核是如何运行的。ftrace、ebpf和systemtap是这些跟踪系统的杰出代表。一个HPC程序说明了这个问题。普通的HPC应用程序是相同的代码(单程序多数据(SPMD)模型),其中一个程序在多个数据副本上运行。如图所示,上面是一个HPC程序运行过程。A、B、C运行相同的A段代码,它们计算完后,将数据发送给D进行下一次计算。普通计算只有蓝色的本地计算,橙色的线程间同步,绿色的线程间通信。这是每个CPU上的线程实际完成的工作。但是,系统中总是有各种因素会中断在CPU上运行的程序。因此,在上面蓝色部分计算的中间,总是引入红色部分。这些用户任务的运行总是被操作系统的一些系统任务打断。这些系统任务可能是NMI、IRQ或软中断,或其他用户线程或内核线程。系统引入的与程序执行无关的时间延迟的不确定性是噪声。这些噪音使得三个相同的程序A、B、C经历了不同的时间。对于HPC来说,带来了很多延迟和性能惩罚,对于实时性强的系统,可能带来的不仅仅是性能惩罚,而是灾难。从上面的讨论可以看出,不同的调度策略对本地CPU某个任务的执行延迟影响很大,严重影响各个并行任务的响应时间。这些由内核的系统活动引起的延迟实际上是操作系统噪音。全世界的超级计算机都使用Linux作为内核。原因之一是Linux可以配置为将系统活动(例如NMI、IRQ或软中断)和内核线程限制在少数几个内核中,而其他大部分内核用于运行真正的计算任务,这些计算的内核任务可以被隔离和绑定以避免系统噪声和其他用户程序产生的噪声的干扰,从而使它们的延迟更具确定性。在软件定义网络的实践中,已经开发了DPDK这样的通用框架来进行类似的噪声隔离工作,在HPC和其他实时系统中也有类似的框架。虽然可以隔离CPU并将所有IRQ、软中断和内核线程移动到少数CPU内核,但仔细配置对延迟特别敏感的任务是一项非常具有挑战性的任务。因为每个CPU仍然有很多软件活动会干扰程序的运行,例如调度程序使用的时钟中断、虚拟内存统计和网络数据包处理。这些噪声源可以通过仔细的配置来去除,比如在内核配置中开启NOHZ_FULL来去除时钟中断的干扰,或者修改内核代码或算法来去除相应的噪声干扰。Linux是一个通用操作系统,它不是为延迟敏感的应用程序而生的,但是HPC,实时操作系统开发者想要使用Linux来实现他们苛刻的目标,是不可能跟踪Linux对其修改的delay干扰的影响。大家习惯于使用一些实时的或者对延迟敏感的HPC负载来衡量Linux内核噪音。两个有用的工具是sysjitter和oslat。这些工具反复测量同一任务的延迟差异,并比较它们的延迟差异是否超过某个阈值。这些工具使用计算任务来测量和发现噪声的存在,而不是找出其原因。为了发现这些噪音的原因,我们需要内核的跟踪系统来观察整个内核系统的运行情况。当然,跟踪系统也会引入噪声,这就需要通过统计找到噪声的根源,抓住主要矛盾。硬件本身的噪音并不少见。这可能是由于共享硬件资源、启用超线程以及优先级高于操作系统的上下文,例如系统管理中断。这些不是操作系统本身引入的问题,但是可以检测到。跟踪系统看到。在一些基于任务的测试中可以看到的噪音很难被跟踪系统重现。这些都是比较麻烦的问题,需要根据两人的数据进行持续长时间的深入分析,才能将时间流逝的地方仔细还原。总的来说,结合基于任务的测试和基于跟踪的分析的最佳工具是osnoise,它很好地结合了这两点并分析了内核时间延迟的变化。
