3. VxWorks 中断控制器:类型、屏蔽与触发方式
中断控制器是 CPU 与外部中断源之间的桥梁,负责中断请求的收集、优先级仲裁、向量分发以及屏蔽控制。在 VxWorks 实时系统中,中断控制器的配置直接影响中断延迟和系统确定性。本章深入剖析 VxWorks 支持的两种主流中断控制器——i8259(PIC)和 GIC(Generic Interrupt Controller),并详细讲解中断屏蔽/使能机制与触发方式的选择策略。
3.1 中断控制器类型对比
VxWorks 支持多种中断控制器架构,最常见的是传统 x86 平台的 i8259A(PIC)和 ARM 平台的 GIC(v2/v3)。下表从关键维度对比两者的差异:
| 特性 | i8259A (PIC) | GICv2 (ARM) | GICv3 (ARM) |
|---|---|---|---|
| 中断源数量 | 最多 15 个(级联后) | 最多 1020 个(SPI+PPI+SGI) | 最多 1020+ 个(支持更多 SPI) |
| 优先级模型 | 固定优先级(IRQ 0 最高) | 可编程优先级(0-255) | 可编程优先级(0-255) |
| 中断向量 | 固定向量号(0x08-0x0F) | 软件可配置中断 ID | 软件可配置中断 ID |
| 中断分组 | 不支持 | Group 0(安全)/ Group 1(非安全) | Group 0/1/1S |
| 亲和性(CPU) | 不支持 | 支持(路由到指定 CPU) | 支持(更灵活的亲和性) |
| 典型平台 | x86、Pentium | Cortex-A9/A15 | Cortex-A53/A72/A76 |
3.2 中断屏蔽与使能机制
中断屏蔽是实时系统中控制中断响应顺序的关键手段。VxWorks 提供两级屏蔽:CPU 级屏蔽(通过中断控制器寄存器)和 系统级屏蔽(通过 VxWorks 中断锁)。
3.2.1 CPU 级屏蔽:操作中断控制器寄存器
在 i8259 中,屏蔽通过操作 IMR(Interrupt Mask Register) 实现。在 GIC 中,屏蔽通过 ICDISER(Interrupt Set-Enable Register) 和 ICDICER(Interrupt Clear-Enable Register) 控制。
/* i8259 屏蔽示例:屏蔽 IRQ3 和 IRQ4 */
#define PIC_MASTER_IMR 0x21
#define PIC_SLAVE_IMR 0xA1
void mask_irq_example(void) {
UINT8 imr_val;
/* 读取当前屏蔽寄存器 */
imr_val = sysInByte(PIC_MASTER_IMR);
/* 屏蔽 IRQ3 (bit3) 和 IRQ4 (bit4) */
imr_val |= (1 << 3) | (1 << 4);
sysOutByte(PIC_MASTER_IMR, imr_val);
}
/* GICv2 屏蔽示例:使能 SPI 中断 ID=67 */
#define GICD_ISENABLER(n) (GICD_BASE + 0x100 + (n) * 4)
void gic_enable_irq(UINT32 irq_id) {
UINT32 reg_offset = irq_id / 32;
UINT32 bit_mask = 1 << (irq_id % 32);
UINT32 *reg = (UINT32 *)GICD_ISENABLER(reg_offset);
/* 写 1 使能中断 */
*reg = bit_mask;
}
3.2.2 系统级屏蔽:VxWorks 中断锁
VxWorks 提供 intLock() 和 intUnlock() 函数,用于在 ISR 或任务中临时关闭所有可屏蔽中断。这是实现临界区保护的最简单方式,但会显著增加中断延迟。
/* 中断锁使用示例 */
void critical_section_example(void) {
int lock_key;
/* 保存当前中断状态并关闭所有中断 */
lock_key = intLock();
/* 临界区:访问共享数据结构 */
shared_data->counter++;
shared_data->flags |= 0x01;
/* 恢复之前的中断状态 */
intUnlock(lock_key);
}
| 屏蔽级别 | 作用范围 | 延迟影响 | 典型使用场景 |
|---|---|---|---|
| intLock() | 所有可屏蔽中断 | 高(全局关闭) | 极短临界区(< 10 µs) |
| intCtlLock() | 指定中断控制器 | 中(局部关闭) | 多核系统中的核间同步 |
| 寄存器级屏蔽 | 单个中断源 | 低(精确控制) | 外设驱动中的流控 |
3.3 中断触发方式
中断触发方式决定了中断信号如何被中断控制器识别。VxWorks 支持两种基本触发方式:电平触发(Level-Triggered) 和 边沿触发(Edge-Triggered)。在 GIC 中,还支持 脉冲触发(Pulsed) 用于 LPI。
3.3.1 电平触发 vs 边沿触发
| 特性 | 电平触发 | 边沿触发 |
|---|---|---|
| 信号识别 | 检测高/低电平持续状态 | 检测上升沿或下降沿跳变 |
| 中断丢失风险 | 低(电平持续存在) | 高(脉冲过窄可能丢失) |
| 共享中断 | 支持(需要电平触发) | 不支持(边沿无法共享) |
| 去抖动要求 | 低 | 高(需要硬件消抖) |
| 典型外设 | GPIO、I2C、SPI | 定时器、外部按键 |
| ISR 处理要求 | 必须清除中断源,否则持续触发 | 只需处理一次,不会重入 |
3.3.2 GIC 中的触发方式配置
在 GICv2 中,触发方式通过 ICDICFR(Interrupt Configuration Register) 配置。每个中断占用 2 位:
/* GICv2 触发方式配置示例 */
#define GICD_ICFGR(n) (GICD_BASE + 0xC00 + (n) * 4)
void gic_set_trigger(UINT32 irq_id, BOOL level_triggered) {
UINT32 reg_offset = irq_id / 16;
UINT32 bit_pos = (irq_id % 16) * 2;
UINT32 *reg = (UINT32 *)GICD_ICFGR(reg_offset);
UINT32 val;
val = *reg;
val &= ~(0x3 << bit_pos); /* 清除配置位 */
if (level_triggered) {
val |= (0x1 << bit_pos); /* 电平触发 */
} else {
val |= (0x2 << bit_pos); /* 边沿触发 */
}
*reg = val;
}
3.3.3 i8259 中的触发方式配置
在 i8259 中,触发方式通过 ICW1(Initialization Command Word 1) 的 LTIM 位配置。LTIM=1 表示电平触发,LTIM=0 表示边沿触发。所有中断共享相同的触发方式。
/* i8259 初始化时配置触发方式 */
#define PIC_MASTER_ICW1 0x20
#define PIC_SLAVE_ICW1 0xA0
void pic_init_trigger(BOOL level_mode) {
UINT8 icw1 = 0x11; /* ICW1 基本值:需要 ICW4 */
if (level_mode) {
icw1 |= 0x08; /* 设置 LTIM 位,电平触发 */
}
sysOutByte(PIC_MASTER_ICW1, icw1);
sysOutByte(PIC_SLAVE_ICW1, icw1);
}
3.4 中断控制器初始化流程
在 VxWorks BSP 中,中断控制器的初始化通常在 sysHwInit() 或 sysIntInit() 中完成。以下是标准初始化步骤:
- 禁用所有中断源:通过屏蔽寄存器或使能寄存器清零,防止初始化过程中产生意外中断。
- 配置触发方式:根据外设需求设置电平或边沿触发。
- 设置优先级:在 GIC 中配置每个中断的优先级值(0-255,0 最高)。
- 配置中断向量表:将中断 ID 映射到 ISR 入口地址。
- 使能中断控制器:设置全局使能位,允许中断传递到 CPU。
- 使能 CPU 中断:通过 CPSR(ARM)或 EFLAGS(x86)打开 CPU 中断位。
/* VxWorks GICv2 初始化伪代码 */
void sysIntInit(void) {
/* 1. 禁用所有 SPI 中断 */
for (int i = 0; i < 32; i++) {
*(volatile UINT32 *)(GICD_BASE + GICD_ICENABLER(i)) = 0xFFFFFFFF;
}
/* 2. 设置所有 SPI 为电平触发(默认) */
for (int i = 0; i < 64; i++) {
*(volatile UINT32 *)(GICD_BASE + GICD_ICFGR(i)) = 0x55555555;
}
/* 3. 设置默认优先级(0xA0,较低优先级) */
for (int i = 0; i < 255; i++) {
*(volatile UINT8 *)(GICD_BASE + GICD_IPRIORITYR(i)) = 0xA0;
}
/* 4. 使能 Distributor */
*(volatile UINT32 *)(GICD_BASE + GICD_CTLR) = 0x1;
/* 5. 使能 CPU Interface */
*(volatile UINT32 *)(GICC_BASE + GICC_CTLR) = 0x1;
/* 6. 设置优先级掩码(允许所有优先级) */
*(volatile UINT32 *)(GICC_BASE + GICC_PMR) = 0xFF;
}
intShow() 命令查看当前中断控制器的状态,包括已使能的中断、挂起状态和优先级配置。对于 GIC,gicShow() 提供更详细的寄存器转储。
3.5 性能优化要点
中断控制器的配置直接影响中断延迟(Interrupt Latency)和中断吞吐量。以下是针对不同控制器的优化建议:
- 减少屏蔽粒度:优先使用寄存器级屏蔽代替
intLock(),避免全局关闭中断。 - 合理设置优先级:将时间敏感的中断(如定时器、网络)设置为高优先级,非关键中断设置为低优先级。
- 利用亲和性:在多核系统中,将不同外设的中断路由到不同 CPU 核心,避免单核过载。
- 避免电平触发陷阱:使用电平触发时,确保 ISR 在返回前清除中断源,否则会导致无限重入。
- 批量操作寄存器:在 GIC 中,使用 32 位寄存器批量使能/屏蔽中断,减少总线访问次数。
/* i8259 EOI 示例 */
void pic_eoi(int irq) {
if (irq >= 8) {
sysOutByte(PIC_SLAVE_ICW2, 0x20); /* 发送 EOI 到从片 */
}
sysOutByte(PIC_MASTER_ICW2, 0x20); /* 发送 EOI 到主片 */
}
/* GICv2 EOI 示例 */
void gic_eoi(UINT32 irq_id) {
*(volatile UINT32 *)(GICC_BASE + GICC_EOIR) = irq_id;
}