Netlink-1-使用Demo
一、通过libnl读取taskstats
1. 实现代码
/* * Minimal pure-C demo for reading taskstats via libnl. * * Usage: * taskstats_demo <pid> */ #include <errno.h> #include <inttypes.h> #include <linux/taskstats.h> #include <netlink/attr.h> #include <netlink/genl/ctrl.h> #include <netlink/genl/genl.h> #include <netlink/msg.h> #include <netlink/netlink.h> #include <netlink/socket.h> #include <stdbool.h> /* bool / true / false */ #include <stdio.h> #include <stdlib.h> #include <string.h> /* 头文件作用: netlink/attr.h: netlink 属性操作:nla_ok / nla_type / nla_data / nla_len 等; netlink/genl/ctrl.h: generic netlink 控制器:genl_ctrl_resolve 用于查 family id; netlink/genl/genl.h: generic netlink 消息构造:genlmsg_put / genlmsg_attrlen 等; netlink/msg.h: netlink 消息管理:nlmsg_alloc / nlmsg_free / nlmsg_hdr 等; netlink/netlink.h: netlink 基础定义:GENL_HDRLEN / NL_OK / NL_STOP / NL_SKIP 等; netlink/socket.h: libnl socket:nl_socket_alloc / nl_socket_free / genl_connect 等; */ struct request_ctx { pid_t pid; /* 我们想查询的目标 PID */ struct taskstats stats; /* 内核回包后,用来存储结果的 taskstats 结构体 */ bool got_stats; /* 回调函数是否已成功填充 stats */ }; /* * parse_aggregate - 解析 TASKSTATS_TYPE_AGGR_PID / AGGR_TGID 内嵌属性列表。 * * AGGR 属性是一个"嵌套属性包",里面依次存放: * TASKSTATS_TYPE_PID / TASKSTATS_TYPE_TGID -> 目标 pid/tgid 值 * TASKSTATS_TYPE_STATS -> 实际的 taskstats 结构体数据 * * 参数: * attr - 嵌套属性列表的起始指针(nla_data(outer_attr)) * attr_len - 嵌套属性列表的总字节长度(nla_len(outer_attr)) * stats - 输出参数,解析到的 taskstats 写到这里 * * 返回值:成功返回解析到的 pid/tgid;失败返回 -1。 */ static pid_t parse_aggregate(struct nlattr *attr, int attr_len, struct taskstats *stats) { pid_t got_pid = -1; /* nla_ok: 判断当前属性是否合法(长度足够、未越界),用作遍历条件 */ while (nla_ok(attr, attr_len)) { /* nla_type: 取属性的 type 字段,用来区分是哪种数据 */ switch (nla_type(attr)) { case TASKSTATS_TYPE_PID: /* 此属性携带的是线程 PID */ case TASKSTATS_TYPE_TGID: /* 此属性携带的是线程组 TGID */ got_pid = (pid_t)nla_get_u32(attr); /* nla_get_u32: 读取属性值(4 字节无符号整数),转成 pid_t 保存 */ break; case TASKSTATS_TYPE_STATS: { /* 此属性携带实际的 taskstats 结构体 */ size_t copy_len = sizeof(*stats); if (copy_len > (size_t)nla_len(attr)) { copy_len = (size_t)nla_len(attr); } memset(stats, 0, sizeof(*stats)); memcpy(stats, nla_data(attr), copy_len); /* nla_data: 返回属性数据区指针(跳过 nlattr 头部) */ return got_pid; } default: /* 遇到未知类型,打印警告后终止(内核格式不符合预期)*/ fprintf(stderr, "unexpected nested attribute type %u\n", nla_type(attr)); return -1; } /* nla_next: 移动到下一个属性,同时更新 attr_len 剩余字节数 */ attr = nla_next(attr, &attr_len); } return -1; /* 遍历完没找到 TASKSTATS_TYPE_STATS, 说明数据不完整 */ } /* * valid_cb - libnl "有效消息"回调,由 nl_recvmsgs 在收到合法回包时调用。 * * 内核对每条 TASKSTATS_CMD_GET 请求回复一条消息,消息里包含: * TASKSTATS_TYPE_AGGR_PID 或 TASKSTATS_TYPE_AGGR_TGID * 这两种外层属性里面再嵌套 PID/TGID + STATS。 * * 返回值语义: * NL_OK - 继续处理下一条消息 * NL_SKIP - 停止处理当前消息,继续下一条 * NL_STOP - 停止接收,nl_recvmsgs 直接返回 */ static int valid_cb(struct nl_msg *msg, void *arg) { /* 把 void* 还原成我们传进来的上下文,用于存结果 */ struct request_ctx *ctx = (struct request_ctx *)arg; /* nlmsg_hdr: 取 netlink 消息头指针(struct nlmsghdr*)*/ struct nlmsghdr *nlh = nlmsg_hdr(msg); /* nlmsg_data: 跳过 nlmsghdr,得到消息正文起点。对于 generic netlink,正文开头是 genlmsghdr。*/ struct genlmsghdr *gnlh = (struct genlmsghdr *)nlmsg_data(nlh); /* * genlmsghdr 之后是属性列表。GENL_HDRLEN 是 genlmsghdr 的对齐后长度(通常 4 字节)。 * 跳过它就到了第一个 nlattr 的位置。 */ struct nlattr *attr = (struct nlattr *)((char *)gnlh + GENL_HDRLEN); /* genlmsg_attrlen: 计算属性区域总字节数(消息总长 - nlmsghdr - genlmsghdr)*/ int remaining = genlmsg_attrlen(gnlh, 0); /* 遍历顶层属性列表 */ while (nla_ok(attr, remaining)) { switch (nla_type(attr)) { case TASKSTATS_TYPE_AGGR_PID: /* 按 PID 请求时,内核回 AGGR_PID */ case TASKSTATS_TYPE_AGGR_TGID: { /* 按 TGID 请求时,内核回 AGGR_TGID */ struct taskstats stats; /* 临时存解析结果 */ pid_t got_pid; /* * nla_data(attr): 取嵌套属性的数据区起始地址。 * nla_len(attr): 取嵌套属性数据区长度。 * 传给 parse_aggregate 去解析内层 PID + STATS 属性。 */ got_pid = parse_aggregate((struct nlattr *)nla_data(attr), nla_len(attr), &stats); if (got_pid < 0) { return NL_STOP; /* 解析失败,停止接收 */ } /* 正是我们要的那个 PID,把结果写入上下文 */ if (got_pid == ctx->pid) { ctx->stats = stats; ctx->got_stats = true; /* NL_SKIP: 不再处理当前消息剩余部分,但 nl_recvmsgs 还会等待 ACK 消息。*/ return NL_SKIP; } break; } case TASKSTATS_TYPE_NULL: /* 空属性,内核有时会在消息末尾填充,直接跳过 */ break; default: /* 不认识的属性类型,打印警告后继续 */ fprintf(stderr, "unexpected taskstats attribute type %u\n", nla_type(attr)); break; } /* 移动到下一个顶层属性 */ attr = nla_next(attr, &remaining); } return NL_OK; /* 遍历完,正常结束 */ } /* * err_cb - libnl 错误消息回调,当内核回包是 NLMSG_ERROR 时由 nl_recvmsgs 调用。 * * 如果 err->error == 0,表示这是 ACK 包(内核确认收到请求), * 直接返回 NL_STOP 告诉 libnl 停止等待。 * 如果 err->error < 0,表示真正的错误,打印后同样返回 NL_STOP。 */ static int err_cb(struct sockaddr_nl *nla, struct nlmsgerr *err, void *arg) { (void)nla; /* 发送方地址,这里不需要 */ (void)arg; /* 用户自定义参数,这里不需要 */ /* error == 0 是正常的 ACK,没有错误,停止等待即可 */ if (err->error == 0) { return NL_STOP; } /* err->error 是负的 errno 值,取反后转成错误描述字符串 */ fprintf(stderr, "netlink error: %s\n", strerror(-err->error)); return NL_STOP; /* 出错后也要停止,避免无限等待 */ } /* * fetch_taskstats - 向内核发送 TASKSTATS_CMD_GET 请求并等待结果。 * * 流程: * 1. 初始化请求上下文 * 2. 分配 netlink 消息,填写 generic netlink 头和 PID 属性 * 3. 注册回调:valid_cb 处理有效回包,err_cb 处理错误包 * 4. 发送消息 * 5. 等待接收回包(nl_recvmsgs 内部调用回调) * 6. 检查是否成功取到 taskstats */ static int fetch_taskstats(struct nl_sock *sock, int family_id, pid_t pid, struct taskstats *stats) { struct request_ctx ctx; /* 上下文,传给回调函数,用于存放结果 */ struct nl_msg *msg; /* 待发送的 netlink 消息 */ struct nl_cb *cb; /* 回调集合 */ int ret; memset(&ctx, 0, sizeof(ctx)); ctx.pid = pid; /* 告诉回调函数我们要找哪个 PID 的数据 */ msg = nlmsg_alloc(); /* 分配一个空的 netlink 消息对象 */ if (msg == NULL) { fprintf(stderr, "failed to allocate netlink message\n"); return -ENOMEM; } /* * genlmsg_put: 在消息里填写 netlink 头 + generic netlink 头。 * NL_AUTO_PORT - 自动填写本端端口号 * NL_AUTO_SEQ - 自动分配序列号(libnl 管理) * family_id - TASKSTATS generic netlink family 编号 * 0 - 额外 header 长度(无) * NLM_F_REQUEST - 标志:这是一条请求消息 * TASKSTATS_CMD_GET - 命令类型:查询 taskstats * TASKSTATS_VERSION - 协议版本号 * 返回 NULL 说明消息空间不足。 */ if (genlmsg_put(msg, NL_AUTO_PORT, NL_AUTO_SEQ, family_id, 0, NLM_F_REQUEST, TASKSTATS_CMD_GET, TASKSTATS_VERSION) == NULL) { nlmsg_free(msg); /* 释放消息,避免内存泄漏 */ return -ENOMEM; } /* * nla_put_u32: 在消息尾部追加一个 u32 类型的 netlink 属性。 * TASKSTATS_CMD_ATTR_PID - 属性类型:按 PID 查询 * (uint32_t)pid - 目标 PID 值 */ if (nla_put_u32(msg, TASKSTATS_CMD_ATTR_PID, (uint32_t)pid) < 0) { nlmsg_free(msg); return -EMSGSIZE; } /* * nl_cb_alloc: 分配一个回调集合,NL_CB_DEFAULT 使用 libnl 内置的默认行为。 * 回调集合控制 nl_recvmsgs 遇到不同消息时的处理方式。 */ cb = nl_cb_alloc(NL_CB_DEFAULT); if (cb == NULL) { nlmsg_free(msg); return -ENOMEM; } /* * nl_cb_err: 注册错误消息回调。 * NL_CB_CUSTOM - 使用自定义回调函数而不是默认行为 * err_cb - 我们的错误处理函数 * NULL - 不需要额外参数 */ nl_cb_err(cb, NL_CB_CUSTOM, err_cb, NULL); /* * nl_cb_set: 注册有效消息回调。 * NL_CB_VALID - 针对有效(非错误)消息类型注册 * NL_CB_CUSTOM - 使用自定义回调 * valid_cb - 我们的解析函数 * &ctx - 把上下文指针传给回调,用于写入解析结果 */ nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, valid_cb, &ctx); /* * nl_send_auto: 发送消息。 * "auto" 表示 libnl 自动填写序列号和本端端口号(与 genlmsg_put 的 AUTO 对应)。 * 返回值 >= 0 表示发出的字节数;< 0 表示错误码。 */ ret = nl_send_auto(sock, msg); if (ret < 0) { fprintf(stderr, "send failed: %s\n", nl_geterror(ret)); nl_cb_put(cb); /* 释放回调集合引用计数 */ nlmsg_free(msg); /* 释放消息 */ return ret; } /* * nl_recvmsgs: 阻塞等待内核回包,循环接收并调用对应回调, * 直到收到 NL_STOP 信号(valid_cb/err_cb 返回 NL_STOP)才结束。 */ ret = nl_recvmsgs(sock, cb); nl_cb_put(cb); /* 无论成功失败都要释放回调 */ nlmsg_free(msg); /* 无论成功失败都要释放消息 */ if (ret < 0) { fprintf(stderr, "recv failed: %s\n", nl_geterror(ret)); return ret; } /* 检查 valid_cb 是否成功把 stats 写入了上下文 */ if (!ctx.got_stats) { fprintf(stderr, "did not receive taskstats for pid %d\n", pid); return -ENOENT; /* ENOENT: 没有找到对应条目 */ } /* 把结果从临时上下文复制到调用方提供的输出参数 */ *stats = ctx.stats; return 0; /* 成功 */ } static void print_usage(const char *prog) { fprintf(stderr, "Usage: %s <pid>\n", prog); } /* * 整体流程: * 1. 解析命令行 PID 参数; * 2. 分配 libnl socket 并连接 generic netlink; * 3. 解析 TASKSTATS family id (内核为每个 generic netlink 家族分配动态编号); * 4. 调用 fetch_taskstats 取数; * 5. 打印结果; * 6. 释放 socket,退出; */ int main(int argc, char *argv[]) { struct nl_sock *sock; /* libnl socket 句柄,代表一条 netlink 连接 */ struct taskstats stats; /* 用来接收最终结果的结构体 */ int family_id; /* TASKSTATS generic netlink family 编号,运行时动态解析 */ int ret; /* 函数返回值 */ pid_t pid; /* 要查询的目标 PID */ if (argc != 2) { print_usage(argv[0]); return EXIT_FAILURE; } pid = (pid_t)atoi(argv[1]); if (pid <= 0) { fprintf(stderr, "invalid pid: %s\n", argv[1]); return EXIT_FAILURE; } /* * nl_socket_alloc: 分配一个 libnl socket 对象。这个对象内部管理文件描述符、序列号等状态。 * 注意:此时还没有打开真正的 socket fd。 */ sock = nl_socket_alloc(); if (sock == NULL) { fprintf(stderr, "failed to allocate libnl socket\n"); return EXIT_FAILURE; } /* * genl_connect: 创建真正的 AF_NETLINK socket 并绑定,同时连接到内核 generic netlink 多路复用器。 * 对应于底层 socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC) + bind。 */ if (genl_connect(sock) < 0) { fprintf(stderr, "failed to connect generic netlink socket\n"); goto error_out_1; } /* * genl_ctrl_resolve: 向内核 generic netlink 控制器查询指定名称的 family id。 * TASKSTATS_GENL_NAME = "TASKSTATS",内核为这个名称分配了一个动态整数编号, * 后续发送请求时必须用这个编号指定目标 family。 * 返回 < 0 表示不支持 taskstats 或查询失败。 */ family_id = genl_ctrl_resolve(sock, TASKSTATS_GENL_NAME); if (family_id < 0) { fprintf(stderr, "failed to resolve TASKSTATS family: %s\n", nl_geterror(family_id)); goto error_out_1; } /* 发送请求并等待结果 */ ret = fetch_taskstats(sock, family_id, pid, &stats); if (ret < 0) { goto error_out_1; } /* 无论成功与否,socket 都应及时释放(关闭 fd 并 free 对象)*/ nl_socket_free(sock); /* 打印 taskstats 中几个最常用的字段 */ printf("pid=%u comm=%s\n", stats.ac_pid, stats.ac_comm); printf("read_bytes=%llu, write_bytes=%llu\n", stats.read_bytes, stats.write_bytes); /* * 延迟统计(单位:纳秒): * cpu_delay_total - 任务处于 runnable 但等不到 CPU 的累计时间 * blkio_delay_total - 等待同步块 I/O 完成的累计时间 * swapin_delay_total - 等待 swap in(缺页换入)完成的累计时间 * freepages_delay_total - 等待内存回收完成的累计时间 * 这些依赖的deconfig默认没有使能,因此读取出来的全是0. */ printf("cpu_delay_ns=%llu, blkio_delay_ns=%llu, swapin_delay_ns=%llu, reclaim_delay_ns=%llu\n", stats.cpu_delay_total, stats.blkio_delay_total, stats.swapin_delay_total, stats.freepages_delay_total); printf("majflt=%llu, minflt=%llu, nvcsw=%llu, nivcsw=%llu\n", stats.ac_majflt, stats.ac_minflt, stats.nvcsw, stats.nivcsw); return EXIT_SUCCESS; error_out_1: nl_socket_free(sock); /* 释放 socket 避免泄漏 */ return EXIT_FAILURE; }
2. Android.bp
cc_binary { name: "taskstats_demo", srcs: ["taskstats_demo.cpp"], shared_libs: [ "libnl", "libutils", "libbinder", ], cflags: [ "-Wall", "-Werror", "-Wno-unused-function", "-Wno-unused-parameter", "-Wno-unused-variable", ], }
3. 测试结果
/data/local/tmp # ./taskstats_demo 2648 pid=2648 comm=peng.montecarlo read_bytes=122699776, write_bytes=16384 cpu_delay_ns=0, blkio_delay_ns=0, swapin_delay_ns=0, reclaim_delay_ns=0 majflt=2154, minflt=38783, nvcsw=9631, nivcsw=23
posted on 2026-06-30 13:46 Hello-World3 阅读(8) 评论(0) 收藏 举报
浙公网安备 33010602011771号