命令字:!ndx.loadavg

用法描述:

显示系统平均负载信息。相当于cat /proc/loadavg

开发过程:

1.找到关键全局变量

1.1avenrun

static int loadavg_proc_show(…):这是一个 proc 文件系统的回调函数,当用户读取/proc/loadavg时被调用
get_avenrun():从内核获取系统平均负载数据

get_avenrun()的定义中我们可以知道:函数内部访问了全局数组avenrun,取其中的数据做相应的计算,然后存入传入的数组loads[]

1.2 基地址:runqueues 偏移数组:_percpu_offset

这里通过函数nr_running()获取处于运行状态的进程总数

遍历所有在线(online)的 CPU,对于每个 CPU,通过cpu_rq(i)获取其对应runqueue结构,再获取runqueue->nr_running字段

这里的DECLARE_PER_CPU_SHARED_ALIGNED(struct rq, runqueues)为系统中的每个cpu声明了一个变量,类型为结构体 rq。
而&per_cpu(runqueues, (cpu))用于获取每个cpu中的变量runqueues的地址。

通过基地址:runqueues 和偏移数组:_percpu_offset计算获取准确的地址。

1.3 线程数量:nr_threads

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

avenrun数组,遍历取出数据仿照内核做相关计算即可。
runqueues: 结构体变量,类型为rq。

通过地址runqueues和偏移数组_percpu_offset中存放的偏移量。获取每个cpu中的这个结构变量runqueues的地址。
计算rq中的nr_running字段的偏移量。

然后计算具体字段的地址,获取具体字段的数据。

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


DECLARE_API(loadavg)
{
    ULONG64 avenrun_addr = 0;
    ULONG read;
    GetExpressionEx("lk!avenrun", &avenrun_addr, NULL);
    if (avenrun_addr == 0) {
        dprintf("Failed to get avenrun address. Please fix symbol.\n");
        return;
    }

    unsigned long long avenrun_buf[3];
    unsigned long long loads[3];
    ExtReadMemory(avenrun_addr, avenrun_buf, sizeof(avenrun_buf), &read);
    if (read != sizeof(avenrun_buf)) {
        dprintf("Failed to read avenrun from memory.\n");
        return;
    }

    // 仿照 get_avenrun 处理
    for (int i = 0; i < 3; i++) {
        loads[i] = (avenrun_buf[i] + FIXED_1 / 200); // shift=0
    }

    // 获取 nr_running 信息
    unsigned long nr_running = 0;
    ULONG64 runqueues_addr = 0;
    ULONG64 per_cpu_offset_addr = 0;

    GetExpressionEx("lk!runqueues", &runqueues_addr, NULL);
    GetExpressionEx("lk!__per_cpu_offset", &per_cpu_offset_addr, NULL);

    if (runqueues_addr != 0 && per_cpu_offset_addr != 0) {
        // 获取CPU数量
        int nr_cpus = get_nr_cpus();

        // 获取 rq 结构体中 nr_running 字段的偏移
        ULONG nr_running_offset = 0;
        GetFieldOffset("rq", "nr_running", &nr_running_offset);

        // 遍历所有CPU的runqueues并累加nr_running
        for (int cpu = 0; cpu < nr_cpus; cpu++) {
            // 读取当前CPU的per_cpu_offset
            ULONG64 cpu_offset = 0;
            ExtReadMemory(per_cpu_offset_addr + (cpu * sizeof(ULONG64)), &cpu_offset, sizeof(cpu_offset), &read);

            if (read == sizeof(cpu_offset)) {
                // 计算当前CPU的runqueue地址: per_cpu_ptr(runqueues, cpu) = runqueues + __per_cpu_offset[cpu]
                ULONG64 cpu_rq_addr = runqueues_addr + cpu_offset;

                // 读取当前CPU的nr_running值
                unsigned int cpu_nr_running = 0;
                ExtReadMemory(cpu_rq_addr + nr_running_offset, &cpu_nr_running, sizeof(cpu_nr_running), &read);
                if (read == sizeof(cpu_nr_running)) {
                    nr_running += cpu_nr_running;
                }
            }
        }
    }

    // 获取 nr_threads 信息
    ULONG64 nr_threads_addr = 0;
    int nr_threads = 0;
    GetExpressionEx("lk!nr_threads", &nr_threads_addr, NULL);
    if (nr_threads_addr != 0) {
        ExtReadMemory(nr_threads_addr, &nr_threads, sizeof(nr_threads), &read);
    }

    dprintf("%lu.%02lu %lu.%02lu %lu.%02lu %u/%d %d\n",
        LOAD_INT(loads[0]), LOAD_FRAC(loads[0]),
        LOAD_INT(loads[1]), LOAD_FRAC(loads[1]),
        LOAD_INT(loads[2]), LOAD_FRAC(loads[2]),
        nr_running, nr_threads);
}

3.实例测试

!ndx.loadavg

cat /proc/loadavg

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