本文主要是介绍Adding Processor Trace support to Linux,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
文章目录
- 前言
- 一、简介
- 二、Implementation
- 三、User interface
- 四、Limitations for non-root users
- 五、Snapshot mode
- 六、Debugging
- 七、Conclusion
前言
本篇文章来自于:https://lwn.net/Articles/648154/
July 1, 2015This article was contributed by Andi Kleen
一、简介
(1)
追踪(tracing)是一种用于性能分析和调试的技术。追踪器会生成日志数据,这些追踪事件可以后续分析,以理解程序的执行情况。Linux中有ftrace,可以记录内核的函数调用和跟踪点数据。CPU也支持不同类型的追踪机制。处理器追踪(Processor Trace,PT)是英特尔CPU的一种新的追踪机制,用于追踪CPU上执行的分支指令,从而可以重构所有执行代码的控制流程。
除了处理器追踪(PT),英特尔CPU还有一种较旧的机制称为分支追踪存储(Branch Trace Store,BTS),它也允许对分支进行追踪。然而,相比于处理器追踪(PT),BTS的性能较低并且没有广泛使用。
BTS是一种硬件追踪机制,可用于记录程序执行期间的分支指令信息。它通过将分支记录存储在一个特定的缓冲区中来实现。然而,由于BTS的实现方式和性能限制,它的使用相对较少,并且在一些特定情况下可能会引入较大的性能开销。
英特尔处理器追踪(PT)是从Broadwell代开始引入的。
传统上,硬件追踪机制主要用于硬件调试器。但也有可能将其集成到操作系统中,与其性能分析和调试机制进行紧密集成。这样可以为不同的进程使用不同的追踪缓冲区,并使该功能对非root用户可用。更重要的是,这也使得在不需要特殊调试硬件的情况下,在每个系统上进行追踪成为可能。
4.1内核在内核的perf_events子系统中实现了处理器跟踪,允许通过perf接口使用PT。用户工具支持尚未完全合并,但预计将在4.2中提供。该代码由Alex Shishkin和Adrian Hunter开发,并由Emmanuel Lucet测试。
这种集成使得用户可以通过perf工具接口利用处理器追踪功能,用于性能分析和调试。在内核的perf_events子系统中,可以利用PT来获得关于程序执行的详细信息。
// arch/x86/events/intel/pt.cstatic __init int pt_init(void)
{perf_pmu_register(&pt_pmu.pmu, "intel_pt", -1);
}
ls -l /sys/bus/event_source/devices/intel_pt/
除了英特尔处理器,其他架构如ARM和MIPS也具有分支追踪机制,但在过去,这些架构的操作系统驱动程序并不支持这些特性。然而,近年来,一些进展已经发生。
ARM正在合并一个单独的驱动程序,用于其CoreSight追踪技术。CoreSight是一种用于ARM处理器的调试和追踪解决方案。该驱动程序提供了对CoreSight追踪硬件的支持,使得在ARM架构上实现分支追踪成为可能。
然而,需要注意的是,ARM的CoreSight追踪驱动程序使用的是不同于perf接口的接口。这意味着在ARM架构上,分支追踪的实现可能与在x86架构上的实现有所不同。具体而言,它可能需要使用特定的工具和接口来访问和分析分支追踪数据。
备注:这篇文章发表于2015年七月,但Linux内核 3.19 主线(2015年二月)已经支持CoreSight驱动程序。
当时,通过Perf(AUX数据 – 一个内核态的 ring_buffer )将跟踪数据从内核传输到用户空间的基础设施并不存在,将跟踪数据的配置和获取留给了sysfs。
现在Perf(AUX数据)将跟踪数据从内核传输到用户空间的基础设施比较完善,将现有的驱动程序与Perf框架进行集成,可以使用 perf 接口 来使用CoreSight框架。
因此在比较高的内核版本:在内核的perf_events子系统(支持AUX Area)中实现了处理器跟踪:
x86_64:允许通过perf接口使用PT(LBR,BTS)。
arm64:允许通过perf接口使用CoreSight。
AUX Area – an auxiliary data :AUX缓冲区作为追踪数据的额外存储区域。为了建立内核空间和用户空间之间的连接,使用了mmap()系统调用来共享AUX缓冲区。通过将AUX缓冲区映射到内核和用户空间的地址空间中,它们可以访问和操作其内容。
(2)
以下是Intel PT与其他相关技术的比较:
Intel PT vs 性能计数器:性能计数器常用于性能分析和监控,提供系统各种事件的整体统计和采样。而Intel PT专注于捕获详细的执行追踪,包括分支和控制流变化,提供更精细的程序行为信息。
Intel PT vs 函数剖析:函数剖析记录函数的进入和退出点,用于分析程序的行为。通常需要重新编译程序以支持剖析。而Intel PT可以在不重新编译程序的情况下捕获调用链,可以追踪用户空间和内核空间,但生成的追踪数据量较大,分析起来可能具有挑战性。
Intel PT vs 最后分支记录(LBR):LBR是英特尔处理器提供的另一项功能,用于捕获分支信息,进行分支分析和剖析。LBR提供比Intel PT更精细的时间信息,可以过滤不同类型的分支。它还可以提供其他信息,如分支预测的“失误”。然而,Intel PT可以记录更长的分支追踪,捕获了更全面的程序执行视图。
Intel PT vs 分支追踪存储(BTS):BTS是一种较早的技术,用于捕获分支和控制流变化,包括中断和异常。然而,与Intel PT相比,BTS的开销较高,且不提供详细的时间信息。
二、Implementation
在现代以多核心和多GHz速度运行的CPU上进行分支追踪会产生大量的数据。虽然CPU可以在几乎没有开销的情况下进行实际的追踪 – 硬件可以并行完成,但是将数据保持在日志缓冲区的带宽可能是一个挑战。为了解决这个问题,PT(Processor Trace)使用了强大的压缩技术来使数据可管理:
(1)无条件的直接分支根本不被记录在追踪中。
(2)条件分支被压缩成一个单独的位,表示分支是被执行还是未执行。
(3)CALL/RET指令可以通过在解码器中维护一个调用栈来省略。
之后,一个特殊的PT解码器会读取压缩的追踪数据流。它会解码可执行文件中的指令。每当解码器遇到条件分支或间接分支时,它会参考追踪信息来确定分支的目标。此外,还会报告异步事件,例如中断或事务中止,以及时间和处理器频率。这些信息足以重构原始程序的完整控制流。性能开销从追踪阶段转移到了解码阶段。解码的速度比追踪慢几个数量级。
标准的perf PMU(性能监控单元)驱动程序会在内核中以标准化的格式生成"perf events",并通过环形缓冲区提供给用户空间中的"perf record"命令。然而,对于高容量的硬件追踪来说,直接使用这种方法是不可行的,因为在内核中运行的PT解码器无法跟上实时指令流。
相反,perf PT驱动程序直接将追踪数据转储到一个单独的环形缓冲区,并在稍后在用户空间中运行的解码器中生成perf_events,然后通过标准的perf工具链进行处理。解码器还需要附加信息来识别可执行文件,并将不同的进程和线程与正确的二进制文件关联起来。标准的perf抽样也依赖于用户空间的后处理来将样本与可执行文件关联起来,因此perf已经具备了所需的元数据信息事件。当在同一个CPU上追踪多个线程时,解码器还需要上下文切换信息,这也是可用的。PT解码同时使用每个逻辑CPU的追踪缓冲区和perf生成的附加信息流。
备注:
在驱动层PT生成压缩的追踪数据流。
在应用层使用PT解码器读取压缩的追踪数据流,解析追踪数据流。
三、User interface
合并PT代码是一个漫长而耗时的过程,大约历时两年。主要争议点是如何在perf_events API中表示额外的追踪缓冲区。经过多次迭代,最终版本使用了与主要perf环形缓冲区关联的辅助数据(AUX数据)缓冲区,通过mmap()在内核空间和用户空间之间进行共享。AUX缓冲区通过在原始perf环形缓冲区之上的一个区域进行mmap()来分配。然后,perf驱动程序配置追踪硬件将数据转储到该缓冲区中。当缓冲区填满时,perf_events内核代码会向主要的perf事件环形缓冲区发送一个AUX数据事件,该事件会通知"perf record"可用的数据量。
此外,正常的perf事件也用于收集解码所需的附加数据。AUX数据基础设施是通用的,可以用于其他类型的高容量追踪数据。
在记录完成后,压缩的数据会被解码为标准的perf事件。这是在"perf record"(或perf script/inject)时进行的。为了将附加数据与硬件解码流同步,使用时间戳。附加数据用于确定可执行文件和线程,解码器需要这些信息来重构分支流。追踪数据以时间戳计数器(TSC)格式报告事件,而perf事件默认使用Linux追踪时钟。内核报告必要的转换因子和偏移量。解码器在时间包之间插值计算分支事件的时间,并使用得到的估计时间戳与上下文切换和可执行文件加载的跟踪点事件同步。
为了遍历可执行文件并找到静态和条件分支,解码器使用了来自内核树的Masami Hiramatsu的x86指令解码器。
PT解码器然后从跟踪流中生成perf“sample”事件。它支持不同的采样选项,由新的–itrace选项控制:
--itrace={letter}periodi synthesize "instructions" eventsb synthesize "branches" eventsx synthesize "transactions" eventsc synthesize branches events (calls only)r synthesize branches events (returns only)e synthesize tracing error eventsd create a debug logg[size] synthesize a call chain (use with i or x)period can be NUMBER unit (e.g. 10us)
默认情况下,指令大约每100µs采样一次(使用默认–itrace=i100us选项)。
下面是一个记录简单程序执行情况的示例:
% perf record -e intel_pt//u ls[ perf record: Woken up 1 times to write data ][ perf record: Captured and wrote 0.190 MB perf.data ]
record完成后,需要对trace进行解码。perf report将运行解码器并生成样本的直方图。
% perf report --stdio...# Samples: 36 of event 'instructions:u'# Event count (approx.): 8613072## Overhead Command Shared Object Symbol # ........ ....... ................ ................................#27.78% ls libc-2.17.so [.] get_next_seq |---get_next_seq__strcoll_l...
跟踪器记录了8613072条指令,解码器每100µs对其进行采样。这是一个比具有perf记录的正常循环采样稍快的采样频率,可以提供更好的结果。
跟踪支持常用的perf过滤器,例如/u仅用于用户代码,/k用于内核代码。PMU还有更多可调谐项,可以在“/”分隔符之间选择性指定:
tsc=0 Disable time stampsnoretcomp Disable return compression
在上面的例子中,我们使用u只记录用户空间。使用正确的权限也可以记录内核空间,但需要以root身份运行解码器。
或者,我们也可以查看确切的跟踪,例如用于调试或查找用于性能分析的特定模式。这可以通过 perf script 来完成。由于默认的 perf script 输出非常详细,我们限制了字段,但也添加了源行(如果可用)。这跟踪ls命令的启动,该命令从执行动态链接器(ld-2.17.so)开始:
% perf script -F time,comm,cpu,sym,dso,ip,srclinels [001] 454279.326516: 0 [unknown] ([unknown])ls [001] 454279.326516: 7fdeb58b1630 _start (/lib/x86_64-linux-gnu/ld-2.17.so).:0ls [001] 454279.326527: 0 [unknown] ([unknown])ls [001] 454279.326527: 7fdeb58b1633 _start (/lib/x86_64-linux-gnu/ld-2.17.so).:0ls [001] 454279.326527: 7fdeb58b4fbf _dl_start (/lib/x86_64-linux-gnu/ld-2.17.so)get-dynamic-info.h:44ls [001] 454279.326532: 0 [unknown] ([unknown])ls [001] 454279.326532: 7fdeb58b4fc6 _dl_start (/lib/x86_64-linux-gnu/ld-2.17.so)rtld.c:385 ls [001] 454279.326539: 0 [unknown] ([unknown])ls [001] 454279.326539: 7fdeb58b4fe1 _dl_start (/lib/x86_64-linux-gnu/ld-2.17.so)rtld.c:414ls [001] 454279.326546: 0 [unknown] ([unknown])ls [001] 454279.326546: 7fdeb58b500e _dl_start (/lib/x86_64-linux-gnu/ld-2.17.so)get-dynamic-info.h:52ls [001] 454279.326546: 7fdeb58b502f _dl_start (/lib/x86_64-linux-gnu/ld-2.17.so)get-dynamic-info.h:46ls [001] 454279.326546: 7fdeb58b502f _dl_start (/lib/x86_64-linux-gnu/ld-2.17.so)get-dynamic-info.h:46ls [001] 454279.326546: 7fdeb58b502f _dl_start (/lib/x86_64-linux-gnu/ld-2.17.so)get-dynamic-info.h:46
默认情况下,perf script 输出分支而不是指令。
该格式是标准的perf格式,因此处理示例的现有脚本可以工作。例如,我们可以使用PT数据生成火焰图。
perf script也支持运行Perl或Python脚本。我们可以使用它们来直接分析追踪数据。这一点非常重要,因为指令追踪通常过长,无法由人来阅读。此外,还可以使用其他能够处理perf输出的工具来查看信息。
perf还具备将追踪数据存储到数据库的能力。perf附带的export-to-postgresql.py脚本将分支信息存储到PostgreSQL数据库中,以便后续使用标准数据库工具进行分析。数据库将调用和返回配对,这样可以确定每个函数调用的生命周期和调用栈。
通过利用perf script与Perl或Python脚本,可以执行复杂的分析和对追踪数据进行自定义处理。这些脚本语言提供了灵活性和可扩展性,可以提取特定信息、进行计算,并基于收集的追踪数据生成定制报告。
四、Limitations for non-root users
Perf支持以非root用户身份进行记录。但是有一些限制。当在单个CPU上跟踪多个线程时,perf使用相同的追踪缓冲区。为了在以后重构追踪,需要一个上下文切换的附加事件来找到正确的可执行文件。PT解码器使用sched::sched_switch跟踪点来实现这一点。传统上,跟踪点需要root权限(除非通过kernel.perf_event_paranoid=-1禁用perf安全性)。如果跟踪点不可用,新创建的线程无法解码。这意味着只能跟踪由perf启动的单线程程序,或者使用–per-thread --uid/–pid/–tid选择的已存在线程。在将来,一个非root的上下文切换事件(如此补丁中提出的)将解决这个问题。
另一个问题是内核代码是自修改的。为了处理内核代码,解码器必须通过/proc/kcore查看修补后的"live"映像,而这仅对root用户可用。新的perf-with-kcore.sh包装脚本允许将kcore保存为perf会话的一部分,从而使解码器能够看到与跟踪期间相同的内核代码状态。
五、Snapshot mode
以上的记录示例尝试将程序的完整追踪存储到磁盘中。存储完整追踪通常仅适用于计算有限的程序。对于执行较长时间、具有许多分支并进行大量计算的工作负载来说,磁盘I/O带宽最终无法跟上CPU的速度,因此可能会丢失数据。
另一种选择是将追踪作为环形缓冲区仅存储在内存中,并且只在发生"感兴趣的事件"时停止并保存追踪数据。内存通常具有足够的带宽来跟上大多数情况下的追踪数据。
这种做法在调试时尤其有用:我们正在寻找错误发生之前的追踪数据,以了解其发生的过程。它还在性能分析中非常有用。例如,在服务器和用户界面优化中,常见问题是需要分析偶尔导致用户可见延迟的延迟峰值。
为了处理这种情况,perf引入了新的"snapshot"模式。该模式下,追踪数据以环形缓冲区的方式持续记录在内存中,并将perf元数据保存到磁盘中。
当发生感兴趣的事件时,我们向perf record进程发送一个SIGUSR2信号,然后该进程将内存中的追踪缓冲区转储到perf.data文件中。
因此,我们需要一个程序,在发生有趣的事件时发送信号。例如,我们可以使用一个简单的REST键值存储,从数据库中检索值。假设数据库偶尔出现延迟故障,导致事务执行时间过长,我们希望了解其中的原因。因此,我们在程序中添加代码,在数据库操作耗时过长时发送信号给perf。然后,我们可以使用perf script稍后查看追踪数据,以找出导致延迟峰值的原因。
为了避免必须以root用户身份运行测试程序,我们禁用了perf安全性,并以与测试相同的用户ID运行perf记录。否则,测试程序无法向perf发送信号。
% sudo sysctl -w kernel.perf_event_paranoid=-1
启动跟踪并在后台运行示例服务器(simple kv.py):
% perf record --snapshot -a -e intel_pt//u sleep 100 &% export PERFPID=$!% python simple-kv.py &% curl http://localhost:4000/foo# perf script -F time,comm,cpu,sym,dso,ip,srcline... tracing output ...
六、Debugging
在使用追踪进行调试时,显而易见的关注点是崩溃或中止。通过追踪信息,我们可以实现有限的向后调试。原始的PT补丁套件具有内置的核心转储支持。通过新的rlimit,可以在程序中启用环形缓冲区模式的追踪,并且追踪数据将自动写入核心转储文件。在4.1中合并的PT代码尚未具备此功能,但希望它最终会被重新添加进来。
这就引发了关于如何处理核心转储的问题。原始的PT补丁套件API得到了GDB的支持,GDB既可以读取PT核心转储,还可以进行控制流的实时向后调试。调试器允许您在追踪中进行虚拟移动,并允许在过去的不同时间点上检查程序状态(如果有的话)。GDB以前使用记录和回放以及早期的分支追踪存储(Branch Trace Store)来实现这一点,但速度非常慢。PT将是一个更快速、更功能丰富的替代方案。
目前,由Markus Metzger正在开发支持当前auxtrace内核API的GDB版本。对于解码PT,GDB使用libipt PT解码器,它提供了一个C API用于解码,也可用于其他PT工具。
七、Conclusion
硬件支持的分支追踪是一种强大的调试和性能分析技术。它可以生成大量的数据,以新的方式分析程序。事实上,挑战将不再是收集性能或调试问题的数据,而是在现在可以收集的所有信息中找到正确的信息。从4.2内核开始,perf和其他工具将提供一系列强大的功能来生成分支追踪。它还将提供基本的数据显示功能。有趣的是,我们可以看到在此基础上开发出什么样的新的可视化和分析工具,以更好地利用这个丰富的数据源。
这篇关于Adding Processor Trace support to Linux的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!