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)    收藏  举报

导航