命令字:!ndx.uptime
用法描述
显示系统的运行时间和空闲时间统计信息。相当于cat /proc/uptime
开发过程(分别为两部分,cpu空闲时间,系统启动时间)
1.找到关键全局变量
1.1cpu空闲时间相关全局变量:
基地址kernel_cpustat
偏移数组 _percpu_offset
cpu数量:nr_cpu_ids
在遍历的时候,kcpustat_cpu_fetch(&kcs, i)获取当前cpu编号对应的结构体kcs,cpu编号肯定有个范围,通过全局变量nr_cpu_ids获取。
通过 kcpustat_cpu(cpu)获取对应的结构体kcs
在include\linux\kernel_stat.h
DECLARE_PER_CPU(struct kernel_cpustat, kernel_cpustat);为每个cpu定义了percpu变量kernel_cpustat,它的类型为struct kernel_cpustat
然后是一系列的宏定义:kcpustat_cpu(cpu)->per_cpu(kernel_cpustat, cpu)->(*per_cpu_ptr(&(var), cpu))
在per_cpu_ptr(ptr, cpu)中有具体一点的实现。
调用SHIFT_PERCPU_PTR这个宏实现获取每个cpu对应的相关结构的地址。具体逻辑:就是通过基地址加偏移,而偏移量用一个数组_percpu_offset维护。
现在我们清楚了,cpu空闲时间的相关的全局变量,可以通过这个三个变量结合相关数据结构获取空闲时间,数据结构将在下一点解释。
1.2系统启动时间的相关全局变量:tk_core
ktime_get_boottime_ts64(&uptime)获取系统从启动到当前时刻的时间信息,并将其存储在 uptime 变量中
然后是系列的函数调用:
ktime_get_boottime_ts64——>ktime_to_timespec64(ktime_get_boottime()) //这里ktime_to_timespec64是格式转换函数。
ktime_get_boottime()->ktime_get_with_offset(TK_OFFS_BOOT)
在ktime_get_with_offset中,return ktime_add_ns(base, nsecs).而这个变量的数据都和全局变量tk_core相关。
2.相关数据结构
2.1cpu空闲时间相关数据结构
我们可以获得每个cpu的结构体kernel_cpustat,它只包含了一个数组,这个数组记录了系统运行过程中各类工作负载消耗的 CPU 时间。
而在get_idle_time获取当前cpu空闲时间的时候,通过索引CPUTIME_IDLE,获取数组中记录的空闲时间。
2.2系统时间相关数据结构
ktime_get_with_offset中
struct timekeeper *tk = &tk_core.timekeeper;//先从全局变量中tk_core获取了时间管理的核心数据结构timekeeper
ktime_t base, *offset = offsets[offs];//定义了一个ktime_t类型的变量base,让offset指针指向offsets数组的第offs个元素,传入的参数是TK_OFFS_BOOT,即指向tk_core.timekeeper.offs_boot。
base = ktime_add(tk->tkr_mono.base, *offset);//将基准时间值和时间偏移量相加赋值给base变量。
内核中最终的结果是由base+nsecs计算精确的启动时间,当前我们实现的启动时间没有添加nsecs.
3.读取变量和相关偏移,输出信息
仿照内核中的格式化输出
DECLARE_API(uptime)
{
ULONG read;
// 获取 CPU 数量
int nr_cpu_ids = get_nr_cpus();
// 获取 tk_core 全局变量地址
ULONG64 tk_core_addr = 0;
GetExpressionEx("lk!tk_core", &tk_core_addr, NULL);
if (tk_core_addr == 0) {
dprintf("Failed to get tk_core address. Please fix symbol.\n");
return;
}
// 计算 timekeeper 结构体在 tk_core 中的偏移量
ULONG timekeeper_offset = 0;
GetFieldOffset("tk_core", "timekeeper", &timekeeper_offset);
// 计算 offs_boot 字段在 timekeeper 结构体中的偏移量
ULONG offs_boot_offset = 0;
GetFieldOffset("timekeeper", "offs_boot", &offs_boot_offset);
// 读取 tk_core.timekeeper.offs_boot 值
ULONG64 offs_boot_addr = tk_core_addr + timekeeper_offset + offs_boot_offset;
ULONG64 offs_boot = 0;
ExtReadMemory(offs_boot_addr, &offs_boot, sizeof(offs_boot), &read);
if (read != sizeof(offs_boot)) {
dprintf("Failed to read offs_boot from memory.\n");
return;
}
// 获取当前单调时间 (ktime_get)
ULONG64 mono_base_addr = tk_core_addr + timekeeper_offset;
ULONG mono_base_offset = 0;
GetFieldOffset("timekeeper", "tkr_mono", &mono_base_offset);
ULONG base_offset = 0;
GetFieldOffset("tk_read_base", "base", &base_offset);
ULONG64 mono_base_field_addr = mono_base_addr + mono_base_offset + base_offset;
ULONG64 mono_base = 0;
ExtReadMemory(mono_base_field_addr, &mono_base, sizeof(mono_base), &read);
if (read != sizeof(mono_base)) {
dprintf("Failed to read mono base from memory.\n");
return;
}
// 获取 kernel_cpustat 基址
ULONG64 kernel_cpustat_addr = 0;
GetExpressionEx("lk!kernel_cpustat", &kernel_cpustat_addr, NULL);
if (kernel_cpustat_addr == 0) {
kernel_cpustat_addr = 0xffff80000a24d8a0; // 临时方案
dprintf("use kernel_cpustat_test_addr = %p\n", kernel_cpustat_addr);
}
// 获取 per-cpu offset 数组
ULONG64 per_cpu_offset_addr = 0;
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;
}
// 获取 cpustat字段在结构体偏移
ULONG nCpustatOffset = 0;
GetFieldOffset("kernel_cpustat", "cpustat", &nCpustatOffset);
// idle 字段是 cpustat[5],每个元素8字节(u64)
ULONG idle_index = 5; // CPUTIME_IDLE
ULONG idle_offset = nCpustatOffset + idle_index * sizeof(ULONG64);
//遍历cpu计算总空闲时间
ULONG64 idle_time = 0, cpu_idle = 0, cpu_offset = 0;
for (int i = 0; i < nr_cpu_ids; i++) {
// 读取 __per_cpu_offset[i]
ExtReadMemory(per_cpu_offset_addr + i * sizeof(ULONG64), &cpu_offset, sizeof(cpu_offset), &read);
if (read != sizeof(cpu_offset)) {
continue;
}
// 计算该 CPU 的 kernel_cpustat->cpustat[5] 地址
ULONG64 cpu_addr = kernel_cpustat_addr + cpu_offset + idle_offset;
ExtReadMemory(cpu_addr, &cpu_idle, sizeof(cpu_idle), &read);
if (read == sizeof(cpu_idle)) {
// 每个cpu的空闲时间(纳秒/百分位)
ULONG64 cpu_idle_sec = cpu_idle / NSEC_PER_SEC;
ULONG64 cpu_idle_frac = (cpu_idle % NSEC_PER_SEC) / (NSEC_PER_SEC / 100);
dprintf("CPU[%d]: idle=%llu.%02llu\n", i, cpu_idle_sec, cpu_idle_frac);
idle_time += cpu_idle;
}
}
// 计算 boottime = mono_base + offs_boot
ULONG64 uptime_nsec = mono_base + offs_boot;
ULONG64 uptime_sec = uptime_nsec / NSEC_PER_SEC;
ULONG64 uptime_frac = (uptime_nsec % NSEC_PER_SEC) / (NSEC_PER_SEC / 100);
// 总空闲时间 纳秒拆分
ULONG64 idle_nsec = idle_time;
ULONG64 idle_sec = idle_nsec / NSEC_PER_SEC;
ULONG64 idle_frac = (idle_nsec % NSEC_PER_SEC) / (NSEC_PER_SEC / 100);
dprintf("%llu.%02llu %llu.%02llu\n", uptime_sec, uptime_frac, idle_sec, idle_frac);
}
4.实例测试
!ndx.uptime
cat /proc/uptime
最后编辑:郭建程 更新时间:2025-09-11 16:05