Perf 环形缓冲区

1. 简介

环形缓冲区是一种基本的数据传输机制。 perf 使用环形缓冲区将事件数据从内核传输到用户空间,另一种环形缓冲区,即所谓的辅助 (AUX) 环形缓冲区,在 Intel 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 工具中第一次从用户空间访问缓冲区的页面时,会发生页面错误的data abort异常,内核会利用这个机会将页面映射到进程 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(),它们依赖于上述内存屏障来确保数据依赖性。

一些架构支持具有 load-acquire 和 store-release 操作的单向渗透屏障,这些屏障更宽松,性能损失更小,因此 (C) 和 (D) 可以分别优化为使用屏障 smp_load_acquire()smp_store_release()

如果某个架构在其内存模型中不支持 load-acquire 和 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 环形缓冲区是常规环形缓冲区的辅助。 常规环形缓冲区主要用于存储事件样本,并且每个事件格式都符合 union perf_event 中的定义; AUX 环形缓冲区用于记录硬件跟踪数据,并且跟踪数据格式与硬件 IP 相关。

AUX 环形缓冲区的通用用法和优点是它由硬件直接写入,而不是由内核写入。 例如,写入常规环形缓冲区的常规分析样本会导致中断。 跟踪执行需要大量的样本,并且使用中断对于常规环形缓冲区机制来说是压倒性的。 拥有一个 AUX 缓冲区允许一个与内核更分离的内存区域,并由硬件跟踪直接写入。

AUX 环形缓冲区重用与常规环形缓冲区相同的算法进行缓冲区管理。 控制结构 perf_event_mmap_page 扩展了新字段 aux_headaux_tail,分别用于 AUX 环形缓冲区的头指针和尾指针。

在初始化阶段,除了 mmap()-ed 常规环形缓冲区之外,perf 工具在 auxtrace_mmap__mmap() 函数中调用第二个系统调用,用于 mmap 具有非零文件偏移量的 AUX 缓冲区; 内核中的 rb_alloc_aux() 相应地分配页面,这些页面将延迟映射到 VMA 中,在处理页面错误时,这与常规环形缓冲区的惰性机制相同。

AUX 事件和 AUX 跟踪数据是两个不同的东西。 让我们看一个例子

perf record -a -e cycles -e cs_etm// -- 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 驱动程序管理,因此屏障 (B)(一个写入屏障,用于确保在更新头指针之前,跟踪数据在外部可见)被要求在 PMU 驱动程序中实现。

然后 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//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