命令字:!ndx.timer_list

用法描述

用于查看内核定时器相关信息的命令相当于cat /proc/timer_list

开发过程

1.找到关键全局变量

调用proc_create_seq_private在/proc目录下创建文件timer_list.当只是cat /proc/timer_list 就会调用proc_create_seq_private下的回调函数,动态打印信息。

基地址 hrtimer_bases 偏移数组 _percpu_offset

声明了一个全局per_cpu变量hrtimer_bases,类型为hrtimer_cpu_base 结构体

2.相关数据结构和思路:

timer_list_show打印函数

当不指定特定 CPU” 且 “处于首次遍历” 时输出头部信息。

如果不是首次遍历,且指定了cpu,则打印这个cpu上与定时器相关的详细状态信息和统计数据
struct hrtimer_cpu_base *cpu_base = &per_cpu(hrtimer_bases, cpu),根据cpu编号获取了这个cpu上的hrtimer_bases实例并且赋值给 cpu_base
然后开始遍历打印当前cpu中的中hrtimer_bases的相关信息

遍历的时候HRTIMER_MAX_CLOCK_BASES属于enum hrtimer_base_type,枚举常量从0开始递增,所以它为8。

cat /proc/timer_list 也能验证这一点。

然后调用print_base根据遍历的索引取出结构体数组clock_base中对应的struct hrtimer_clock_base类型的元素,然后打印当前结构体地址和相关字段。

打印当前活跃的定时器信息,用于调试或状态查看,这里面涉及的数据结构较为复杂。

先看最终打印了什么信息,可以看到都是和hrtimer相关的,意味着我们获取到这个结构体的实例,即可打印出相关信息

再看是怎么从 hrtimer_clock_base获取到对应的hrtimer
curr = timerqueue_getnext(&base->active);
从活动定时器队列(base->active)中获取第一个元素,作为遍历的起始节点。这里所说的队列底层实现是红黑树,只是宏观上它是按序号排列,所以被称为“队列”
curr = timerqueue_iterate_next(curr);
用于迭代遍历定时器,中序遍历红黑树。
timer = container_of(curr, struct hrtimer, node);
内核常用宏,通过结构体的成员指针(这里是 hrtimer 里的 node 成员),反向找到包含该成员的整个结构体(struct hrtimer)的起始地址。

如图hrtimer_clock_base,hrtimer通过红黑树的节点联系起来,刚开始是和红黑树的最左节点联系起来。
rb_root (struct rb_root_cached) //红黑数的一个扩展结构
这个扩展结构struct rb_root_cached缓存了红黑树的最左节点,便于遍历。

值得注意的是:因为内核中通过修改父节点指针的地址来进行红黑染色,所以在遍历红黑树获取父节点的时候,需要进行转换

下面是内核中获取父节点指针的具体做法:
~3 是按位取反操作,结果是除了最低 2 位为 0 外,其他位都为 1 的掩码
通过 (r)->_rbparent_color & ~3 可以清除最低 2 位的颜色信息,只保留父节点指针的地址部分

3.读取变量和相关偏移,输出信息

在遍历相关结构时候,要注意层级输出。

// timer_list所有需要的字段偏移量
static struct {
    ULONG rb_parent_color;  // rb_node.__rb_parent_color
    ULONG rb_right;         // rb_node.rb_right
    ULONG rb_left;         // rb_node.rb_left
    ULONG tnode_expires;    // timerqueue_node.expires
    ULONG hrtimer_node;     // hrtimer.node
    ULONG hrtimer_function; // hrtimer.function
    ULONG hrtimer_state;    // hrtimer.state
    ULONG hrtimer_is_rel;   // hrtimer.is_rel
    ULONG hrtimer_is_soft;  // hrtimer.is_soft
    ULONG hrtimer_is_hard;  // hrtimer.is_hard
    ULONG hrtimer_softexpires; // hrtimer._softexpires
} field_offsets;


// 查找父节点后继
static ULONG64 find_parent_successor(ULONG64 node_addr) {
    ULONG read;
    ULONG64 child = node_addr;
    ULONG64 parent_color = 0;
    ULONG64 parent = 0;

    while (1) {
        ExtReadMemory(child + field_offsets.rb_parent_color, &parent_color, sizeof(parent_color), &read);
        if (read != sizeof(parent_color)) {
            dprintf("Failed to read parent_color at %p\n", child);
            return 0;
        }
        parent = parent_color & ~3ULL;
        if (!parent)
            return 0;
        // 如果当前节点是父节点的左子节点,则返回父节点
        ULONG64 left = 0;
        ExtReadMemory(parent + field_offsets.rb_left, &left, sizeof(left), &read);
        if (read != sizeof(left)) {
            dprintf("Failed to read rb_left at %p\n", parent);
            return 0;
        }
        if (left == child)
            return parent;
        // 否则继续向上查找
        child = parent;
    }
}

// 查找最左节点
static ULONG64 find_leftmost(ULONG64 node_addr) {
    ULONG read;

    while (node_addr) {
        // 读取rb_left字段
        ULONG64 left = 0;
        ExtReadMemory(node_addr + field_offsets.rb_left, &left, sizeof(left), &read);
        if (read != sizeof(left)) {
            dprintf("Failed to read rb_left at %p\n", node_addr);
            return 0;
        }

        if (!left)
            break;

        node_addr = left;
    }

    return node_addr;
}

// 获取下一个节点
static ULONG64 rb_next_node(ULONG64 node_addr) {
    ULONG read;

    // 读取右子节点指针 (64位)
    ULONG64 right = 0;
    ExtReadMemory(node_addr + field_offsets.rb_right, &right, sizeof(ULONG64), &read);
    if (read != sizeof(ULONG64)) {
        dprintf("Failed to read rb_right at %p (read=%d bytes)\n", node_addr + field_offsets.rb_right, read);
        return 0;
    }

    // 如果有右子树,找右子树的最左节点
    if (right) {
        return find_leftmost(right);
    }

    // 否则向上查找合适的父节点
    return find_parent_successor(node_addr);
}


// 定时器信息打印函数
static void print_timer_info(ULONG64 node_addr, ULONG index) {
    ULONG read;

    // 计算hrtimer的地址(node_addr是timerqueue_node的地址)
    ULONG64 timer_addr = node_addr - field_offsets.hrtimer_node;


    // 读取function指针 (64位)
    ULONG64 function = 0;
    ExtReadMemory(timer_addr + field_offsets.hrtimer_function, &function, sizeof(ULONG64), &read);
    if (read != sizeof(ULONG64)) {
        dprintf("Failed to read timer function at %p (read=%d bytes)\n", timer_addr + field_offsets.hrtimer_function, read);
        return;
    }

    // 读取state字段
    u8 state = 0;
    ExtReadMemory(timer_addr + field_offsets.hrtimer_state, &state, sizeof(state), &read);
    if (read != sizeof(state)) {
        dprintf("Failed to read timer state at %p\n", timer_addr);
        return;
    }

    // 读取标志位
    u8 is_soft = 0, is_hard = 0, is_rel = 0;
    ExtReadMemory(timer_addr + field_offsets.hrtimer_is_soft, &is_soft, sizeof(is_soft), &read);
    ExtReadMemory(timer_addr + field_offsets.hrtimer_is_hard, &is_hard, sizeof(is_hard), &read);
    ExtReadMemory(timer_addr + field_offsets.hrtimer_is_rel, &is_rel, sizeof(is_rel), &read);


    // 读取软/硬到期时间
    ktime_t softexpires = 0, expires = 0;
    ExtReadMemory(timer_addr + field_offsets.hrtimer_softexpires, &softexpires, sizeof(softexpires), &read);
    ExtReadMemory(timer_addr + field_offsets.hrtimer_node + field_offsets.tnode_expires, &expires, sizeof(expires), &read);

    // 打印基本信息
    dprintf(" #%02d: <%p>, function: %p, S:%02x",
        index, timer_addr, function, state);

    // 打印标志位
    if (is_soft) dprintf(" soft");
    if (is_hard) dprintf(" hard");
    if (is_rel) dprintf(" rel");
    dprintf("\n");

    // 打印到期时间
    dprintf(" # expires at %lld-%lld nsecs \n",
        softexpires, expires);
}





// 打印clock base的信息
static void print_clock_base(ULONG64 clock_base_addr, int index) {
    ULONG read;
    dprintf(" clock %d:\n", index);

    // 获取所有需要的字段偏移
    ULONG nIndexOffset, nGetTimeOffset, nOffsetOffset;
    ULONG nCpuBaseOffset, nClockidOffset, nActiveOffset;
    GetFieldOffset("hrtimer_clock_base", "index", &nIndexOffset);
    GetFieldOffset("hrtimer_clock_base", "get_time", &nGetTimeOffset);
    GetFieldOffset("hrtimer_clock_base", "offset", &nOffsetOffset);
    GetFieldOffset("hrtimer_clock_base", "cpu_base", &nCpuBaseOffset);
    GetFieldOffset("hrtimer_clock_base", "clockid", &nClockidOffset);
    GetFieldOffset("hrtimer_clock_base", "active", &nActiveOffset);

    // 打印基本信息
    dprintf("  .base:       %p\n", clock_base_addr);

    unsigned int idx = 0;
    ExtReadMemory(clock_base_addr + nIndexOffset, &idx, sizeof(idx), &read);
    dprintf("  .index:      %d\n", idx);

    dprintf("  .resolution: %u nsecs\n", hrtimer_resolution);


    ULONG64 get_time_fn = 0;
    ExtReadMemory(clock_base_addr + nGetTimeOffset, &get_time_fn, sizeof(get_time_fn), &read);
    //to do (指针转函数名,通过符号表)

    dprintf("  .get_time:   %p\n", get_time_fn);

#ifdef CONFIG_HIGH_RES_TIMERS
    ULONG64 offset = 0;
    ExtReadMemory(clock_base_addr + nOffsetOffset, &offset, sizeof(offset), &read);
    dprintf("  .offset:     %llu nsecs\n", (unsigned long long)offset);
#endif

    // 计算active的地址
    ULONG64 active_addr = clock_base_addr + nActiveOffset;

    // 获取嵌套结构体中rb_leftmost的完整偏移
    ULONG rb_root_cached_offset = 0;
    ULONG rb_leftmost_in_cached_offset = 0;
    GetFieldOffset("timerqueue_head", "rb_root", &rb_root_cached_offset);
    GetFieldOffset("rb_root_cached", "rb_leftmost", &rb_leftmost_in_cached_offset);

    // 读取rb_leftmost(struct rb_node *)
    ULONG64 rb_leftmost = 0;
    ExtReadMemory(active_addr + rb_root_cached_offset + rb_leftmost_in_cached_offset,
        &rb_leftmost, sizeof(rb_leftmost), &read);

    if (rb_leftmost) {
        dprintf("active timers:\n");
        ULONG64 curr_node = rb_leftmost;
        ULONG i = 0;

        while (curr_node) {
            print_timer_info(curr_node, i++);
            curr_node = rb_next_node(curr_node);
        }
    }
    else {
        dprintf("    No active timers\n");
    }
}



// 主命令实现
DECLARE_API(timer_list)
{

    ULONG64 hrtimer_bases_addr = 0;
    ULONG64 per_cpu_offset_addr = 0;

    GetExpressionEx("lk!hrtimer_bases", &hrtimer_bases_addr, NULL);
    if (hrtimer_bases_addr == 0) {
        dprintf("Failed to get hrtimer_bases address. Please fix symbol.\n");
        return;
    }
    dprintf("hrtimer_bases_addr: %p\n", hrtimer_bases_addr);

    GetExpressionEx("lk!__per_cpu_offset", &per_cpu_offset_addr, NULL);
    if (per_cpu_offset_addr == 0) {
        dprintf("Failed to get __per_cpu_offset address. Please fix symbol.\n");
        return;
    }

    // 获取clock_base数组在结构体hrtimer_cpu_base的字段偏移
    ULONG nClockBaseOffset;
    GetFieldOffset("hrtimer_cpu_base", "clock_base", &nClockBaseOffset);
    // 获取所有需要的字段偏移   
    GetFieldOffset("rb_node", "__rb_parent_color", &field_offsets.rb_parent_color);
    GetFieldOffset("rb_node", "rb_right", &field_offsets.rb_right);
    GetFieldOffset("rb_node", "rb_left", &field_offsets.rb_left);
    GetFieldOffset("timerqueue_node", "expires", &field_offsets.tnode_expires);
    GetFieldOffset("hrtimer", "node", &field_offsets.hrtimer_node);
    GetFieldOffset("hrtimer", "function", &field_offsets.hrtimer_function);
    GetFieldOffset("hrtimer", "state", &field_offsets.hrtimer_state);
    GetFieldOffset("hrtimer", "is_rel", &field_offsets.hrtimer_is_rel);
    GetFieldOffset("hrtimer", "is_soft", &field_offsets.hrtimer_is_soft);
    GetFieldOffset("hrtimer", "is_hard", &field_offsets.hrtimer_is_hard);
    GetFieldOffset("hrtimer", "_softexpires", &field_offsets.hrtimer_softexpires);

    // 遍历每个CPU
    for (int cpu = 0; cpu < get_nr_cpus(); cpu++) {
        ULONG read;
        ULONG64 cpu_offset = 0;
        ExtReadMemory(per_cpu_offset_addr + cpu * sizeof(ULONG64),
            &cpu_offset, sizeof(cpu_offset), &read);

        ULONG64 cpu_base_addr = hrtimer_bases_addr + cpu_offset;

        dprintf("cpu: %d\n", cpu);

        // 遍历每个clock base
        for (int i = 0; i < HRTIMER_MAX_CLOCK_BASES; i++) {
            //计算每个clock base的地址(在hrtimer_cpu_base数组中)
            ULONG64 clock_base_addr = cpu_base_addr + nClockBaseOffset +
                i * GetTypeSize("hrtimer_clock_base");
            print_clock_base(clock_base_addr, i);
        }

        dprintf("\n");
    }
}

4.实例测试

!ndx.timer_list

sudo cat /proc/timer_list

作者:郭建程  创建时间:2025-09-09 11:17
最后编辑:郭建程  更新时间:2025-09-11 16:05