user_events: 基於使用者的事件追蹤

作者:

Beau Belgrave

概述

基於使用者的跟蹤事件允許使用者程序建立事件和跟蹤資料,這些資料可以透過現有的工具(如 ftrace 和 perf)檢視。要啟用此功能,請使用 CONFIG_USER_EVENTS=y 構建您的核心。

程式可以透過 /sys/kernel/tracing/user_events_status 檢視事件的狀態,並且可以透過 /sys/kernel/tracing/user_events_data 註冊事件和寫入資料。

程式還可以使用 /sys/kernel/tracing/dynamic_events 透過 u: 字首註冊和刪除基於使用者的事件。dynamic_events 的命令格式與應用了 u: 字首的 ioctl 相同。這需要 CAP_PERFMON 許可權才能持久化事件,否則會返回 -EPERM。

通常,程式會註冊一組它們希望暴露給可以讀取 trace_events 的工具(如 ftrace 和 perf)的事件。註冊過程會告訴核心哪個地址和位反映事件是否已被任何工具啟用以及是否應該寫入資料。註冊將返回一個寫入索引,該索引描述了在 /sys/kernel/tracing/user_events_data 檔案上呼叫 write() 或 writev() 時的資料。

本文件中引用的結構包含在源樹的 /include/uapi/linux/user_events.h 檔案中。

注意: user_events_status 和 user_events_data 都位於 tracefs 檔案系統下,並且可能以與上述不同的路徑掛載。

註冊

使用者程序中的註冊透過 ioctl() 傳送到 /sys/kernel/tracing/user_events_data 檔案完成。要執行的命令是 DIAG_IOCSREG。

此命令將打包的 struct user_reg 作為引數

struct user_reg {
      /* Input: Size of the user_reg structure being used */
      __u32 size;

      /* Input: Bit in enable address to use */
      __u8 enable_bit;

      /* Input: Enable size in bytes at address */
      __u8 enable_size;

      /* Input: Flags to use, if any */
      __u16 flags;

      /* Input: Address to update when enabled */
      __u64 enable_addr;

      /* Input: Pointer to string with event name, description and flags */
      __u64 name_args;

      /* Output: Index of the event to use when writing data */
      __u32 write_index;
} __attribute__((__packed__));

struct user_reg 需要設定所有上述輸入。

  • size:必須設定為 sizeof(struct user_reg)。

  • enable_bit:該位反映由 enable_addr 指定的地址上的事件狀態。

  • enable_size:由 enable_addr 指定的值的大小。必須為 4 (32 位) 或 8 (64 位)。64 位值只能在 64 位核心上使用,但 32 位可以在所有核心上使用。

  • flags:要使用的標誌(如果有)。呼叫者應首先嚐試使用標誌,然後在沒有標誌的情況下重試,以確保支援較低版本的核心。如果不支援標誌,則返回 -EINVAL。

  • enable_addr:用於反映事件狀態的值的地址。此地址必須自然對齊並且在使用者程式中可寫。

  • name_args:描述事件的名稱和引數,請參閱命令格式以獲取詳細資訊。

目前支援以下標誌。

  • USER_EVENT_REG_PERSIST:事件不會在最後一個引用關閉時刪除。如果事件即使在程序關閉或取消註冊事件後也應該存在,則呼叫者可以使用此標誌。需要 CAP_PERFMON 許可權,否則返回 -EPERM。

  • USER_EVENT_REG_MULTI_FORMAT:事件可以包含多種格式。這允許程式在它們的事件格式更改並且希望使用相同的名稱時,防止自身被阻塞。當使用此標誌時,跟蹤點名稱將採用 “name.unique_id” 的新格式,而不是 “name” 的舊格式。將為每個唯一的名稱和格式對建立一個跟蹤點。這意味著如果多個程序使用相同的名稱和格式,它們將使用相同的跟蹤點。如果另一個程序使用相同的名稱,但與另一個程序的格式不同,它將使用具有新唯一 id 的不同跟蹤點。記錄程式需要掃描 tracefs 以查詢它們感興趣的事件名稱的各種不同格式。跟蹤點的系統名稱也將使用 “user_events_multi” 而不是 “user_events”。這可以防止單格式事件名稱與 tracefs 中的任何多格式事件名稱衝突。unique_id 輸出為十六進位制字串。記錄程式應確保跟蹤點名稱以它們註冊的事件名稱開頭,並具有以 . 開頭且僅具有十六進位制字元的字尾。例如,要查詢事件 “test” 的所有版本,您可以使用正則表示式 “^test.[0-9a-fA-F]+$”。

成功註冊後,將設定以下內容。

  • write_index:寫入資料時,用於表示此事件的此檔案描述符的索引。該索引對於用於註冊的檔案描述符的此例項是唯一的。有關詳細資訊,請參見寫入資料。

基於使用者的事件在 tracefs 下以 “user_events” 命名的子系統下的任何其他事件一樣顯示。這意味著希望附加到事件的工具需要使用 /sys/kernel/tracing/events/user_events/[name]/enable 或 perf record -e user_events:[name] 來附加/記錄。

注意: 事件子系統的名稱預設情況下為 “user_events”。呼叫者不應假設它將始終是 “user_events”。操作員保留將來更改每個程序的子系統名稱以適應事件隔離的權利。此外,如果使用 USER_EVENT_REG_MULTI_FORMAT 標誌,則跟蹤點名稱將附加一個唯一 id,並且系統名稱將為 “user_events_multi”,如上所述。

命令格式

命令字串格式如下

name[:FLAG1[,FLAG2...]] [Field1[;Field2...]]

支援的標誌

暫無

欄位格式

type name [size]

支援基本型別(__data_loc、u32、u64、int、char、char[20] 等)。鼓勵使用者程式使用明確大小的型別,如 u32。

注意: 不支援 Long,因為大小在使用者和核心之間可能有所不同。

大小僅對於以 struct 字首開頭的型別有效。這允許使用者程式在需要時向工具描述自定義結構。

例如,C 中的一個結構如下所示

struct mytype {
  char data[20];
};

將由以下欄位表示

struct mytype myname 20

刪除

從使用者程序中刪除事件透過 ioctl() 傳送到 /sys/kernel/tracing/user_events_data 檔案完成。要執行的命令是 DIAG_IOCSDEL。

此命令僅需要一個字串,用於按名稱指定要刪除的事件。只有當沒有對事件的剩餘引用(在使用者和核心空間中)時,刪除才會成功。由於這一點,使用者程式應該使用單獨的檔案來請求刪除,而不是用於註冊的檔案。

注意: 預設情況下,當沒有對事件的剩餘引用時,事件將自動刪除。如果程式不希望自動刪除,則必須在註冊事件時使用 USER_EVENT_REG_PERSIST 標誌。一旦使用了該標誌,事件就會存在,直到呼叫 DIAG_IOCSDEL。持久化事件的註冊和刪除都需要 CAP_PERFMON 許可權,否則返回 -EPERM。當同一事件名稱有多種格式時,將嘗試刪除所有具有相同名稱的事件。如果只想刪除特定版本,則應使用 /sys/kernel/tracing/dynamic_events 檔案來刪除該特定格式的事件。

取消註冊

如果在註冊事件後不再希望更新事件,則可以透過 ioctl() 傳送到 /sys/kernel/tracing/user_events_data 檔案來停用它。要執行的命令是 DIAG_IOCSUNREG。這與刪除不同,刪除實際上是從系統中刪除事件。取消註冊只是告訴核心您的程序不再對事件的更新感興趣。

此命令將打包的 struct user_unreg 作為引數

struct user_unreg {
      /* Input: Size of the user_unreg structure being used */
      __u32 size;

      /* Input: Bit to unregister */
      __u8 disable_bit;

      /* Input: Reserved, set to 0 */
      __u8 __reserved;

      /* Input: Reserved, set to 0 */
      __u16 __reserved2;

      /* Input: Address to unregister */
      __u64 disable_addr;
} __attribute__((__packed__));

struct user_unreg 需要設定所有上述輸入。

  • size:必須設定為 sizeof(struct user_unreg)。

  • disable_bit:必須設定為要停用的位(與先前透過 enable_bit 註冊的位相同)。

  • disable_addr:必須設定為要停用的地址(與先前透過 enable_addr 註冊的地址相同)。

注意: 呼叫 execve() 時,事件會自動取消註冊。在 fork() 期間,註冊的事件將被保留,如果需要,必須在每個程序中手動取消註冊。

狀態

當工具附加/記錄基於使用者的事件時,事件的狀態會即時更新。這允許使用者程式僅在某些內容主動附加到事件時才產生 write() 或 writev() 呼叫的成本。

當工具附加/分離事件時,核心將更新為該事件註冊的指定位。使用者程式只需檢查是否設定了該位即可檢視是否附加了某些內容。

管理員可以透過終端直接讀取 user_events_status 檔案來輕鬆檢查所有註冊事件的狀態。輸出如下

Name [# Comments]
...

Active: ActiveCount
Busy: BusyCount

例如,在只有一個事件的系統上,輸出如下所示

test

Active: 1
Busy: 0

如果使用者透過 ftrace 啟用使用者事件,則輸出將更改為如下所示

test # Used by ftrace

Active: 1
Busy: 1

寫入資料

註冊事件後,可以使用用於註冊的相同 fd 為該事件寫入條目。返回的 write_index 必須位於資料的開頭,然後剩餘的資料被視為事件的有效負載。

例如,如果返回的 write_index 是 1,並且我想為事件寫入一個 int 有效負載。那麼資料必須是 8 個位元組(2 個 int)大小,前 4 個位元組等於 1,後 4 個位元組等於我想要作為有效負載的值。

在記憶體中,它看起來像這樣

int index;
int payload;

使用者程式可能具有它們希望用作有效負載的眾所周知的結構。在這些情況下,可以使用 writev(),第一個向量是索引,後面的向量是實際的事件有效負載。

例如,如果我有一個像這樣的結構

struct payload {
      int src;
      int dst;
      int flags;
} __attribute__((__packed__));

建議使用者程式執行以下操作

struct iovec io[2];
struct payload e;

io[0].iov_base = &write_index;
io[0].iov_len = sizeof(write_index);
io[1].iov_base = &e;
io[1].iov_len = sizeof(e);

writev(fd, (const struct iovec*)io, 2);

注意: write_index 不會輸出到正在記錄的跟蹤中。

示例程式碼

請參閱 samples/user_events 中的示例程式碼。