英语

每任务统计接口

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 的统计信息将发送到注册的监听器。使用 cpumasks 可以限制一个监听器接收的数据,并有助于控制 netlink 接口上的流量,这将在下面更详细地解释。

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

getdelays.c 是一个简单的实用程序,演示了如何使用 taskstats 接口报告延迟记账统计信息。用户可以注册 cpumasks、发送命令和处理响应、监听每个 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 错误消息,则应采取措施处理数据丢失。