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