目录
1. 前言与背景
1.1 问题的由来
在文档《为什么Atomic-Context不能睡眠-极度详细版》中,我们详细分析了使用独立IRQ-Stack时,中断处理函数为什么不能调用引起睡眠的函数。核心问题包括:
- 栈地址错误(保存了中断栈地址而非进程栈地址)
- 中断栈数据被覆盖(新中断覆盖旧中断的pt_regs)
- 中断返回路径断裂
- 进程上下文丢失
现在要探讨:如果中断使用被中断任务的任务栈(而非独立IRQ-Stack),情况会如何?
1.2 本文的核心观点
即使使用任务栈,中断处理函数仍然绝对不能睡眠!
虽然使用任务栈避免了”栈地址错误”的问题,但引入了其他同样严重甚至更危险的问题。
2. 两种中断栈模型对比
2.1 模型一:独立IRQ-Stack
ARM 处理器模式与栈的关系
════════════════════════════════════════════════════════════
User mode: SP_usr → 用户栈 (用户空间)
SVC mode: SP_svc → 内核栈 (每进程独立, 8KB)
IRQ mode: SP_irq → 中断栈 (全局共享, 8KB) ← 独立IRQ-Stack
中断发生时的栈切换:
用户态运行 (SP = SP_usr)
↓ 中断发生
硬件切换到 IRQ mode
SP ← SP_irq (切换到独立中断栈)
↓ 执行中断处理
在中断栈上运行
↓ 中断返回
SP ← SP_usr (切换回用户栈)
内存布局示意:
物理内存布局
════════════════════════════════════════════════════════════
0xC0802000 ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ CPU0 IRQ 栈 (8KB) ┃ ← 独立中断栈
0xC0800000 ┃ 所有硬中断共享 ┃ (全局共享)
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
0xC1003000 ┃ 进程A 内核栈 (8KB) ┃
┃ ├─ thread_info ┃
0xC1001000 ┃ └─ 栈空间 ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
0xC1005000 ┃ 进程B 内核栈 (8KB) ┃
0xC1003000 ┃ ├─ thread_info ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
特点:
✓ 中断栈与进程栈物理分离
✓ 所有中断共享一个固定大小的栈
✓ 中断栈空间可控、可预测
✗ 需要硬件支持不同的SP (SP_irq, SP_svc)
✗ 栈切换有轻微开销
2.2 模型二:使用任务栈
中断发生时的栈切换:
用户态运行 (SP = SP_usr)
↓ 中断发生
硬件切换到 SVC mode (注意: 不是IRQ mode)
SP ← SP_svc (使用当前进程的内核栈) ← 使用任务栈!
↓ 执行中断处理
在进程的内核栈上运行
↓ 中断返回
SP ← SP_usr (切换回用户栈)
关键区别:
• 不切换到IRQ mode, 而是切换到SVC mode
• 使用当前进程的内核栈 (SP_svc)
• 没有独立的中断栈
内存布局示意:
物理内存布局
════════════════════════════════════════════════════════════
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
0xC1003000 ┃ 进程A 内核栈 (8KB) ┃
┃ ┃
┃ ┌──────────────────────┐ ┃
0xC1002F00 ┃ │ pt_regs (用户上下文) │ ┃ ← 中断发生时保存
┃ ├──────────────────────┤ ┃
0xC1002E00 ┃ │ 中断处理栈帧 │ ┃ ← 中断在这里执行
┃ │ (函数调用、局部变量) │ ┃
┃ └──────────────────────┘ ┃
┃ ┃
0xC1001000 ┃ thread_info ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
0xC1005000 ┃ 进程B 内核栈 (8KB) ┃
0xC1003000 ┃ (进程B运行时使用) ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
特点:
✓ 不需要独立的中断栈空间
✓ 栈地址"正确" (是进程的内核栈)
✓ 没有栈切换开销
✗ 进程栈必须足够大以容纳中断嵌套
✗ 栈溢出风险更高
✗ 安全边界不清晰
2.3 关键差异总结
对比项 独立IRQ-Stack 使用任务栈
───────────── ───────────── ──────────────
栈的所有者 全局共享 当前进程
栈的位置 固定物理地址 随进程变化
SP切换 需要 (到SP_irq) 不需要 (保持SP_svc)
CPU模式 IRQ mode SVC mode
栈大小 固定 (8KB) 随进程 (8KB)
栈溢出风险 低 (可控) 较高 (共享)
内存开销 额外8KB/CPU 无额外开销
schedule()栈地址 错误 (中断栈) 正确 (进程内核栈)
3. 使用任务栈的中断处理流程
3.1 正常流程(不睡眠)
完整时间线: 进程A运行 → 中断 → 处理 → 返回
════════════════════════════════════════════════════════════
┌─────────────────────────────────────────────────────────┐
│ T0: 进程A在用户态运行 │
├─────────────────────────────────────────────────────────┤
│ CPU模式: User mode │
│ PC: 0x08048100 (用户代码) │
│ SP_usr: 0xBFFFFE00 (用户栈) │
│ SP_svc: 0xC1002F00 (进程A的内核栈, 空闲) │
│ current: 进程A │
│ │
│ 进程A正在执行用户态代码 │
└─────────────────────────────────────────────────────────┘
↓ UART中断信号到达
┌─────────────────────────────────────────────────────────┐
│ T1: 硬件自动响应 │
├─────────────────────────────────────────────────────────┤
│ 硬件自动操作: │
│ 1. SPSR_svc ← CPSR (保存用户态CPSR) │
│ 2. LR_svc ← PC+4 (保存返回地址) │
│ 3. CPSR ← 0xD3 (SVC mode, IRQ disabled) │
│ 4. SP ← SP_svc = 0xC1002F00 │
│ 5. PC ← 中断向量地址 │
│ │
│ 关键点: │
│ • 切换到SVC mode (不是IRQ mode) │
│ • 使用进程A的内核栈 ✓ │
│ • 没有切换到独立中断栈 │
│ │
│ CPU模式: SVC mode │
│ SP: SP_svc = 0xC1002F00 (进程A内核栈) │
│ current: 进程A │
└─────────────────────────────────────────────────────────┘
↓ 执行中断入口代码
┌─────────────────────────────────────────────────────────┐
│ T2: 软件保存上下文 (类似entry-armv.S) │
├─────────────────────────────────────────────────────────┤
│ 伪代码: │
│ sub sp, sp, #S_FRAME_SIZE @ 分配pt_regs空间 │
│ stmia sp, {r0-r12}^ @ 保存r0-r12到进程栈 │
│ stmdb sp, {sp,lr}^ @ 保存sp_usr, lr_usr │
│ str lr, [sp, #S_PC] @ 保存PC │
│ mrs r1, spsr │
│ str r1, [sp, #S_PSR] @ 保存CPSR │
│ │
│ 进程A的内核栈状态: │
│ 0xC1002F00 ┏━━━━━━━━━━━━━━━┓ │
│ ┃ (栈顶) ┃ │
│ 0xC1002E80 ┣━━━━━━━━━━━━━━━┫ │
│ ┃ pt_regs: ┃ │
│ ┃ r0-r12 ┃ ← 用户寄存器 │
│ ┃ sp_usr ┃ ← 0xBFFFFE00 │
│ ┃ lr_usr ┃ ← 0x08048200 │
│ ┃ pc ┃ ← 0x08048100 │
│ ┃ cpsr ┃ ← 0x60000010 │
│ 0xC1002E00 ┗━━━━━━━━━━━━━━━┛ ← SP_svc │
│ │
│ 关键点: pt_regs保存在进程A的内核栈上 ✓ │
└─────────────────────────────────────────────────────────┘
↓ 调用C语言中断处理函数
┌─────────────────────────────────────────────────────────┐
│ T3: 执行中断处理函数 │
├─────────────────────────────────────────────────────────┤
│ irqreturn_t uart_irq_handler(int irq, void *dev_id) │
│ { │
│ /* 当前状态 */ │
│ current = 进程A │
│ SP = 0xC1002D00 (进程A的内核栈) │
│ Mode = SVC mode │
│ │
│ /* 读取硬件 */ │
│ uint32_t status = readl(UART_STATUS); │
│ writel(status, UART_STATUS); │
│ │
│ return IRQ_HANDLED; │
│ } │
│ │
│ 进程A的内核栈状态: │
│ 0xC1002E00 ┏━━━━━━━━━━━━━━━┓ │
│ ┃ pt_regs ┃ │
│ 0xC1002D80 ┣━━━━━━━━━━━━━━━┫ │
│ ┃ 中断处理栈帧 ┃ ← SP现在在这里 │
│ 0xC1002D00 ┗━━━━━━━━━━━━━━━┛ │
│ │
│ 关键观察: │
│ 1. 使用进程A的内核栈 ✓ │
│ 2. current指向进程A ✓ │
│ 3. 栈地址正确 ✓ │
└─────────────────────────────────────────────────────────┘
↓ return IRQ_HANDLED
┌─────────────────────────────────────────────────────────┐
│ T4: 中断返回 │
├─────────────────────────────────────────────────────────┤
│ 汇编代码恢复上下文: │
│ ldmia sp, {r0-r12}^ @ 从进程栈恢复r0-r12 │
│ ldr lr, [sp, #S_PC] @ 恢复PC → LR │
│ add sp, sp, #S_FRAME_SIZE │
│ msr spsr_cxsf, cpsr @ 恢复CPSR │
│ movs pc, lr @ 返回用户态 │
│ │
│ 硬件自动: │
│ CPSR ← SPSR_svc = 0x60000010 (恢复用户态) │
│ PC ← LR = 0x08048104 │
│ SP ← SP_usr = 0xBFFFFE00 (恢复用户栈) │
│ 模式切换: SVC → User │
│ │
│ 进程A继续运行, 完全无感知 ✓ │
└─────────────────────────────────────────────────────────┘
总结: 正常流程
════════════════════════════════════════════════════════════
✓ 栈地址正确 (进程A的内核栈)
✓ 上下文完整保存和恢复
✓ 中断返回路径正确
✓ 进程无感知
✓ 总耗时: 2-5μs
3.2 错误流程(中断中睡眠)
灾难时间线: 中断中睡眠导致的问题
════════════════════════════════════════════════════════════
┌─────────────────────────────────────────────────────────┐
│ T0-T2: 与正常流程相同 │
├─────────────────────────────────────────────────────────┤
│ 进程A运行 → 中断发生 → 切换到SVC mode → 保存上下文 │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ T3: 错误的中断处理函数 │
├─────────────────────────────────────────────────────────┤
│ irqreturn_t bad_uart_handler(int irq, void *dev_id) │
│ { │
│ /* 当前状态 */ │
│ current = 进程A │
│ SP = 0xC1002D00 (进程A的内核栈) ✓ │
│ preempt_count = 0x00010000 (HARDIRQ位=1) │
│ │
│ /* 错误: 尝试获取mutex */ │
│ mutex_lock(&uart_mutex); // 假设锁被进程B持有 │
│ ↓ │
│ /* mutex发现锁被占用 */ │
│ ↓ │
│ __mutex_lock_slowpath() │
│ ↓ │
│ schedule(); ← 灾难开始! │
│ │
│ return IRQ_HANDLED; // 永远不会执行到这里 ✗ │
│ } │
│ │
│ 关键点: │
│ • SP地址是正确的 (进程A内核栈) │
│ • 但仍然不能睡眠! │
└─────────────────────────────────────────────────────────┘
↓ 进入schedule()
┌─────────────────────────────────────────────────────────┐
│ T4: schedule()执行 │
├─────────────────────────────────────────────────────────┤
│ asmlinkage void __sched schedule(void) │
│ { │
│ struct task_struct *prev, *next; │
│ │
│ prev = current; // prev = 进程A │
│ │
│ /* 检查 (会打印警告) */ │
│ if (in_interrupt()) { │
│ printk("BUG: scheduling while in interrupt!\n");│
│ dump_stack(); │
│ /* 但仍然继续执行 ✗ */ │
│ } │
│ │
│ if (preempt_count() != 0) { │
│ printk("BUG: scheduling while atomic!\n"); │
│ } │
│ │
│ /* 选择下一个进程 */ │
│ next = pick_next_task(); // next = 进程B │
│ │
│ /* 上下文切换 */ │
│ context_switch(prev, next); │
│ } │
│ │
│ 关键观察: │
│ • 当前SP = 0xC1002D00 (进程A内核栈) ✓ │
│ • schedule()认为这是进程A的正常内核栈 ✓ │
│ • 栈地址"正确" - 这次没有栈地址错误! │
└─────────────────────────────────────────────────────────┘
↓ 进入context_switch()
┌─────────────────────────────────────────────────────────┐
│ T5: context_switch() - 看似正常实则有严重问题 │
├─────────────────────────────────────────────────────────┤
│ static inline void context_switch( │
│ struct task_struct *prev, // 进程A │
│ struct task_struct *next) // 进程B │
│ { │
│ /* 切换内存映射 */ │
│ switch_mm(prev->mm, next->mm, next); │
│ │
│ /* 切换寄存器和栈 */ │
│ switch_to(prev, next, prev); │
│ } │
│ │
│ switch_to() 的关键操作: │
│ │
│ /* 保存进程A的上下文 */ │
│ 进程A->thread.cpu_context.sp = SP; │
│ = 0xC1002D00 ✓ 这次地址是正确的! │
│ │
│ 进程A->thread.cpu_context.pc = PC; │
│ 进程A->thread.cpu_context.r4-r11 = ...; │
│ │
│ /* 保存preempt_count */ │
│ 进程A->thread_info->preempt_count = 0x00010000 │
│ ✗ 保存了错误的值! (HARDIRQ位仍为1) │
│ │
│ /* 恢复进程B的上下文 */ │
│ SP = 进程B->thread.cpu_context.sp; │
│ = 0xC1003F00 (进程B的内核栈) │
│ │
│ PC = 进程B->thread.cpu_context.pc; │
│ R4-R11 = 进程B->thread.cpu_context.r4-r11; │
│ │
│ /* 恢复preempt_count */ │
│ preempt_count = 进程B->thread_info->preempt_count │
│ = 0x00000000 (进程B的正常值) │
│ │
│ 关键观察: │
│ ✓ 栈地址保存正确 (0xC1002D00是进程A的内核栈) │
│ ✗ 中断处理函数没有返回! │
│ ✗ pt_regs仍在栈上, 但中断返回路径断裂 │
└─────────────────────────────────────────────────────────┘
↓ 进程B开始运行
┌─────────────────────────────────────────────────────────┐
│ T6: 进程B运行 │
├─────────────────────────────────────────────────────────┤
│ CPU状态: 运行进程B │
│ current: 进程B │
│ SP: 0xC1003F00 (进程B的内核栈) │
│ preempt_count: 0x00000000 │
│ │
│ 进程A的状态: │
│ 状态: TASK_UNINTERRUPTIBLE (睡眠) │
│ 等待资源: uart_mutex │
│ 栈指针: 0xC1002D00 │
│ 栈内容: 完整保留 ✓ │
│ │
│ 进程A的内核栈快照: │
│ 0xC1002F00 ┏━━━━━━━━━━━━━━━┓ │
│ ┃ pt_regs ┃ ← 用户态上下文仍在! │
│ 0xC1002E80 ┃ r0-r12 ┃ │
│ ┃ sp_usr ┃ = 0xBFFFFE00 │
│ ┃ pc ┃ = 0x08048100 │
│ 0xC1002E00 ┣━━━━━━━━━━━━━━━┫ │
│ ┃ 中断处理栈帧 ┃ │
│ ┃ uart_handler ┃ │
│ ┃ 栈帧 ┃ │
│ 0xC1002D00 ┗━━━━━━━━━━━━━━━┛ ← 保存的SP │
│ │
│ 问题: pt_regs保存的用户态上下文永远无法恢复! ✗ │
└─────────────────────────────────────────────────────────┘
↓ 时间流逝...
┌─────────────────────────────────────────────────────────┐
│ T7: 10ms后, 进程A重新调度 │
├─────────────────────────────────────────────────────────┤
│ 假设进程B释放了uart_mutex, 进程A被唤醒 │
│ │
│ schedule() 选择进程A │
│ context_switch(进程B, 进程A): │
│ │
│ /* 恢复进程A的上下文 */ │
│ SP = 进程A->thread.cpu_context.sp │
│ = 0xC1002D00 ✓ 地址正确 │
│ │
│ PC = 进程A->thread.cpu_context.pc │
│ = schedule()中的某个地址 │
│ │
│ preempt_count = 进程A->thread_info->preempt_count │
│ = 0x00010000 ✗ 恢复了错误的值! │
│ │
│ 进程A恢复执行: │
│ 从 schedule() 返回 │
│ → 回到 __mutex_lock_slowpath() │
│ → 回到 mutex_lock() │
│ → 回到 bad_uart_handler() │
│ → return IRQ_HANDLED │
│ │
│ 问题: 返回到哪里? ✗ │
└─────────────────────────────────────────────────────────┘
↓ 尝试返回
┌─────────────────────────────────────────────────────────┐
│ T8: 返回路径的混乱 │
├─────────────────────────────────────────────────────────┤
│ bad_uart_handler() 返回后: │
│ │
│ 正常应该返回到中断入口的汇编代码: │
│ vector_irq → asm_do_IRQ() → uart_handler() → 返回 │
│ │
│ 但经过schedule()切换后: │
│ 返回地址链已经不完整! │
│ │
│ 可能的情况: │
│ │
│ 情况1: 栈帧信息仍然有效 │
│ 从 uart_handler() 返回到 asm_do_IRQ() │
│ 从 asm_do_IRQ() 返回到中断入口汇编 │
│ 执行 ldmia sp, {r0-r12}^ │
│ 执行 movs pc, lr │
│ ✓ 理论上可能成功返回用户态 │
│ │
│ 但是! preempt_count = 0x00010000 │
│ 系统认为进程A仍在中断上下文中! ✗ │
│ │
│ 情况2: 栈帧被破坏 (如果有新中断嵌套) │
│ 新中断使用同一栈, 覆盖了返回地址 │
│ 返回到错误的地址 │
│ ✗ 系统崩溃 │
│ │
│ 情况3: 更复杂的调度序列 │
│ 多次切换后, 栈状态更加混乱 │
│ ✗ 不可预测的行为 │
└─────────────────────────────────────────────────────────┘
4. 核心问题详细分析
虽然使用任务栈避免了”栈地址错误”问题,但仍有以下严重问题:
4.1 问题一:中断返回路径永久断裂
这是最根本、最致命的问题,与使用哪种栈无关。
问题描述
正常的中断返回路径
════════════════════════════════════════════════════════════
用户代码 → [中断发生] → 中断入口汇编 → C处理函数 → return
↑ ↓
└────────────── 返回汇编 ← 恢复上下文 ←──────────────┘
流程:
1. 用户代码执行
2. 中断发生
3. 硬件保存部分上下文 (自动)
4. 中断入口汇编保存完整上下文
5. 调用C语言中断处理函数
6. C函数return
7. 返回到中断入口汇编 ← 关键!
8. 汇编代码恢复上下文
9. movs pc, lr 返回用户态
中断入口汇编代码 (简化):
────────────────────────────────────────────────────────
vector_irq:
sub sp, sp, #S_FRAME_SIZE @ 分配pt_regs空间
stmia sp, {r0-r12} @ 保存寄存器
... @ 保存其他上下文
bl asm_do_IRQ @ 调用C函数
@ ← 期望执行到这里! 但如果睡眠就永远不会!
ldmia sp, {r0-r12}^ @ 恢复寄存器
ldr lr, [sp, #S_PC] @ 恢复PC
add sp, sp, #S_FRAME_SIZE @ 释放栈空间
movs pc, lr @ 返回用户态
睡眠导致返回路径断裂
错误流程: 中断中调用schedule()
════════════════════════════════════════════════════════════
用户代码 → [中断] → 入口汇编 → C处理函数 → mutex_lock()
↓
schedule()
↓
切换进程B
↓
进程B运行...
↓
进程A恢复
↓
从schedule()返回
↓
继续执行mutex_lock()后的代码
↓
C处理函数return
↓
返回到哪里? ✗
分析:
C函数return后, 应该返回到它的调用者
调用链: vector_irq → asm_do_IRQ() → uart_handler()
正常情况:
uart_handler() return → asm_do_IRQ()
asm_do_IRQ() return → vector_irq的下一条指令
vector_irq 继续执行恢复代码
睡眠后:
经过schedule()切换, 虽然栈帧保留
但从schedule()返回后, 继续执行uart_handler()
uart_handler() return...
理论上仍会按调用链返回
但此时系统状态已经混乱:
• preempt_count错误
• in_interrupt()返回错误
• 时间延迟巨大 (10ms vs 10μs)
最坏情况:栈被破坏
如果中断嵌套
════════════════════════════════════════════════════════════
T0: 进程A运行
T1: 中断1发生 (UART)
在进程A栈上: 0xC1002E00
调用 uart_handler()
T2: uart_handler() 睡眠
schedule() 切换到进程B
T3: 进程B运行时, 新中断发生 (Timer)
在进程B栈上: 0xC1003E00
处理Timer中断
返回进程B
T4: 进程A重新调度
SP恢复到 0xC1002E00
继续执行 uart_handler()
问题: 如果Timer中断也在进程A栈上怎么办?
T3': 进程B运行, 但进程A被唤醒并抢占进程B
(比如高优先级)
此时进程A的栈指针 = 0xC1002E00
新中断发生!
硬件: SP_svc = 0xC1002E00
中断入口: sub sp, sp, #S_FRAME_SIZE
新pt_regs覆盖旧的返回地址! ✗
原uart_handler()的返回路径彻底破坏!
4.2 问题二:pt_regs用户态上下文孤立
pt_regs的生命周期
════════════════════════════════════════════════════════════
正常流程:
T0: 用户态运行
r0 = 用户数据1
r1 = 用户数据2
...
PC = 0x08048100
T1: 中断发生, 保存到pt_regs
pt_regs.r0 = 用户数据1
pt_regs.r1 = 用户数据2
pt_regs.pc = 0x08048100
T2: 中断处理 (3μs)
T3: 从pt_regs恢复
r0 ← pt_regs.r0
r1 ← pt_regs.r1
PC ← pt_regs.pc
T4: 返回用户态, 继续执行
✓ 用户态完全无感知
睡眠流程:
T0: 用户态运行
T1: 中断发生, 保存到pt_regs
pt_regs在栈上: 0xC1002E80
T2: 中断处理函数睡眠
schedule()切换进程
T3: 进程A睡眠期间 (10ms)
pt_regs仍在栈上
但无人访问它!
T4: 进程A恢复
从schedule()返回
继续执行中断处理函数
中断处理函数return
T5: 返回路径 (如果能走到这里)
理论上会恢复pt_regs
但时间延迟巨大 (10ms)
问题:
• 用户进程被"冻结"10ms
• 用户进程完全不知道发生了什么
• 实时性完全丧失
• 如果是实时任务, 彻底失败
更严重的情况:
如果进程A在睡眠期间被kill
pt_regs永远不会被恢复
用户进程状态永久丢失 ✗
4.3 问题三:preempt_count状态污染
preempt_count的正常生命周期
════════════════════════════════════════════════════════════
bit: 31 28 27 16 15 8 7 0
┌────┬──────────┬──────────┬──────────┐
│ │ HARDIRQ │ SOFTIRQ │ PREEMPT │
└────┴──────────┴──────────┴──────────┘
正常流程:
用户态: 0x00000000
↓ 中断发生
进入中断: 0x00010000 (HARDIRQ+=1)
↓ 中断处理
退出中断: 0x00000000 (HARDIRQ-=1)
↓ 返回用户态
用户态: 0x00000000
睡眠流程的preempt_count污染:
════════════════════════════════════════════════════════════
T0: 进程A用户态
进程A->thread_info->preempt_count = 0x00000000
T1: 中断发生
进入中断: preempt_count = 0x00010000
__irq_svc:
preempt_count += HARDIRQ_OFFSET
// preempt_count = 0x00010000
T2: 中断处理函数调用schedule()
mutex_lock() → schedule()
T3: context_switch()保存进程A
进程A->thread_info->preempt_count = 0x00010000 ✗
/* 保存了错误的值! */
T4: 切换到进程B
preempt_count = 进程B->thread_info->preempt_count
= 0x00000000 (进程B的正常值)
T5: 进程B运行...
T6: 进程A重新调度
preempt_count = 进程A->thread_info->preempt_count
= 0x00010000 ✗ 恢复了错误的值!
T7: 进程A继续运行
in_interrupt() 检查:
return (preempt_count() & 0x00ff0000) != 0
return (0x00010000 & 0x00ff0000) != 0
return true ✗ 错误! 进程A不在中断中!
文档已成功创建!文件包含了详细的分析,说明即使中断使用任务栈而非独立IRQ-Stack,仍然不能睡眠的6个核心原因。
总结
我已经为您创建了一篇详细的文章 《中断使用任务栈时为什么仍然不能睡眠-详细分析.md》,存放在与原文档相同的目录下。
文章主要内容:
两种中断栈模型对比
- 独立IRQ-Stack vs 使用任务栈
- 内存布局详细对比
使用任务栈的中断处理流程
- 正常流程(不睡眠)
- 错误流程(中断中睡眠)
核心问题详细分析(6个严重问题)
- 问题1: 中断返回路径永久断裂(最致命)
- 问题2: pt_regs用户态上下文孤立
- 问题3: preempt_count状态污染
- 问题4: 自死锁(使用任务栈特有,极危险)
- 问题5: 实时性彻底破坏
- 问题6: 中断状态不一致
问题对比矩阵
- 详细对比两种栈模式下的所有问题
实际内核的选择
- Linux内核演进历史
- 各种RTOS的实现
总结与最佳实践
- 正确的代码示例(Linux、FreeRTOS)
- 调试检查清单
- 常见错误与解决方案
核心结论:
即使使用任务栈,中断处理函数仍然绝对不能睡眠!
虽然使用任务栈避免了”栈地址错误”问题,但仍有6个严重问题,其中”自死锁”是使用任务栈特有的最危险问题。中断不能睡眠的根本原因与使用哪种栈无关,而是中断的本质决定的。