每任务统计接口¶
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 统计信息。
内核在任务退出时发送的新消息。有效负载包含一系列以下类型的属性
TASKSTATS_TYPE_AGGR_PID:表示接下来的两个属性将是 pid+统计信息
TASKSTATS_TYPE_PID:包含退出任务的 pid
TASKSTATS_TYPE_STATS:包含退出任务的每个 pid 统计信息
TASKSTATS_TYPE_AGGR_TGID:表示接下来的两个属性将是 tgid+统计信息
TASKSTATS_TYPE_TGID:包含任务所属进程的 tgid
TASKSTATS_TYPE_STATS:包含退出任务进程的每个 tgid 统计信息
每个 tgid 统计信息¶
Taskstats 除了提供每个任务的统计信息外,还提供每个进程的统计信息,因为资源管理通常是在进程粒度上完成的,并且仅在用户空间中聚合任务统计信息效率低下且可能不准确(由于缺乏原子性)。
但是,除了每个任务的统计信息外,在内核中维护每个进程的统计信息还存在空间和时间开销。为了解决这个问题,taskstats 代码将每个退出任务的统计信息累积到进程范围内的数据结构中。当进程的最后一个任务退出时,累积的进程级别数据也会发送到用户空间(以及每个任务的数据)。
当用户查询以获取每个 tgid 数据时,组中所有其他活动线程的总和将相加,并添加到同一线程组先前退出线程的累积总数中。
扩展 taskstats¶
有两种方法可以扩展 taskstats 接口,以导出更多每个任务/进程的统计信息,因为随着补丁的添加,它们会在将来添加到内核中
在现有 struct taskstats 的末尾添加更多字段。通过结构中的版本号确保向后兼容性。用户空间将仅使用与其正在使用的版本相对应的结构的字段。
定义单独的统计结构并使用 netlink 属性接口返回它们。由于用户空间独立处理每个 netlink 属性,因此它始终可以忽略其不理解的类型的属性(因为它使用的是旧版本的接口)。
在 1 和 2 之间进行选择是权衡灵活性和开销的问题。如果只需要添加少量字段,那么 1 是首选路径,因为内核和用户空间不需要承担处理新 netlink 属性的开销。但是,如果新字段过度扩展了现有结构,导致不同的用户空间记账实用程序不必要地接收到大型结构,而这些结构中的字段对其没有意义,那么扩展属性结构将是值得的。
taskstats 的流量控制¶
当任务退出的速率变得很大时,监听器可能无法跟上内核发送每个 tid/tgid 退出数据的速率,从而导致数据丢失。当 taskstats 结构被扩展并且 cpu 的数量增加时,这种可能性会加剧。
为了避免丢失统计信息,用户空间应执行以下一项或多项操作
增加监听器打开的 netlink 套接字的接收缓冲区大小,以接收退出数据。
创建更多监听器并减少每个监听器正在监听的 cpu 数量。在极端情况下,每个 cpu 可能有一个监听器。用户还可以考虑将监听器的 cpu 亲和性设置为它所监听的 cpu 的子集,尤其是在它们仅监听一个 cpu 的情况下。
尽管采取了这些措施,如果用户空间收到表示接收缓冲区溢出的 ENOBUFS 错误消息,则应采取措施处理数据丢失。