掌握内核中的中断处理
抱歉,我需要您提供要翻译的具体文本内容(除保留的 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 之外 仅在 非关键 ISR 且 IsrDoStats 标志被设置时更新。
执行时间统计
/* 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 */
}
}
数据告诉你的内容
| Metric | Insight |
|---|---|
| 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。
- 监控执行统计信息(延迟、频率、计数)。
- 验证标志和处理程序状态,以确保行为正确。
这些机制共同为内核开发者提供了稳健性和透明性。