OS开发:键盘中断处理程序中自旋锁引发死锁问题排查
问题分析与解决
核心死锁原因
单CPU环境下,自旋锁临界区未配合中断禁用是导致死锁的根本原因:
当主循环持有自旋锁时,若此时键盘中断触发,CPU会立即暂停主循环,转而执行中断处理程序。中断处理程序尝试获取同一把锁时,发现锁已被持有,便进入自旋等待;但被中断挂起的主循环无法继续执行,自然无法释放锁——双方陷入永久等待,最终导致系统卡死,键盘缓冲区因无法处理而溢出。
具体代码问题点
- 主循环持有自旋锁时未禁用中断,导致持有锁期间可能被键盘中断抢占;
- 中断处理程序手动执行
__cli()/__sti(),但未解决主循环侧的中断抢占问题,反而可能导致中断状态混乱。
修复方案
1. 修改自旋锁实现,自动管理中断状态
修改汇编代码,让自旋锁在加锁时保存当前中断状态并禁用中断,解锁时恢复原状态,从根源避免持有锁时被中断抢占:
__spinlock_lock: pushfq ; 保存当前标志寄存器(包含中断使能位IF) cli ; 禁用中断,防止持有锁时被抢占 .lock_loop: lock bts word [rcx], 0 ; 原子尝试设置锁的第0位 jnc .acquired ; 若成功获取锁(CF=0),跳转到返回逻辑 .wait: test word [rcx], 0 ; 检查锁是否已释放 jnz .wait ; 未释放则继续自旋等待 jmp .lock_loop ; 锁已释放,再次尝试获取 .acquired: ret ; 返回,此时锁已持有,中断处于禁用状态 __spinlock_unlock: lock btr word [rcx], 0 ; 原子清除锁的第0位,释放锁 popfq ; 恢复加锁前的中断状态 ret
2. 移除中断处理程序中的手动中断控制
自旋锁现在会自动管理中断状态,无需手动执行__cli()/__sti(),修改后的中断处理函数:
_Use_decl_annotations_ bool HandleKeyboard( _In_ const BYTE Index, _In_ const PINTERRUPT_STACK Stack, _In_ const BYTE HasError, _In_ const PINTERRUPT_CPU_STATE Cpu ) { (void)Index; (void)Stack; (void)HasError; (void)Cpu; if (const auto key = Pic::PicManager::keyboard.GetKeyPress(); key.hasValue) { auto& [capitalize, keyLock, currentKey, wakeUp] = Pic::PicManager::keyboard; Sync::Guard guard(keyLock); currentKey = key.inner; wakeUp = true; } return true; }
3. 优化主循环的等待逻辑(可选)
原主循环在锁内空转浪费CPU资源,可在无按键时释放锁并休眠,直到中断触发唤醒CPU:
while (true) { auto& [capitalize, keyLock, currentKey, wakeUp] = Pic::PicManager::keyboard; Sync::Guard guard(keyLock); if (!wakeUp || currentKey == 0) { guard.~Guard(); // 手动提前释放锁,避免休眠时持有锁 __hlt(); // 休眠,直到中断触发唤醒CPU continue; } // 此处添加按键处理逻辑 currentKey = 0; wakeUp = false; }
内容的提问来源于stack exchange,提问作者Toma
相关产品推荐
相关产品推荐

