命令字:!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-22 20:17