在sched()函数中,有一个看似矛盾的操作:先检查中断是否已关闭,然后又保存CPU的中断启用状态。
在 xv6 中存在两个不同但相关的中断概念:
1. 当前中断状态#
- 通过
intr_get()函数检查 - 表示中断是否当前实际处于启用状态
2. 中断启用历史#
- 存储在
mycpu()->intena中 - 表示在最外层调用
push_off()之前中断是否处于启用状态 intena是一个标志位
为什么需要两者#
代码中的注释解释了这一点:
// Saves and restores intena because intena is a property of this
// kernel thread, not this CPU. It should be proc->intena and proc->noff,
// but that would break in the few places where a lock is held but
// there's no process.c这表明:
intena记录的状态属于内核线程(进程),而不是 CPU 硬件- 每次上下文切换需要保留这个值,以便进程下次运行时恢复正确的状态
具体工作流程#
1. 检查中断是否关闭:#
if(intr_get())
panic("sched interruptible");c这确保在上下文切换期间中断是实际禁用的,防止切换过程被打断
2. 保存中断启用历史:#
intena = mycpu()->intena;c这保存了进程在最初禁用中断前的状态(由 push_off()/pop_off() 维护)
3. 上下文切换:#
swtch(&p->context, &mycpu()->context);c切换到调度器,可能长时间不返回
4. 恢复中断启用历史:#
mycpu()->intena = intena;c当进程再次被调度时,恢复原来的中断历史状态
具体案例说明#
考虑这种情况:
- 进程 P1 原本中断是启用的
- P1 调用
acquire()获取锁,其中调用push_off()禁用中断,并记录mycpu()->intena = 1 - P1 执行
yield(),进入sched() sched()保存intena=1,切换到调度器- 很长时间后,P1 再次被调度
- 此时需要恢复
mycpu()->intena = 1,表示”此进程最初是启用中断的” - 当 P1 完成工作并释放锁时,
pop_off()会根据intena=1重新启用中断
如果不保存和恢复 intena,P1 重新运行时将”忘记”它原本应该启用中断。
总结#
- 检查中断关闭:是安全性检查,确保上下文切换过程不会被打断
- 保存
intena:是状态保存,确保进程重新运行时能正确恢复中断历史状态
这种设计反映了 xv6 操作系统对并发和中断处理的精心设计,确保了在多处理器环境下的正确行为,同时维护了每个内核线程的中断期望状态。