英文

每个任务的统计信息接口

Taskstats 是一个基于 netlink 的接口,用于将每个任务和每个进程的统计信息从内核发送到用户空间。

Taskstats 的设计具有以下优点:

  • 在任务的生命周期内及其退出时,有效地提供统计信息

  • 多个统计子系统的统一接口

  • 可扩展性,供将来的统计补丁使用

术语

“pid”、“tid”和“task”可以互换使用,指的是由 struct task_struct 定义的标准 Linux 任务。每个 pid 的统计信息与每个任务的统计信息相同。

“tgid”、“process”和“thread group”可以互换使用,指的是共享 mm_struct 的任务,即传统的 Unix 进程。尽管使用了 tgid,但线程组的领导者任务没有特殊待遇 - 只要进程中包含属于它的任何任务,该进程就被认为是存活的。

用法

为了在任务的生命周期内获取统计信息,用户空间打开一个单播 netlink 套接字(NETLINK_GENERIC 族),并发送指定 pid 或 tgid 的命令。响应包含任务的统计信息(如果指定了 pid),或进程的所有任务的统计信息之和(如果指定了 tgid)。

为了获取正在退出的任务的统计信息,用户空间侦听器发送一个注册命令并指定一个 cpumask。每当一个任务在 cpumask 中的某个 CPU 上退出时,其每个 pid 的统计信息都会发送到注册的侦听器。使用 cpumask 允许限制一个侦听器接收的数据,并有助于 netlink 接口上的流量控制,这将在下面详细说明。

如果退出的任务是退出其线程组的最后一个线程,则还会向用户空间发送一条包含每个 tgid 统计信息的附加记录。后者包含线程组中所有线程(包括过去和现在的线程)的每个 pid 统计信息之和。

getdelays.c 是一个简单的实用程序,演示了 taskstats 接口用于报告延迟统计信息的用法。用户可以注册 cpumask,发送命令和处理响应,侦听每个 tid/tgid 的退出数据,将接收到的数据写入文件,并通过增加接收缓冲区大小来执行基本的流量控制。

接口

用户-内核接口封装在 include/linux/taskstats.h 中

为了避免此文档随着接口的发展而过时,此处仅给出当前版本的概要。taskstats.h 始终会覆盖此处的描述。

struct taskstats 是每个 pid 和每个 tgid 数据的通用统计结构。它已版本化,并且可以由添加到内核的每个统计子系统进行扩展。字段及其语义在 taskstats.h 文件中定义。

用户空间和内核空间之间交换的数据是一个属于 NETLINK_GENERIC 族的 netlink 消息,并使用 netlink 属性接口。消息的格式如下:

+----------+- - -+-------------+-------------------+
| nlmsghdr | Pad |  genlmsghdr | taskstats payload |
+----------+- - -+-------------+-------------------+

taskstats 负载是以下三种类型之一:

1. 命令:从用户发送到内核。用于获取 pid/tgid 数据的命令由一个属性组成,类型为 TASKSTATS_CMD_ATTR_PID/TGID,包含属性负载中的 u32 pid 或 tgid。pid/tgid 表示用户空间想要统计信息的任务/进程。

用于注册/注销对来自一组 CPU 的退出数据的兴趣的命令由一个属性组成,类型为 TASKSTATS_CMD_ATTR_REGISTER/DEREGISTER_CPUMASK,并包含属性负载中的 cpumask。cpumask 被指定为逗号分隔的 CPU 范围的 ascii 字符串,例如,要侦听来自 CPU 1,2,3,5,7,8 的退出数据,cpumask 将为“1-3,5,7-8”。如果用户空间在关闭侦听套接字之前忘记注销对 CPU 的兴趣,则内核会随着时间的推移清理其兴趣集。但是,为了提高效率,建议显式注销。

2. 命令的响应:从内核发送到用户空间以响应用户空间命令。负载是一系列三个属性,类型为:

a) TASKSTATS_TYPE_AGGR_PID/TGID:属性不包含负载,但指示 pid/tgid 后面将跟随一些统计信息。

b) TASKSTATS_TYPE_PID/TGID:属性的负载是要返回其统计信息的 pid/tgid。

c) TASKSTATS_TYPE_STATS:属性以 struct taskstats 作为负载。相同的结构用于每个 pid 和每个 tgid 的统计信息。

  1. 每当任务退出时,内核发送的新消息。负载由以下类型的属性组成:

  1. TASKSTATS_TYPE_AGGR_PID:指示接下来的两个属性将是 pid + 统计信息

  2. TASKSTATS_TYPE_PID:包含退出任务的 pid

  3. TASKSTATS_TYPE_STATS:包含退出任务的每个 pid 的统计信息

  4. TASKSTATS_TYPE_AGGR_TGID:指示接下来的两个属性将是 tgid + 统计信息

  5. TASKSTATS_TYPE_TGID:包含任务所属进程的 tgid

  6. TASKSTATS_TYPE_STATS:包含退出任务进程的每个 tgid 的统计信息

每个 tgid 的统计信息

Taskstats 除了提供每个任务的统计信息外,还提供每个进程的统计信息,因为资源管理通常以进程粒度完成,并且仅在用户空间中聚合任务统计信息效率低下且可能不准确(由于缺乏原子性)。

但是,在内核中维护每个进程(除了每个任务)的统计信息具有空间和时间开销。为了解决这个问题,taskstats 代码将每个退出任务的统计信息累积到一个进程范围内的数据结构中。当进程的最后一个任务退出时,也会将累积的进程级别数据(以及每个任务的数据)发送到用户空间。

当用户查询以获取每个 tgid 的数据时,组中所有其他活动线程的总和将被加起来,并添加到同一线程组先前退出的线程的累积总和中。

扩展 taskstats

有两种方法可以扩展 taskstats 接口,以导出更多的每个任务/进程统计信息,因为收集它们的补丁会在将来添加到内核中:

  1. 在现有 struct taskstats 的末尾添加更多字段。向后兼容性由结构中的版本号确保。用户空间将仅使用结构中与其正在使用的版本相对应的字段。

  2. 定义单独的统计结构,并使用 netlink 属性接口返回它们。由于用户空间独立处理每个 netlink 属性,因此它始终可以忽略其类型不理解的属性(因为它使用的是接口的旧版本)。

在 1 和 2 之间进行选择是权衡灵活性和开销的问题。如果只需要添加少量字段,那么 1 是首选路径,因为内核和用户空间不需要承担处理新 netlink 属性的开销。但是,如果新字段过度扩展了现有结构,导致不同的用户空间统计实用程序不必要地接收到大量对其字段不感兴趣的结构,那么扩展属性结构将是值得的。

taskstats 的流量控制

当任务退出速率变得很大时,侦听器可能无法跟上内核发送每个 tid/tgid 退出数据的速率,从而导致数据丢失。当 taskstats 结构被扩展并且 CPU 数量变得很大时,这种可能性会变得更加复杂。

为了避免丢失统计信息,用户空间应执行以下一项或多项操作:

  • 增加侦听器打开的 netlink 套接字的接收缓冲区大小,以接收退出数据。

  • 创建更多侦听器并减少每个侦听器正在侦听的 CPU 数量。在极端情况下,每个 CPU 可以有一个侦听器。用户还可以考虑将侦听器的 CPU 亲和性设置为它正在侦听的 CPU 子集,特别是如果它们只侦听一个 CPU。

尽管采取了这些措施,如果用户空间收到指示接收缓冲区溢出的 ENOBUFS 错误消息,则应采取措施处理数据丢失。