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” 的线程上下文,而 T2T3 是系统中不相关的线程。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_headaux_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