掌握内核中的中断处理

发布: (2025年12月24日 GMT+8 09:02)
11 min read
原文: Dev.to

抱歉,我需要您提供要翻译的具体文本内容(除保留的 Source 行之外)。请把文章的正文粘贴在这里,我会按照要求将其翻译成简体中文并保留原有的格式。

Introduction

从头构建内核是一回事,精通其中断系统则是另一回事。在本文中,我们将逐步讲解为自定义内核设计、实现和微调一个功能完整的中断处理子系统——包括队列、优先级以及统计收集,且完全不依赖外部多任务框架。

主要目标

#目标
1立即执行关键 ISR(例如,PIT)
2灵活的排队和优先级处理非关键 ISR
3与中断相关的统计信息(处理时间、触发频率、丢弃计数)
4简洁、易维护的 API,用于注册和处理 ISR

ISR 参数块

系统的核心是 ISR 参数块 (isrpb_t)。
它存储配置标志、运行时统计信息、处理函数指针以及可选的上下文解析回调。

/* kernel_isrpb.h */
struct kernel_isrpb {
    ISR  handler;                                   /* ISR entry point */

    /* Runtime statistics */
    unsigned int avgTimeElapsed;    /* moving average of execution time (ticks) */
    unsigned int avgTrigPerSecond; /* average trigger frequency (Hz)          */
    unsigned int maxTimeElapsed;   /* longest observed execution time          */
    unsigned int minTimeElapsed;   /* shortest observed execution time         */

    unsigned int trigCount;        /* total number of triggers                 */
    unsigned int trigCountTmp;    /* temporary counter for frequency calc.   */
    unsigned int completeCount;   /* number of successful executions         */
    unsigned int trigTimestamp;   /* timestamp of the last trigger            */
    unsigned int droppedCount;     /* how many times the ISR was dropped      */

    unsigned int flags;            /* configuration flags (see below)         */

    /* Optional extra context resolver */
    void* (*additionalContextResolv)(struct kernel_isrpb* param);
} __attribute__((packed));

使用统一的块可以让我们始终如一地管理每个中断,并在以后扩展功能。

队列与优先级

非关键 ISR 被放入 FIFO 循环缓冲区
队列支持从 ISR 标志派生的 三级优先级

/* Priority helper */
static inline unsigned int isr_getPriority(const isrpb_t *isr)
{
    if (isr->flags & IsrPriorityCrit)   return 0;          /* highest */
    return (isr->flags & IsrPriorityHigh) ? 1 : 2;      /* medium / low */
}

自动从队列取出

调度器始终挑选就绪的最高优先级 ISR:

/* Return the next ISR, or NULL if the queue is empty */
isrpb_t* isr_queue_autotake(void)
{
    for (unsigned int priority = 0; priority trigCount++;
    isr->trigCountTmp++;
    isr->trigTimestamp = pit_get();          /* current timer tick */

    /* Re‑entrancy protection */
    if ((isr->flags & IsrActive) && !(isr->flags & IsrReentrant))
        return;                              /* already running – ignore */

    isr->flags |= IsrActive;                 /* mark as active */

    u32 start = 0;
    int collectStats = (isr->flags & IsrDoStats) && !(isr->flags & IsrPriorityCrit);
    if (collectStats) start = isr->trigTimestamp;

    /* Call the actual handler */
    ISR handler = isr->handler;
    handler(reg);

    /* Update statistics if requested */
    if (collectStats) isr_doStats(isr, start);

    isr->flags &= ~IsrActive;               /* clear active flag */
    isr->completeCount++;                   /* successful execution */
}

队列处理与 IRQ 入口点

/* Process the next ISR from the queue */
static void isr_processQueue(void)
{
    isrpb_t *isr = isr_queue_autotake();
    if (isr) isr_dispatch(isr, NULL, 1);
}

/* Central IRQ handler (called by the CPU on every interrupt) */
void isr_irqHandler(REGISTERS *reg)
{
    /* ... decode the interrupt, locate the matching ISR block ... */
    unsigned int priority = isr_getPriority(isr);

    /* Critical ISR or empty queue → run immediately */
    if (isr_canRunNow(priority))
        goto run_now;

    /* Otherwise enqueue */
    isr_queue_push(isr);
    return;

run_now:
    isr_dispatch(isr, reg, 1);
    isr_processQueue();    /* keep the system responsive */
}

关键 ISR(例如 PIT)绕过队列,立即执行,确保最短的延迟。

统计收集

对于每个 ISR,我们保留一小组运行时指标。
所有统计 droppedCount 之外 仅在 非关键 ISRIsrDoStats 标志被设置时更新。

执行时间统计

/* Called from isr_doStats() after an ISR finishes */
static void isr_updateTimeStats(isrpb_t *isr, u32 elapsed)
{
    if (isr->completeCount > 0) {
        /* Moving average */
        isr->avgTimeElapsed = ((isr->avgTimeElapsed * isr->completeCount) + elapsed)
                               / (isr->completeCount + 1);

        if (elapsed > isr->maxTimeElapsed) isr->maxTimeElapsed = elapsed;
        if (elapsed minTimeElapsed) isr->minTimeElapsed = elapsed;
    } else {
        /* First measurement */
        isr->avgTimeElapsed = isr->minTimeElapsed = isr->maxTimeElapsed = elapsed;
    }
}

丢弃计数处理

当循环缓冲区已满时,ISR 无法入队,并计为已丢弃:

/* Inside isr_queue_push() */
if (next == queue->head) {
    /* Queue full – overload condition */
    isr->droppedCount++;
    return;
}

触发频率统计

/* Update average trigger frequency (Hz) */
static void isr_updateFreqStats(isrpb_t *isr, u32 now)
{
    u32 elapsed = now - isr->trigTimestamp;
    if (elapsed == 0) elapsed = 1;               /* avoid division by zero */

    if (elapsed >= PIT_FREQUENCY) {
        isr->avgTrigPerSecond = (isr->trigCountTmp * PIT_FREQUENCY) / elapsed;
        isr->trigCountTmp = 0;                    /* reset temporary counter */
    }
}

数据告诉你的内容

MetricInsight
avgTimeElapsed / min / max哪个 ISR 消耗的 CPU 时间最多,以及其变化幅度。
avgTrigPerSecond每个中断的触发频率——有助于发现失控的定时器。
trigCount vs. completeCount差异表明 ISR 被进入但未完成的次数(例如,被抢占)。
droppedCount过载情况——帮助你确定队列大小或优化 ISR 延迟。

通过分析这些数字,你可以识别“热点”中断,平衡优先级,并调优内核以实现确定性的实时行为。

摘要

  • 一个 单一、紧凑的 ISR 参数块 (isrpb_t) 存储所有配置和运行时数据。
  • 三级优先级(critical → high → low)驱动即时执行和队列排序。
  • 循环 FIFO 队列 在保持系统响应的同时遵循优先级。
  • 调度逻辑 更新统计信息,防止重入,并遵守 IsrDoStats 标志。
  • 统计信息(执行时间、频率、丢弃计数)提供对中断行为的深入洞察,支持基于数据的优化。

有了这个基础,您拥有一个稳健、可扩展的中断子系统,可在此之上构建更高级的功能(嵌套中断、每 CPU 队列等),同时保持内核精简且易于维护。

概述

当补丁和注册机制就绪时,系统运行顺畅。
即使我尚未实现多任务,这种设计仍然让内核能够:

  • 保持可预测且可度量的行为。
  • 简化设备驱动程序的调试。

为了更好地理解和调试中断行为,我在内核中添加了一个 IRQ Lookup 实用工具。

用法

1. 列出所有已注册的中断处理程序

irqlookup          # no arguments

该工具遍历所有已注册的中断处理程序,并以以下格式打印每个 ISR 的关键信息:

IRQ_BASE+ -> (Handler address (hex)) (Handler function symbol name):
    -> avgTps=X avgTE=X minTE=X maxTE=X, lastTrig=X cc=X tc=X dc=X flags=X
  • avgTps – 每秒平均触发次数
  • avgTE – 平均耗时(µs)
  • minTE / maxTE – 最小/最大耗时(µs)
  • lastTrig – 最近一次触发的时间戳
  • cc – 确认计数
  • tc – 完成计数
  • dc – 丢弃计数
  • flags – 标志位(以原始整数显示)

2. 显示特定 IRQ 的详细信息

irqlookup --show-irq <IRQ_NUMBER>

该命令打印 ISR 参数块的详细摘要,包括:

字段描述
IRQ 编号中断的编号
处理程序地址和符号地址(十六进制)和函数名
上下文解析函数地址和符号地址(十六进制)以及解析额外上下文的辅助函数名称
统计信息
– 确认计数 (trigCount)IRQ 被触发的次数
– 完成计数 (completeCount)ISR 成功完成的次数
– 丢弃计数 (dropCount)ISR 被中止的次数
– 最近时间戳最近一次触发的时间
– 平均/最小/最大耗时单位为 µs
– 总耗时 (time_tot)avgTimeElapsed * trigCount(u64)
– 平均频率每秒触发次数,取整个生命周期的平均值
– 标志位人类可读的标志名称(见下文 标志解码

总耗时计算

u64 time_tot = ((p->flags & IsrDoStats) && !(p->flags & IsrPriorityCrit) && p->avgTimeElapsed != 0)
               ? (p->completeCount * p->avgTimeElapsed)
               : 0;

3. 解码原始标志整数

irqlookup --decode-flags <FLAGS_INTEGER>

该工具将整数转换为以空格分隔的标志名称列表。

标志名称助手

const char* flagName(unsigned int bit) {
    switch (bit) {
        case IsrActive:      return "IsrActive";
        // …
        case IsrWakeup:      return "IsrWakeup";
        default:             return "Unknown";
    }
}

打印已设置的标志

int first = 1;
for (unsigned int bit = 1; bit != 0; bit flags & bit) {
        if (!first) callback_stdout("\n                         ");
        callback_stdout((char*)flagName(bit));
        first = 0;
    }
}

设计原理

掌握内核中的中断处理不仅仅是让 ISR 运行;还需要观察、控制和优先级管理它们,以实现高效处理。

  • 基于队列的优先级系统 – 确保关键中断能够立即得到服务,而次要中断则按顺序等待,从而保持系统的稳定性和可预测性。
  • IRQ 查询工具 – 提供对中断子系统的实时可视化,使开发者能够:
    • 检查已注册和激活的 ISR。
    • 监控执行统计信息(延迟、频率、计数)。
    • 验证标志和处理程序状态,以确保行为正确。

这些机制共同为内核开发者提供了稳健性和透明性。

Back to Blog

相关文章

阅读更多 »