命令字:!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-11 16:05