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”的线程上下文,而 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()
,它们依赖于上述内存屏障来确保数据依赖性。
一些架构支持具有 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_head
和 aux_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