user_events: 基于用户的事件跟踪¶
- 作者:
Beau Belgrave
概述¶
基于用户的跟踪事件允许用户进程创建事件并跟踪数据,这些数据可以通过现有工具(如 ftrace 和 perf)进行查看。要启用此功能,请在构建内核时配置 CONFIG_USER_EVENTS=y。
程序可以通过 /sys/kernel/tracing/user_events_status 查看事件状态,并通过 /sys/kernel/tracing/user_events_data 注册并写出数据。
程序还可以通过带有 u: 前缀的 /sys/kernel/tracing/dynamic_events 来注册和删除基于用户的事件。发送给 dynamic_events 的命令格式与应用了 u: 前缀的 ioctl 相同。由于事件会持久化,这需要 CAP_PERFMON 权限,否则会返回 -EPERM。
通常,程序会注册一组它们希望向能够读取 trace_events 的工具(如 ftrace 和 perf)公开的事件。注册过程会告诉内核:如果有任何工具启用了该事件并且应该写入数据,则应反映在哪个地址和哪个位上。注册会返回一个写入索引(write index),当在 /sys/kernel/tracing/user_events_data 文件上调用 write() 或 writev() 时,该索引用于描述数据。
本文档中引用的结构体包含在源码树的 /include/uapi/linux/user_events.h 文件中。
注意: user_events_status 和 user_events_data 都位于 tracefs 文件系统下,并且可能挂载在与上述不同的路径下。
注册¶
在用户进程内部进行注册是通过对 /sys/kernel/tracing/user_events_data 文件调用 ioctl() 来完成的。要发出的命令是 DIAG_IOCSREG。
该命令接受一个紧凑的(packed)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:要使用的标志(如果有)。调用者应首先尝试使用 flags,如果失败则在不使用 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:在写出数据时,用于代表此事件的文件描述符的索引。该索引对于用于注册的文件描述符实例是唯一的。详见“写入数据”。
基于用户的事件会像名为“user_events”子系统下的任何其他事件一样显示在 tracefs 下。这意味着希望附加到这些事件的工具在附加/录制时需要使用 /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 in 看起来像这样
struct mytype {
char data[20];
};
将由以下字段表示
struct mytype myname 20
删除¶
从用户进程内部删除事件是通过对 /sys/kernel/tracing/user_events_data 文件调用 ioctl() 来完成的。要发出的命令是 DIAG_IOCSDEL。
此命令仅需要一个字符串,通过其名称指定要删除的事件。只有在对该事件没有剩余引用(在用户空间和内核空间中)时,删除才会成功。因此,用户程序应使用与注册时不同的文件来请求删除。
注意: 默认情况下,当事件没有剩余引用时,事件将自动删除。如果程序不希望自动删除,必须在注册事件时使用 USER_EVENT_REG_PERSIST 标志。一旦使用该标志,事件将一直存在,直到调用 DIAG_IOCSDEL。持久化事件的注册和删除都需要 CAP_PERFMON,否则会返回 -EPERM。当存在相同事件名称的多种格式时,将尝试删除所有同名事件。如果只想删除特定版本,则应使用 /sys/kernel/tracing/dynamic_events 文件来删除该特定格式的事件。
注销¶
如果在注册事件后不再希望更新它,可以通过对 /sys/kernel/tracing/user_events_data 文件调用 ioctl() 来禁用它。要发出的命令是 DIAG_IOCSUNREG。这与删除不同,删除实际上是从系统中移除事件。注销只是告诉内核你的进程不再对该事件的更新感兴趣。
该命令接受一个紧凑的(packed)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 必须位于数据的开头,然后其余数据被视为事件的有效负载(payload)。
例如,如果返回的 write_index 是 1,并且我想要写出一个 int 类型的事件有效负载。那么数据的大小必须是 8 字节(2 个 int),前 4 字节等于 1,最后 4 字节等于我作为有效负载所需的值。
在内存中,这看起来像这样
int index;
int payload;
用户程序可能拥有它们希望用作有效负载发出的知名结构体。在这些情况下,可以使用 writev(),其中第一个向量(vector)是索引,随后的向量是实际的事件有效负载。
例如,如果我有一个这样的 struct like
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 不会输出到正在记录的跟踪(trace)中。
示例代码¶
请参阅 samples/user_events 中的示例代码。