Perf 环形缓冲区¶
1. 简介¶
环形缓冲区是数据传输的基本机制。 perf 使用环形缓冲区将事件数据从内核传输到用户空间,另一种称为辅助 (AUX) 环形缓冲区的环形缓冲区在英特尔 PT、Arm CoreSight 等硬件跟踪中也发挥着重要作用。
环形缓冲区的实现至关重要,但也是一项非常具有挑战性的工作。一方面,内核和用户空间中的 perf 工具使用环形缓冲区交换数据并将数据存储到数据文件中,因此环形缓冲区需要以高吞吐量传输数据;另一方面,环形缓冲区管理应避免对分析结果造成明显的超负荷。
本文档深入探讨了 perf 环形缓冲区的细节,分为两部分:首先解释 perf 环形缓冲区的实现,然后第二部分讨论 AUX 环形缓冲区机制。
2. 环形缓冲区实现¶
2.1 基本算法¶
也就是说,典型的环形缓冲区由头指针和尾指针管理;头指针由写入器操作,尾指针分别由读取器更新。
+---------------------------+
| | |***|***|***| | |
+---------------------------+
`-> Tail `-> Head
* : the data is filled by the writer.
Figure 1. Ring buffer
Perf 使用相同的方式管理其环形缓冲区。在实现中,有两个关键的数据结构保存在一组连续的页面中,即控制结构和环形缓冲区本身。包含控制结构的页面称为“用户页面”。保存在连续的虚拟地址中简化了环形缓冲区地址的定位,它位于用户页面之后的页面中。
控制结构被命名为 perf_event_mmap_page
,它包含一个头指针 data_head
和一个尾指针 data_tail
。当内核开始将记录填充到环形缓冲区时,它会更新头指针以保留内存,以便稍后可以安全地将事件存储到缓冲区中。另一方面,当用户页面是可写映射时,perf 工具有权在从环形缓冲区使用数据后更新尾指针。另一种情况是用户页面的只读映射,将在 2.3.3 将采样写入缓冲区 部分中讨论。
user page ring buffer
+---------+---------+ +---------------------------------------+
|data_head|data_tail|...| | |***|***|***|***|***| | | |
+---------+---------+ +---------------------------------------+
` `----------------^ ^
`----------------------------------------------|
* : the data is filled by the writer.
Figure 2. Perf ring buffer
当使用 perf record
工具时,我们可以使用选项 -m
或 --mmap-pages=
指定环形缓冲区大小,给定的大小将被向上舍入为 2 的幂,它是页面大小的倍数。尽管内核一次分配所有内存页面,但它会延迟将页面映射到 VMA 区域,直到 perf 工具从用户空间访问缓冲区。换句话说,当 perf 工具首次从用户空间访问缓冲区的页面时,会发生页面错误的数据中止异常,内核会利用这个机会将页面映射到进程 VMA(请参阅 perf_mmap_fault()
),因此 perf 工具可以在从异常返回后继续访问该页面。
2.2 不同跟踪模式下的环形缓冲区¶
perf 以不同的模式分析程序:默认模式、按线程模式、按 CPU 模式和系统范围模式。本节描述这些模式以及环形缓冲区如何满足它们的要求。最后,我们将回顾这些模式引起的竞争条件。
2.2.1 默认模式¶
通常,我们执行 perf record
命令,后跟一个分析程序名称,如下所示
perf record test_program
此命令不指定 CPU 和线程模式的任何选项,perf 工具在 perf 事件上应用默认模式。它将系统中的所有 CPU 和分析程序的 PID 映射到 perf 事件,并在事件上启用继承模式,以便子任务继承事件。因此,perf 事件被归因于
evsel::cpus::map[] = { 0 .. _SC_NPROCESSORS_ONLN-1 }
evsel::threads::map[] = { pid }
evsel::attr::inherit = 1
这些属性最终将反映在环形缓冲区的部署上。如下所示,perf 工具为每个 CPU 分配单独的环形缓冲区,但它仅为分析程序启用事件,而不是为系统中的所有线程启用事件。T1 线程表示 “test_program” 的线程上下文,而 T2 和 T3 是系统中不相关的线程。perf 采样专门为 T1 线程收集,并存储在与 T1 线程正在运行的 CPU 关联的环形缓冲区中。
T1 T2 T1
+----+ +-----------+ +----+
CPU0 |xxxx| |xxxxxxxxxxx| |xxxx|
+----+--------------+-----------+----------+----+-------->
| |
v v
+-----------------------------------------------------+
| Ring buffer 0 |
+-----------------------------------------------------+
T1
+-----+
CPU1 |xxxxx|
-----+-----+--------------------------------------------->
|
v
+-----------------------------------------------------+
| Ring buffer 1 |
+-----------------------------------------------------+
T1 T3
+----+ +-------+
CPU2 |xxxx| |xxxxxxx|
--------------------------+----+--------+-------+-------->
|
v
+-----------------------------------------------------+
| Ring buffer 2 |
+-----------------------------------------------------+
T1
+--------------+
CPU3 |xxxxxxxxxxxxxx|
-----------+--------------+------------------------------>
|
v
+-----------------------------------------------------+
| Ring buffer 3 |
+-----------------------------------------------------+
T1: Thread 1; T2: Thread 2; T3: Thread 3
x: Thread is in running state
Figure 3. Ring buffer for default mode
2.2.2 按线程模式¶
通过在 perf 命令中指定选项 --per-thread
,例如
perf record --per-thread test_program
perf 事件不映射到任何 CPU,并且仅绑定到分析的进程,因此,perf 事件的属性为
evsel::cpus::map[0] = { -1 }
evsel::threads::map[] = { pid }
evsel::attr::inherit = 0
在此模式下,为分析的线程分配单个环形缓冲区;如果该线程被调度到 CPU 上,则将启用该 CPU 上的事件;如果该线程从 CPU 上调度出去,则将禁用该 CPU 上的事件。当线程从一个 CPU 迁移到另一个 CPU 时,事件将在先前的 CPU 上被禁用,并在下一个 CPU 上相应地被启用。
T1 T2 T1
+----+ +-----------+ +----+
CPU0 |xxxx| |xxxxxxxxxxx| |xxxx|
+----+--------------+-----------+----------+----+-------->
| |
| T1 |
| +-----+ |
CPU1 | |xxxxx| |
--|--+-----+----------------------------------|---------->
| | |
| | T1 T3 |
| | +----+ +---+ |
CPU2 | | |xxxx| |xxx| |
--|-----|-----------------+----+--------+---+-|---------->
| | | |
| | T1 | |
| | +--------------+ | |
CPU3 | | |xxxxxxxxxxxxxx| | |
--|-----|--+--------------+-|-----------------|---------->
| | | | |
v v v v v
+-----------------------------------------------------+
| Ring buffer |
+-----------------------------------------------------+
T1: Thread 1
x: Thread is in running state
Figure 4. Ring buffer for per-thread mode
当 perf 在按线程模式下运行时,将为分析的线程 T1 分配一个环形缓冲区。环形缓冲区专用于线程 T1,如果线程 T1 正在运行,perf 事件将记录到环形缓冲区中;当线程处于睡眠状态时,所有相关事件将被禁用,因此不会将任何跟踪数据记录到环形缓冲区中。
2.2.3 按 CPU 模式¶
选项 -C
用于收集 CPU 列表上的采样,例如,以下 perf 命令接收选项 -C 0,2
perf record -C 0,2 test_program
它将 perf 事件映射到 CPU 0 和 2,并且该事件与任何 PID 都不关联。因此,perf 事件属性设置为
evsel::cpus::map[0] = { 0, 2 }
evsel::threads::map[] = { -1 }
evsel::attr::inherit = 0
这导致 perf record
会话将对 CPU0 和 CPU2 上的所有线程进行采样,并且在 test_program 退出之前终止。即使在 CPU1 和 CPU3 上运行了任务,由于它们没有环形缓冲区,因此将忽略这两个 CPU 上的任何活动。一个使用案例是将按线程模式和按 CPU 模式的选项组合起来,例如,一起指定选项 –C 0,2
和 ––per–thread
,仅当分析的线程被调度到任何列出的 CPU 上时,才会记录采样。
T1 T2 T1
+----+ +-----------+ +----+
CPU0 |xxxx| |xxxxxxxxxxx| |xxxx|
+----+--------------+-----------+----------+----+-------->
| | |
v v v
+-----------------------------------------------------+
| Ring buffer 0 |
+-----------------------------------------------------+
T1
+-----+
CPU1 |xxxxx|
-----+-----+--------------------------------------------->
T1 T3
+----+ +-------+
CPU2 |xxxx| |xxxxxxx|
--------------------------+----+--------+-------+-------->
| |
v v
+-----------------------------------------------------+
| Ring buffer 1 |
+-----------------------------------------------------+
T1
+--------------+
CPU3 |xxxxxxxxxxxxxx|
-----------+--------------+------------------------------>
T1: Thread 1; T2: Thread 2; T3: Thread 3
x: Thread is in running state
Figure 5. Ring buffer for per-CPU mode
2.2.4 系统范围模式¶
通过使用选项 –a
或 ––all–cpus
,perf 会收集所有 CPU 上所有任务的采样,我们称之为系统范围模式,命令为
perf record -a test_program
与按 CPU 模式类似,perf 事件不绑定到任何 PID,并且映射到系统中的所有 CPU
evsel::cpus::map[] = { 0 .. _SC_NPROCESSORS_ONLN-1 }
evsel::threads::map[] = { -1 }
evsel::attr::inherit = 0
在系统范围模式下,每个 CPU 都有自己的环形缓冲区,所有线程在运行状态期间都受到监视,并且采样会记录到事件发生的 CPU 所属的环形缓冲区中。
T1 T2 T1
+----+ +-----------+ +----+
CPU0 |xxxx| |xxxxxxxxxxx| |xxxx|
+----+--------------+-----------+----------+----+-------->
| | |
v v v
+-----------------------------------------------------+
| Ring buffer 0 |
+-----------------------------------------------------+
T1
+-----+
CPU1 |xxxxx|
-----+-----+--------------------------------------------->
|
v
+-----------------------------------------------------+
| Ring buffer 1 |
+-----------------------------------------------------+
T1 T3
+----+ +-------+
CPU2 |xxxx| |xxxxxxx|
--------------------------+----+--------+-------+-------->
| |
v v
+-----------------------------------------------------+
| Ring buffer 2 |
+-----------------------------------------------------+
T1
+--------------+
CPU3 |xxxxxxxxxxxxxx|
-----------+--------------+------------------------------>
|
v
+-----------------------------------------------------+
| Ring buffer 3 |
+-----------------------------------------------------+
T1: Thread 1; T2: Thread 2; T3: Thread 3
x: Thread is in running state
Figure 6. Ring buffer for system wide mode
2.3 访问缓冲区¶
基于对环形缓冲区如何在各种模式下分配的理解,本节将解释如何访问环形缓冲区。
2.3.1 生产者-消费者模型¶
在 Linux 内核中,PMU 事件可以生成存储在环形缓冲区中的采样;用户空间中的 perf 命令通过从环形缓冲区读取数据来使用采样,并最终将数据保存到文件中以进行后期分析。这是使用环形缓冲区的典型生产者-消费者模型。
perf 进程轮询 PMU 事件,并在没有传入事件时休眠。为了防止内核和用户空间之间的频繁交换,内核事件核心层引入了水印,该水印存储在 perf_buffer::watermark
中。当采样被记录到环形缓冲区中,并且如果已使用的缓冲区超过水印,则内核会唤醒 perf 进程以从环形缓冲区读取采样。
Perf
/ | Read samples
Polling / `--------------| Ring buffer
v v ;---------------------v
+----------------+ +---------+---------+ +-------------------+
|Event wait queue| |data_head|data_tail| |***|***| | |***|
+----------------+ +---------+---------+ +-------------------+
^ ^ `------------------------^
| Wake up tasks | Store samples
+-----------------------------+
| Kernel event core layer |
+-----------------------------+
* : the data is filled by the writer.
Figure 7. Writing and reading the ring buffer
当内核事件核心层通知用户空间时,由于多个事件可能共享同一个环形缓冲区来记录采样,因此核心层会迭代与环形缓冲区关联的每个事件,并唤醒正在等待该事件的任务。这是由内核函数 ring_buffer_wakeup()
完成的。
在 perf 进程被唤醒后,它开始逐个检查环形缓冲区,如果发现任何包含采样的环形缓冲区,它将读取采样以进行统计或保存到数据文件中。鉴于 perf 进程能够在任何 CPU 上运行,这会导致环形缓冲区可能同时从多个 CPU 访问,从而导致竞争条件。竞争条件处理在 2.3.5 内存同步 部分中描述。
2.3.2 环形缓冲区的属性¶
Linux 内核支持环形缓冲区的两个写入方向:正向和反向。正向写入从环形缓冲区的开头保存采样,反向写入从环形缓冲区的末尾以相反的方向存储数据。perf 工具确定写入方向。
此外,该工具可以将缓冲区以读写模式或只读模式映射到用户空间。
读写模式下的环形缓冲区映射具有 PROT_READ | PROT_WRITE
属性。凭借写入权限,perf 工具会更新 data_tail
以指示数据起始位置。结合用作当前数据结束位置的头指针 data_head
,perf 工具可以轻松知道从哪里读取数据。
或者,在只读模式下,只有内核会更新 data_head
,而用户空间由于映射属性 PROT_READ
而无法访问 data_tail
。
因此,下面的矩阵说明了方向和映射特征的各种组合。perf 工具使用其中两种组合来支持缓冲区类型:非覆盖缓冲区和可覆盖缓冲区。
映射模式 |
向前 |
向后 |
---|---|---|
读写 |
非覆盖环形缓冲区 |
未使用 |
只读 |
未使用 |
可覆盖环形缓冲区 |
非覆盖环形缓冲区使用具有向前写入的读写映射。它从环形缓冲区的开头开始保存数据,并在溢出时环绕,这在普通环形缓冲区中与读写模式一起使用。当消费者跟不上生产者时,会丢失一些数据,内核会记录丢失了多少条记录,并在下次在环形缓冲区中找到空间时生成 PERF_RECORD_LOST
记录。
可覆盖环形缓冲区使用具有只读模式的向后写入。它从环形缓冲区的末尾开始保存数据,data_head
保存当前数据的位置,perf 始终知道从哪里开始读取以及直到环形缓冲区的末尾,因此它不需要 data_tail
。在这种模式下,它不会生成 PERF_RECORD_LOST
记录。
2.3.3 将样本写入缓冲区¶
当采集一个样本并将其保存到环形缓冲区时,内核会根据样本类型准备样本字段;然后它会准备写入环形缓冲区的信息,这些信息存储在结构 perf_output_handle
中。最后,内核将样本输出到环形缓冲区,并更新用户页面中的头指针,以便 perf 工具可以看到最新值。
结构 perf_output_handle
用作跟踪与缓冲区相关信息的临时上下文。它的优点在于,它允许不同的事件并发写入缓冲区。例如,如果同时启用软件事件和硬件 PMU 事件进行分析,则 perf_output_handle
的两个实例分别用作软件事件和硬件事件的独立上下文。这允许每个事件保留自己的内存空间来填充记录数据。
2.3.4 从缓冲区读取样本¶
在用户空间中,perf 工具利用 perf_event_mmap_page
结构来处理缓冲区的头部和尾部。它还使用 perf_mmap
结构来跟踪环形缓冲区的上下文,此上下文包括有关缓冲区的起始和结束地址的信息。此外,即使发生溢出,也可以利用掩码值来计算循环缓冲区指针。
与内核类似,用户空间中的 perf 工具首先从环形缓冲区中读取记录的数据,然后更新缓冲区的尾部指针 perf_event_mmap_page::data_tail
。
2.3.5 内存同步¶
具有宽松内存模型的现代 CPU 不能保证内存排序,这意味着有可能无序访问环形缓冲区和 perf_event_mmap_page
结构。为了确保访问 perf 环形缓冲区的特定顺序,使用内存屏障来确保数据依赖性。内存同步的基本原理如下
Kernel User space
if (LOAD ->data_tail) { LOAD ->data_head
(A) smp_rmb() (C)
STORE $data LOAD $data
smp_wmb() (B) smp_mb() (D)
STORE ->data_head STORE ->data_tail
}
tools/include/linux/ring_buffer.h 中的注释对为什么以及如何使用内存屏障进行了很好的描述,这里我们将只提供另一种解释
(A) 是一个控制依赖,因此 CPU 确保检查指针 perf_event_mmap_page::data_tail
和将样本填充到环形缓冲区之间的顺序;
(D) 与 (A) 配对。(D) 将环形缓冲区数据读取与写入指针 data_tail
分开,perf 工具先使用样本,然后告诉内核数据块已释放。由于读取操作后跟写入操作,因此 (D) 是一个完整的内存屏障。
(B) 是两个写入操作之间的写入屏障,它确保记录样本必须先于更新头指针。
(C) 与 (B) 配对。(C) 是读取内存屏障,以确保在读取样本之前获取头指针。
为了实现上述算法,引入了内核中的 perf_output_put_handle()
函数和用户空间中的两个辅助函数 ring_buffer_read_head()
和 ring_buffer_write_tail()
,它们依赖于如上所述的内存屏障来确保数据依赖性。
某些架构支持具有加载获取和存储释放操作的单向渗透屏障,这些屏障更宽松,性能损失更小,因此 (C) 和 (D) 可以优化为分别使用屏障 smp_load_acquire()
和 smp_store_release()
。
如果架构在其内存模型中不支持加载获取和存储释放,它将回退到旧式的内存屏障操作。在这种情况下,smp_load_acquire()
封装了 READ_ONCE()
+ smp_mb()
,由于 smp_mb()
的成本很高,因此 ring_buffer_read_head()
不会调用 smp_load_acquire()
,而是使用屏障 READ_ONCE()
+ smp_rmb()
。
3. AUX 环形缓冲区的机制¶
在本章中,我们将解释 AUX 环形缓冲区的实现。第一部分将讨论 AUX 环形缓冲区和常规环形缓冲区之间的联系,然后第二部分将检查 AUX 环形缓冲区如何与常规环形缓冲区协同工作,以及 AUX 环形缓冲区为采样机制引入的附加功能。
3.1 AUX 和常规环形缓冲区之间的关系¶
通常,AUX 环形缓冲区是常规环形缓冲区的辅助缓冲区。常规环形缓冲区主要用于存储事件样本,并且每个事件格式都符合联合 perf_event
中的定义;AUX 环形缓冲区用于记录硬件跟踪数据,并且跟踪数据格式与硬件 IP 相关。
AUX 环形缓冲区的通用用途和优点是,它是由硬件直接写入而不是由内核写入的。例如,写入常规环形缓冲区的常规配置文件样本会导致中断。跟踪执行需要大量样本,并且使用中断对于常规环形缓冲区机制来说是难以承受的。使用 AUX 缓冲区可以使内存区域与内核更加分离,并由硬件跟踪直接写入。
AUX 环形缓冲区重用与常规环形缓冲区相同的算法进行缓冲区管理。控制结构 perf_event_mmap_page
扩展了新字段 aux_head
和 aux_tail
,用于 AUX 环形缓冲区的头部和尾部指针。
在初始化阶段,除了 mmap() 的常规环形缓冲区外,perf 工具还在 auxtrace_mmap__mmap()
函数中为 AUX 缓冲区的 mmap 调用第二个系统调用,文件偏移量为非零;内核中的 rb_alloc_aux()
会相应地分配页面,当处理页面错误时,这些页面将被延迟映射到 VMA,这与常规环形缓冲区的懒惰机制相同。
AUX 事件和 AUX 跟踪数据是两个不同的东西。让我们看一个例子
perf record -a -e cycles -e cs_etm/@tmc_etr0/ -- sleep 2
上面的命令启用了两个事件:一个是来自 PMU 的事件 *cycles*,另一个是来自 Arm CoreSight 的 AUX 事件 *cs_etm*,它们都保存在常规环形缓冲区中,而 CoreSight 的 AUX 跟踪数据存储在 AUX 环形缓冲区中。
因此,我们可以看到常规环形缓冲区和 AUX 环形缓冲区是成对分配的。在默认模式下,perf 工具会按 CPU 为单位分配常规环形缓冲区和 AUX 环形缓冲区,这与系统全局模式相同。然而,默认模式仅记录被分析程序的样本,而后者则分析系统中所有程序的样本。对于每个线程模式,perf 工具仅为整个会话分配一个常规环形缓冲区和一个 AUX 环形缓冲区。对于每个 CPU 模式,perf 会为由选项 -C
指定的选定 CPU 分配两种环形缓冲区。
下图展示了系统全局模式下的缓冲区布局;如果在某个 CPU 上有任何活动,AUX 事件样本和硬件跟踪数据将被记录到该 CPU 的专用缓冲区中。
T1 T2 T1
+----+ +-----------+ +----+
CPU0 |xxxx| |xxxxxxxxxxx| |xxxx|
+----+--------------+-----------+----------+----+-------->
| | |
v v v
+-----------------------------------------------------+
| Ring buffer 0 |
+-----------------------------------------------------+
| | |
v v v
+-----------------------------------------------------+
| AUX Ring buffer 0 |
+-----------------------------------------------------+
T1
+-----+
CPU1 |xxxxx|
-----+-----+--------------------------------------------->
|
v
+-----------------------------------------------------+
| Ring buffer 1 |
+-----------------------------------------------------+
|
v
+-----------------------------------------------------+
| AUX Ring buffer 1 |
+-----------------------------------------------------+
T1 T3
+----+ +-------+
CPU2 |xxxx| |xxxxxxx|
--------------------------+----+--------+-------+-------->
| |
v v
+-----------------------------------------------------+
| Ring buffer 2 |
+-----------------------------------------------------+
| |
v v
+-----------------------------------------------------+
| AUX Ring buffer 2 |
+-----------------------------------------------------+
T1
+--------------+
CPU3 |xxxxxxxxxxxxxx|
-----------+--------------+------------------------------>
|
v
+-----------------------------------------------------+
| Ring buffer 3 |
+-----------------------------------------------------+
|
v
+-----------------------------------------------------+
| AUX Ring buffer 3 |
+-----------------------------------------------------+
T1: Thread 1; T2: Thread 2; T3: Thread 3
x: Thread is in running state
Figure 8. AUX ring buffer for system wide mode
3.2 AUX 事件¶
类似于 perf_output_begin()
和 perf_output_end()
对常规环形缓冲区的作用,perf_aux_output_begin()
和 perf_aux_output_end()
用于处理硬件跟踪数据的 AUX 环形缓冲区。
一旦硬件跟踪数据存储到 AUX 环形缓冲区中,PMU 驱动程序将通过调用 pmu::stop()
回调来停止硬件跟踪。与常规环形缓冲区类似,AUX 环形缓冲区需要应用 2.3.5 内存同步 部分中讨论的内存同步机制。由于 AUX 环形缓冲区由 PMU 驱动程序管理,因此要求在 PMU 驱动程序中实现屏障 (B),它是一个写入屏障,用于确保跟踪数据在更新头指针之前对外部可见。
然后 pmu::stop()
可以安全地调用 perf_aux_output_end()
函数来完成两件事:
它在常规环形缓冲区中填充一个事件
PERF_RECORD_AUX
,此事件传递的信息是已存储到 AUX 环形缓冲区中的一块硬件跟踪数据的起始地址和数据大小;由于硬件跟踪驱动程序已将新的跟踪数据存储到 AUX 环形缓冲区中,参数 *size* 指示硬件跟踪使用了多少字节,因此
perf_aux_output_end()
更新头指针perf_buffer::aux_head
以反映最新的缓冲区使用情况。
最后,PMU 驱动程序将重新启动硬件跟踪。在此临时暂停期间,它将丢失硬件跟踪数据,这将在解码阶段引入不连续性。
事件 PERF_RECORD_AUX
表示在内核中处理的 AUX 事件,但它缺少在 perf 文件中保存 AUX 跟踪数据的信息。当 perf 工具将跟踪数据从 AUX 环形缓冲区复制到 perf 数据文件时,它会合成一个 PERF_RECORD_AUXTRACE
事件,它不是内核 ABI,而是由 perf 工具定义的,用于描述 AUX 环形缓冲区中保存了哪些部分数据。之后,perf 工具根据 PERF_RECORD_AUXTRACE
事件从 perf 文件中读取 AUX 跟踪数据,并且 PERF_RECORD_AUX
事件用于通过与时间顺序相关联来解码一块数据。
3.3 快照模式¶
Perf 支持 AUX 环形缓冲区的快照模式。在此模式下,用户仅在用户感兴趣的特定时间点记录 AUX 跟踪数据。例如,下面给出了一个使用 Arm CoreSight 以 1 秒间隔拍摄快照的示例:
perf record -e cs_etm/@tmc_etr0/u -S -a program &
PERFPID=$!
while true; do
kill -USR2 $PERFPID
sleep 1
done
快照模式的主要流程是:
在拍摄快照之前,AUX 环形缓冲区以自由运行模式运行。在自由运行模式下,perf 不会记录任何 AUX 事件和跟踪数据;
一旦 perf 工具收到 *USR2* 信号,它将触发回调函数
auxtrace_record::snapshot_start()
以停用硬件跟踪。然后,内核驱动程序将硬件跟踪数据填充到 AUX 环形缓冲区中,并将事件PERF_RECORD_AUX
存储在常规环形缓冲区中;然后,perf 工具拍摄快照,
record__read_auxtrace_snapshot()
从 AUX 环形缓冲区读取硬件跟踪数据并将其保存到 perf 数据文件中;快照完成后,
auxtrace_record::snapshot_finish()
重新启动用于 AUX 跟踪的 PMU 事件。
perf 仅在快照模式下访问头指针 perf_event_mmap_page::aux_head
,而不触及尾指针 aux_tail
,这是因为 AUX 环形缓冲区在自由运行模式下可能会溢出,在这种情况下尾指针是无用的。或者,引入回调 auxtrace_record::find_snapshot()
用于决定 AUX 环形缓冲区是否已回绕,最后,它会修复用于计算跟踪数据大小的 AUX 缓冲区的头指针。
如我们所知,缓冲区的部署可以是每个线程模式、每个 CPU 模式或系统全局模式,并且快照可以应用于这些模式中的任何一种。下面是在系统全局模式下拍摄快照的示例。
Snapshot is taken
|
v
+------------------------+
| AUX Ring buffer 0 | <- aux_head
+------------------------+
v
+--------------------------------+
| AUX Ring buffer 1 | <- aux_head
+--------------------------------+
v
+--------------------------------------------+
| AUX Ring buffer 2 | <- aux_head
+--------------------------------------------+
v
+---------------------------------------+
| AUX Ring buffer 3 | <- aux_head
+---------------------------------------+
Figure 9. Snapshot with system wide mode