Linux上内核和用户空间跟踪器的调查与分析:设计,实现和开销外文翻译资料
2021-12-13 22:45:39
英语原文共 33 页
Linux上内核和用户空间跟踪器的调查与分析:设计,实现和开销
随着应用程序和操作系统变得越来越复杂,过去十年中,许多跟踪工具都在软件堆栈中崛起。 本文介绍了Linux系统上现代跟踪器的实际比较,包括用户空间和内核空间。 作者实现了微基准测试,不仅可以量化不同跟踪器的开销,还可以采用细粒度指标,揭示对跟踪器内部结构的见解,并显示每个跟踪器开销的原因。 讨论了内部设计选择和实施特性,这有助于我们了解开发跟踪器的挑战。 此外,该分析旨在帮助用户根据他们的具体要求选择和配置他们的跟踪器,以减少他们的开销并充分利用他们。
1 引言
事实证明,跟踪是一种强大而有效的调试手段和对复杂系统进行逆向工程的方法。在过去的十年里,许多跟踪器在软件堆栈的层次,甚至在硬件层面(英特尔公司2016年; Sharma和Dagenais 2016a)都在崛起。某些应用程序(如Google Chrome)甚至可以提供在产品本身内部集成的本地跟踪器。从根本上说,追踪是一种复杂的日志形式,它是一个提供了实现高效和有效的框架的可配置的日志记录的称为跟踪器的软件组件。开发人员最常用的日志记录是通过printf()完成的功能(或等效的),虽然这种方法效率低且有限。跟踪器提供更灵活,更强大的方法,可以随着时间的推移轻松维护,通常增加很少管理费用。跟踪在用户应用程序中很常见,也广泛用于Linux内核,它提供了多个跟踪基础架构。使用复杂的在线分布式系统,跟踪成为调试问题的有效方法。虽然被低估但这个问题依旧存在,尤其是现代并行异构系统的复杂性不断增加,对高效和低影响跟踪器的需求正在增加。 Linux内核控制超过1,000个跟踪点以及可在运行时生成的事件量增强时需要低影响的跟踪器。在本文中,我们将重点放在不同跟踪器的开销上,在Linux系统上添加到用户和内核级别的跟踪应用程序。我们开始建立本文中使用的命名法,并对已经使用的许多聚集在术语“跟踪器”下的工具进行分类。我们从用户角度解释它们之间的差异。我们总结了每个人执行任务所使用的机制,并展示了关键设计和相关的每个实施决策。我们提出了一个旨在实现的微基准为深入分析和比较提供可靠的低级和细粒度指标。
在本文中,我们将重点介绍多个跟踪器的性能和空间占用,以及他们的基础设施。 许多商业和广为人知的工具都依赖于跟踪这里研究的基础设施变体,因此适用直接测量的开销。本文深入探讨了Linux上现代跟踪器的设计原则。本文通过测量细粒度和低水平来解决比较跟踪器的问题性能指标,以便由跟踪器开发人员做出设计选择,以及在评估影响时,会考虑实施和编码决策跟踪系统上的跟踪工具。 此外,该方案还包括一种用于低级基准测试的方法,即解开跟踪器的实际行为,而不是使用平台和微架构仿真器。
2 相关工作
Bitzes和Nowak(2014)研究使用性能计数器进行采样的开销。然而,他们的研究一般不涉及追踪。他们也只关注收集方法 - 硬件计数器的数据和性能开销,而不是覆盖内部跟踪器或设计和实现选择。 Sivakumar和Sundar Rajan(2010)mea-确保了LTTng跟踪器在用户和内核空间中的影响。作者运行了多次已知的一般基准并报告了跟踪器添加的开销。这种方法有帮助估计LTTng可能对特定工作负载的影响,但没有详细量化仪器的成本或开销的原因。 Mihajlović等(2014)讨论他们的通过修改ARM ISA仿真来在虚拟环境中启用硬件跟踪并显示他们的方法所增加的开销。虽然它提出的方法很有趣,他们的方法依赖于动态跟踪,这是一种特定的跟踪方法。而且,它不包括基准跟踪器的基本工作。此外,没有与其他跟踪器一起呈现的详细的比较。摩尔等人(2001)审查了MPI的性能分析工具应用。虽然它们涵盖了本文的两个跟踪器,但工作主要集中在MPI框架,并没有解决内核空间跟踪问题。这项工作的目标也有所不同,不包括具有不同范围的跟踪器的基本设计选择。在Ghods(2016),作者解释并分析了Perf工具的内部结构,主要用于采样性能硬件柜台。这项工作不包括与其他跟踪工具的比较。Desnoyers(2009)的工作报告了LTTng和其他跟踪器的基准测试结果,尽管只是显示记录事件的延迟,不与其他跟踪器进行详细比较。
本文中介绍的基础架构和框架通常是其他组合的基础:mercial和更广为人知的监控和性能工具。 例如,Gregg(2017)的工作广泛使用Perf和eBPF。 火焰图通常由剖析产生使用Perf的应用程序,即使它是分析器而不是跟踪器。 另一个例子是SysDig,它使用跟踪点基础结构是从内核中提取信息的。
3定义和命名
3.1 定义
本节提醒人们追踪世界中一些必不可少的常用术语来了解文章的其余部分。
跟踪点: tracepoint是直接放在提供的应用程序代码中的语句,是一个用来调用探针的钩子。跟踪点通常提供了一种可以启用它的方法以及动态禁用。
探测器:探测器是一个挂钩到跟踪点的函数,只要在运行时遇到(如果启用)跟踪点就会被调用。探针通常执行自定义任务,并且是由追踪者或用户补充。通常,探针需要尽可能小和快,尽可能少地添加开销并减少对系统的扰动。
事件:事件标记在运行时遇到跟踪点,视情况而定跟踪,事件可以具有逻辑意义,例如上下文切换,或者可以简单地表示一个代码中的位置,例如函数入口或出口。事件是准时的,没有持续时间,并且通常使用时间戳进行注释。
有效载荷:事件通常包含有效负载,这是与事件相关的附加信息。例如,上下文切换的有效载荷可以包含所涉及的两个任务的标识符。
环形缓冲区:用作事件占位符的数据结构。记录事件时,在运行时,将调用跟踪器的探测器。探针通过放置它来记录遇到的事件在环形缓冲区(生产者)的内存中。以后,消费者可以阅读环缓冲区内容并将其报告给用户。当数据结构已满时,传入的事件可能会
要么取代最古老的事件(以环状方式),要么可以丢弃它们直到某些事件已经从缓冲区消耗掉了。
原子操作:原子操作具有不可分割的特征,这意味着中间值或中间状态对并发操作是不可见的。原子操作通常需要硬件或操作系统的支持,并且非常小心必须由开发人员保证原子性。例如,在x86架构上,一个mov,除非其操作数是高速缓存对齐的,否则不保证指令是原子的。考虑这个案子:其中一个操作数存储在两个页面中:mov指令需要访问不同的页面(并可能导致虚拟地址转换),使操作是可分割和非原子的,如果访问操作数,则可以看到中间不稳定值,即这些步骤之间的另一条指令。
3.2 命名法
我们之前将跟踪点定义为应用程序中可以挂接探针的位置。本节首先介绍用于探测回调的不同机制,以及他们的实施。机制是关于回调如何可以以已知理论方法实现,但实际的实现留给跟踪基础设施。例如,atrampoline是一种允许在运行时进行检测的机制,但实际的实现留给了一个跟踪基础设施,如DynInst或Kprobes。同样,一个工具可以支持多种机制并允许其用户配置要使用的机制,取决于他们的需求。然后可以在这些技术之上构建跟踪器以利用它们的呼叫 - 支持机制,从而将这一关键部分外包出去。可以构建跟踪器以支持多个呼叫 - 支持机制,提供更好的灵活性和功能。总之,跟踪器可以使用一个或许多回调实现,它们反过来实现一个或多个机制。例如,LTTng可以使用TRACE_EVENTKprobes,Kprobes可以使用陷阱或蹦床。
我们将跟踪器定义为实现以下模式的工具:回调,序列化,写入。 一方面跟踪器的输出是一个跟踪,并致力于尽可能地减少它冗余。另一方面,eBPF和SystemTap等工具从根本上遵循不同的方式模式:回调,计算,更新。我们将它们称为聚合器,因为它们的工作经常是在应用程序的关键路径上实时或以实时方式收集和聚合指标,与痕量分析的性质相反。为此,他们为用户提供脚本功能和高级数据结构(如hashmaps)来实现聚合方法在某些事件中被执行。与跟踪器相反,聚合器的输出结果就是用户定义的探测器,通常是度量的集合,在运行时超过阈值时警报,依此类推。他们经常忽略时间方面而不是隐含地执行每个回调读取一个时钟。
4 回调机制
4.1 功能仪表
(略)
4.2 静态跟踪点
(略)
4.3 陷阱
基于陷阱的检测是一种在运行时动态检测应用程序的机制。它依赖于操作系统陷阱的支持,它利用它来插入和执行自定义几乎可以探测内核或应用程序代码中的任何位置。在Linux内核中,这个机制由Kprobe基础设施实施,该基础设施使用基于陷阱的方法动态地挂钩到内核代码中(Hiramatsu等人2016; Mavinakayanahalli等人2006)。什么时候在给定指令处加载并注册Kprobe,该指令被复制和替换使用断点指令(x86上的int3)。稍后执行断点指令时通过CPU,调用内核的断点处理程序。它保存了应用程序的状态(注册,堆栈等)并使用Linux通知程序控制Kprobe基础结构调用链,最终调用跟踪探测器。一旦此过程完成并且陷阱已经完成处理后,最终执行复制的指令(由断点替换)并且控制在呼叫站点正常继续。 Kprobes也提供对预处理程序和后处理程序探测以及函数返回检测的支持(Kretprobe),本研究未涉及。
可以在Kprobes上构建跟踪基础结构,而不是手动插入trace_tracepoint_name()语句在调用站点的代码中,可以在Kprobe上注册运行时所需的位置。 Tracers利用这种方法将探针连接到Kprobe而不是TRACE_EVENT宏。以这种方式,回调机制被抽象化,并且只将跟踪器的探针连接到不同的后端,为用户提供了更大的灵活性。该结果跟踪与使用TRACE_EVENT宏生成的跟踪相同,但用于调用探测器的回调机制是不同的,这会对性能产生影响。当卸载Kprobe时,断点指令将替换为原始指令,从而恢复完全。
内核中的Ptrace基础结构(Haardt和Coleman 1999; Padala 2002)也使用了陷阱为用户空间应用程序提供一种挂钩进程的机制。重要的是要注意到,与其名称相反,Ptrace本身并不是一种跟踪器,而是一种由Linux内核提供的基础设施,用于监视其他进程的进程。它允许一个进程“挂钩”进入另一个并中断其执行,检查其内部数据,访问其寄存器等等上。许多调试器使用Ptrace作为后端,包括GDB。
4.4 蹦床
Trampolines是一种基于跳跃的方法,用于动态修补或检测应用程序运行。它们为基于陷阱的机制提供了一个更复杂的实现且较低的开销替代。在更新的版本中,Linux内核尝试进行优化注册Kprobes使用基于跳跃的蹦床而不是昂贵的断点。(Hiramatsu等人,2016年)的核心优化是使用“绕行”缓冲区(称为优化区域)来模拟断点方法。而不是使用断点指令修补指令,触发陷阱时,它会被简单的跳转到优化区域。以跳跃为主方法首先将CPU的寄存器推入堆栈,跳转到一个行动的蹦床作为中间体,反过来跳转到用户定义的探测器(Hiramatsu 2010)。当它完成执行,过程颠倒过来:代码跳出优化区域,即从堆栈恢复寄存器,并继续执行原始路径。注意并非所有加载的Kprobes都使用蹦床方法,因为它需要满足一系列条件(例如,目标位置处的指令的长度)。如果不是,内核就会回归到先前描述的基于断点的方法。
5 跟踪器
为每个跟踪器提供设计和实现,并在以后与之关联结果。
本文不评估Dtrace(Gregg和Mauro 2011)和Ktap(Github 2017c)。该前者是Solaris上经过验证的跟踪器,被认为是跟踪领域的先驱之一。但是,它似乎没有被积极开发,总共有13个邮件列表2017年上半年在dtrace.org上发布;它的Linux端口从未达到稳定或广泛Solaris端口的使用,其优势在于其灵活性和易用性,而不是其优化的性能和可扩展性(Brosseau 2017)。 Ktap是一个有趣的轻量级基于字节码的动态跟踪工具实验,但很快就被eBPF取代了并提供类似的功能,与其他内核子系统共享核心基础结构。
5.1 内核跟踪器
启用跟踪时,回调机制会调用Ftrace的探测器,该探测器将事件存储在其中环形缓冲区。一旦环形缓冲区满了可以将Ftrace配置为覆盖最早的事件或丢弃传入事件。有趣的是,跟踪保留在内存中而不是刷新到磁盘,只有在读取跟踪内存支持的内容时才可用file(可以手动将跟踪文件的内容转储到磁盘)。它也有可能通过trace_pipe文件消耗写入时的环形缓冲区。
默认情况下,Ftrace使用本地时钟为其记录的事件添加时间戳。本地时钟是一个CPU本地的时钟源,因此读取速度更快,但不提供任何保证单调性和与其他CPU时钟同步的术语。但是,这是可能的配置Ftrace使用其他时钟源,例如全局时钟(系统范围内),a逻辑计数器,甚至是体系结构特定的时钟,例如x86上的TSC(时间戳计数器)。
Ftrace将事件的大小(包括其有效负载)限制为页面的大小。它使用每CPU缓冲区,
这样可以避免在多个内核上进行跟踪时需要同步缓冲区。 Ftrace seg-将其环形缓冲区转换为页面并单独操作它们。环形缓冲区本身是一个链接页面列表(Rostedt 2016b)和内部参考资料用于簿记。例如,tail_page是对应写入下一个事件的页面的引用,以及commit_page是对最后完成写入的页面的引用。虽然不能同时进行作者到同一页面(每个CPU缓冲区),一个作者仍然可以被另一个作者打断中断和非屏蔽中断(NMI)。
通过遵循这个方案,写入事务看起来是原子的,在某种意义上说没有两个嵌套写入可以写入缓冲区中的同一个槽。当嵌套写入发生时,一些细微之处需要实施。例如,嵌套写入在写入之前无法提交占用。在此之前,它处于“待定提交”状态。这是必需的,因为之前的所有事件必须提交commit_page(commit_page实际指向最新的事件已提交,并屏蔽其最低
资料编号:[5381]