命令字:!ndx.intr

用法描述

!ndx.intr系统中断的详细统计信息,相当与cat /proc/interrupts

开发过程

1.找到关键全局变量

关键全局变量 作用
irq_desc_tree 中断描述符基数树根节点地址,索引所有中断描述符
nr_irqs 系统支持的最大中断号数量,决定遍历范围
per_cpu_offset 每CPU的偏移数组地址,统计各CPU中断次数时使用

在fs\proc\interrupts.c中:
调用proc_create_seq创建了interrupts文件接口,注册了四个回调函数,用于遍历相关数据结构显示具体信息

nr_irqs决定了遍历的范围

在show_interrupts中通过irq_to_desc获取与中断号i对应的中断描述符结构指针

irq_desc_trees这个全局变量为中断描述符基数树根节点
基数树的设计是 “内部节点用于索引,叶子节点存储实际数据”

2.相关数据结构

struct xarray 是 xarray 的核心结构,包含指向树结构头部的 xa_head 指针

struct xa_node 是 xarray 树结构中的节点

具体通过调用_radixtree_lookup 遍历基数树依据索引获取irq_desc结构体实例
radix_tree_is_internal_node(node)判断当前节点 node 是否为基数树的内部节点(而非叶子节点)。
radix_tree_descend(parent, &node, index)从父节点 parent 出发,根据索引 index 计算 “下一层子节点的偏移量 offset”,并通过 &node 传出下一层要访问的子节点。

radix_tree_descend函数通过索引计算父节点 slots 数组中的偏移量,从中获取下一层子节点(或数据项)并传出。
这里为什么可能是获取数据项呢?因为基数树的设计是 “内部节点用于索引,叶子节点存储实际数据”
当父节点是叶子节点的直接上层节点(即最底层内部节点)时,其slots数组不再指向子节点,而是直接存储实际数据项(如irq_desc实例)

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

static bool radix_tree_is_internal_node(ULONG64 ptr) {
    return (ptr & RADIX_TREE_ENTRY_MASK) == RADIX_TREE_INTERNAL_NODE;
}

// 从内部节点指针获取实际节点地址
static ULONG64 entry_to_node(ULONG64 ptr) {
    return ptr & ~RADIX_TREE_INTERNAL_NODE;
}

// 实现radix tree查找函数,模拟内核中的irq_to_desc
static ULONG64 irq_to_desc(int irq, ULONG64 irq_desc_tree_addr, ULONG xa_head_offset, ULONG shift_offset, ULONG slots_offset) {
    ULONG read;

    if (irq_desc_tree_addr == 0) {
        return 0;
    }

    // 读取radix_tree_root结构体中的xa_head字段(偏移量已预先获取)

    ULONG64 node = 0;
    ExtReadMemory(irq_desc_tree_addr + xa_head_offset, &node, sizeof(node), &read);
    if (read != sizeof(node) || node == 0) {
        return 0;
    }

    // 实现简化的radix tree查找
    unsigned long index = (unsigned long)irq;

    // 如果不是内部节点,直接返回
    if (!radix_tree_is_internal_node(node)) {
        return node;
    }

    // 处理内部节点的遍历
    while (radix_tree_is_internal_node(node)) {
        ULONG64 parent = entry_to_node(node);

        // 使用预先获取的xa_node结构字段偏移

        // 读取shift值
        unsigned char shift = 0;
        ExtReadMemory(parent + shift_offset, &shift, sizeof(shift), &read);
        if (read != sizeof(shift)) {
            return 0;
        }

        // 计算slot偏移,参考内核radix_tree_descend实现
        unsigned offset = (index >> shift) & RADIX_TREE_MAP_MASK;

        // 读取对应的slot
        ULONG64 slot_addr = parent + slots_offset + offset * sizeof(ULONG64);
        ExtReadMemory(slot_addr, &node, sizeof(node), &read);
        if (read != sizeof(node) || node == 0) {
            return 0;
        }

        // 如果shift为0,说明到达叶子节点
        if (shift == 0) {
            break;
        }
    }

    return node;
}

DECLARE_API(intr)
{
    ULONG read;

    // 获取irq_desc_tree的地址
    ULONG64 irq_desc_tree_addr = 0;
    GetExpressionEx("lk!irq_desc_tree", &irq_desc_tree_addr, NULL);
    if (irq_desc_tree_addr == 0) {
        dprintf("Failed to get irq_desc_tree address. Please fix symbol.\n");
        return;
    }

    // 获取 __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;
    }

    // 获取 nr_irqs 中断数量
    ULONG64 nr_irqs_addr = 0;
    int nr_irqs = 0;
    GetExpressionEx("lk!nr_irqs", &nr_irqs_addr, NULL);
    if (nr_irqs_addr == 0) {
        dprintf("Failed to get nr_irqs address. Please fix symbol.\n");
        return;
    }
    ExtReadMemory(nr_irqs_addr, &nr_irqs, sizeof(nr_irqs), &read);

    // 获取结构体字段偏移
    ULONG irqaction_offset, kstat_irqs_offset, chip_name_offset, action_name_offset, action_next_offset;
    ULONG irq_data_offset, chip_offset_in_irq_data, hwirq_offset;
    ULONG common_offset, state_offset; // 新增:用于获取触发类型信息
    // 预先获取irq_to_desc函数需要的偏移量,避免在循环中重复调用
    ULONG xa_head_offset, shift_offset, slots_offset;

    GetFieldOffset("irq_desc", "action", &irqaction_offset);
    GetFieldOffset("irq_desc", "kstat_irqs", &kstat_irqs_offset);
    GetFieldOffset("irq_desc", "irq_data", &irq_data_offset);
    GetFieldOffset("irq_data", "chip", &chip_offset_in_irq_data);
    GetFieldOffset("irq_data", "hwirq", &hwirq_offset);
    GetFieldOffset("irq_data", "common", &common_offset); // 新增
    GetFieldOffset("irq_common_data", "state_use_accessors", &state_offset); // 新增
    GetFieldOffset("irq_chip", "name", &chip_name_offset);
    GetFieldOffset("irqaction", "name", &action_name_offset);
    GetFieldOffset("irqaction", "next", &action_next_offset);
    GetFieldOffset("xarray", "xa_head", &xa_head_offset);
    GetFieldOffset("xa_node", "shift", &shift_offset);
    GetFieldOffset("xa_node", "slots", &slots_offset);

    // 获取 nr_cpu_ids cpu数量
    int nr_cpu_ids = get_nr_cpus();

    // 预先读取所有CPU的per_cpu_offset,避免在循环中重复读取
    ULONG64* cpu_offsets = new ULONG64[nr_cpu_ids];
    for (int i = 0; i < nr_cpu_ids; i++) {
        ExtReadMemory(per_cpu_offset_addr + i * sizeof(ULONG64), &cpu_offsets[i], sizeof(ULONG64), &read);
        if (read != sizeof(ULONG64)) {
            dprintf("Failed to read per_cpu_offset for CPU %d\n", i);
            cpu_offsets[i] = 0; // 读取失败时设为0
        }
    }

    // IPI中断信息存储结构
    struct IPIInfo {
        ULONG64 cpu_counts[32]; // 支持最多32个CPU
        char action_name[64];
    };
    IPIInfo ipi_interrupts[7]; // 存储1-7号IPI中断
    int ipi_count = 0;

    // IPI中断类型说明
    const char* ipi_descriptions[] = {
        "Rescheduling interrupts",
        "Function call interrupts",
        "CPU stop interrupts",
        "CPU stop (for crash dump) interrupts",
        "Timer broadcast interrupts",
        "IRQ work interrupts",
        "CPU wake-up interrupts"
    };

    // 打印表头
    dprintf("    ");
    for (int cpu = 0; cpu < nr_cpu_ids; cpu++)
        dprintf("    CPU%d", cpu);
    dprintf("\n");

    for (int i = 0; i < nr_irqs; i++) {
        // 使用irq_to_desc函数获取irq_desc结构体指针,传入预先获取的偏移量
        ULONG64 desc_addr = irq_to_desc(i, irq_desc_tree_addr, xa_head_offset, shift_offset, slots_offset);
        if (desc_addr == 0)
            continue;

        ULONG64 action_ptr = 0;
        ExtReadMemory(desc_addr + irqaction_offset, &action_ptr, sizeof(action_ptr), &read);
        if (action_ptr == 0)
            continue;

        // 检查是否为IPI中断 (1-7号)
        if (i >= 1 && i <= 7) {
            // 收集IPI中断信息
            IPIInfo& ipi = ipi_interrupts[ipi_count];

            // 读取每个CPU的统计信息
            ULONG64 kstat_irqs_ptr = 0;
            ExtReadMemory(desc_addr + kstat_irqs_offset, &kstat_irqs_ptr, sizeof(kstat_irqs_ptr), &read);

            for (int cpu = 0; cpu < nr_cpu_ids; cpu++) {
                ULONG64 cpu_offset = cpu_offsets[cpu];
                if (cpu_offset == 0) {
                    ipi.cpu_counts[cpu] = 0;
                    continue;
                }
                unsigned int irq_count = 0;
                ExtReadMemory(kstat_irqs_ptr + cpu_offset, &irq_count, sizeof(irq_count), &read);
                ipi.cpu_counts[cpu] = irq_count;
            }

            // 使用预定义的IPI说明
            strcpy_s(ipi.action_name, sizeof(ipi.action_name), ipi_descriptions[i - 1]);
            ipi_count++;
            continue; // 跳过普通中断的打印
        }

        dprintf("%3d: ", i);

        // 打印每个CPU统计该中断触发次数
        ULONG64 kstat_irqs_ptr = 0;
        ExtReadMemory(desc_addr + kstat_irqs_offset, &kstat_irqs_ptr, sizeof(kstat_irqs_ptr), &read);

        for (int cpu = 0; cpu < nr_cpu_ids; cpu++) {
            // 使用预先读取的cpu_offset
            ULONG64 cpu_offset = cpu_offsets[cpu];
            if (cpu_offset == 0) {
                dprintf("%8u ", 0);
                continue;
            }
            unsigned int irq_count = 0;
            ExtReadMemory(kstat_irqs_ptr + cpu_offset, &irq_count, sizeof(irq_count), &read);
            dprintf("%8u ", irq_count);
        }

        // 打印控制器名称 - desc->irq_data.chip->name
        ULONG64 chip_ptr = 0, chip_name_ptr = 0;
        // 先获取irq_data地址
        ULONG64 irq_data_addr = desc_addr + irq_data_offset;
        // 再获取chip指针
        ExtReadMemory(irq_data_addr + chip_offset_in_irq_data, &chip_ptr, sizeof(chip_ptr), &read);
        // 再获取chip->name指针
        if (chip_ptr) {
            ExtReadMemory(chip_ptr + chip_name_offset, &chip_name_ptr, sizeof(chip_name_ptr), &read);
        }
        char chip_name[32] = { 0 };
        if (chip_name_ptr) {
            ExtReadMemory(chip_name_ptr, chip_name, sizeof(chip_name), &read);

            // 过滤掉换行符和其他控制字符
            for (int i = 0; chip_name[i]; i++) {
                if (chip_name[i] == '\n' || chip_name[i] == '\r' || chip_name[i] == '\t') {
                    chip_name[i] = ' '; // 替换为空格
                }
            }
        }

        // 读取硬件中断号 (hwirq)
        ULONG64 hwirq = 0;
        ExtReadMemory(irq_data_addr + hwirq_offset, &hwirq, sizeof(hwirq), &read);

        // 获取触发类型信息
        ULONG64 common_ptr = 0;
        ExtReadMemory(irq_data_addr + common_offset, &common_ptr, sizeof(common_ptr), &read);

        const char* trigger_type = "";
        if (common_ptr) {
            unsigned int state = 0;
            ExtReadMemory(common_ptr + state_offset, &state, sizeof(state), &read);

            // 定义触发类型标志位
            const unsigned int IRQD_LEVEL = (1 << 13);

            if (state & IRQD_LEVEL) {
                trigger_type = " Level";
            }
            else {
                trigger_type = " Edge";
            }
        }

        dprintf(" %s %llu%s", chip_name, hwirq, trigger_type);

        // 打印action链表的所有name
        ULONG64 act_ptr = action_ptr;
        if (act_ptr) {
            // 读取第一个action的name指针
            ULONG64 act_name_ptr = 0;
            ExtReadMemory(act_ptr + action_name_offset, &act_name_ptr, sizeof(act_name_ptr), &read);
            if (act_name_ptr) {
                char act_name[64] = { 0 };
                ExtReadMemory(act_name_ptr, act_name, sizeof(act_name) - 1, &read);
                act_name[sizeof(act_name) - 1] = '\0'; // 确保字符串结尾

                // 过滤掉换行符和其他控制字符
                for (int i = 0; act_name[i]; i++) {
                    if (act_name[i] == '\n' || act_name[i] == '\r' || act_name[i] == '\t') {
                        act_name[i] = ' '; // 替换为空格
                    }
                }

                dprintf(" %s", act_name); // 紧凑格式显示
            }

            // 遍历action链表
            ExtReadMemory(act_ptr + action_next_offset, &act_ptr, sizeof(act_ptr), &read);
            while (act_ptr) {
                ULONG64 next_name_ptr = 0;
                ExtReadMemory(act_ptr + action_name_offset, &next_name_ptr, sizeof(next_name_ptr), &read);
                if (next_name_ptr) {
                    char act_name[64] = { 0 };
                    ExtReadMemory(next_name_ptr, act_name, sizeof(act_name) - 1, &read);
                    act_name[sizeof(act_name) - 1] = '\0'; // 确保字符串结尾

                    // 过滤掉换行符和其他控制字符
                    for (int i = 0; act_name[i]; i++) {
                        if (act_name[i] == '\n' || act_name[i] == '\r' || act_name[i] == '\t') {
                            act_name[i] = ' '; // 替换为空格
                        }
                    }

                    dprintf(", %s", act_name); // 紧凑格式,用逗号分隔
                }
                ExtReadMemory(act_ptr + action_next_offset, &act_ptr, sizeof(act_ptr), &read);
            }
        }
        dprintf("\n");
    }

    // 在最后显示收集的IPI中断信息,按照内核格式
    for (int i = 0; i < ipi_count; i++) {
        dprintf("IPI%d:", i);
        for (int cpu = 0; cpu < nr_cpu_ids; cpu++) {
            dprintf("%8llu ", ipi_interrupts[i].cpu_counts[cpu]);
        }
        dprintf("      %s\n", ipi_descriptions[i]);
    }

    // 清理内存
    delete[] cpu_offsets;
};

4.实例测试

!ndx.intr

cat /proc/interrupts

作者:郭建程  创建时间:2025-09-16 15:02
最后编辑:郭建程  更新时间:2025-09-22 20:17