我一直试图理解上下文切换在Linux内核中是如何工作的 . 在我看来,有一种情况(后面会解释)导致在中断后没有调用IRET指令(我确信有些东西我错过了!) . 我假设在中断之后调用IRET是非常必要的,因为在调用IRET之前你不能得到相同的中断 . 我只担心在x86 arch上运行的单处理器内核 .
我认为可能导致所述行为的情况如下:
-
进程在内核模式下运行会自动调用
schedule()
(例如,在尝试获取已锁定的互斥锁时) . -
schedule()
决定执行上下文切换到进程B,因此调用context_switch()
-
context_switch()
通过调用switch_mm()
将虚拟内存从A切换到B -
context_switch()
运行宏switch_to()
以切换堆栈并实际将运行进程从A更改为B.请注意,进程A现在位于switch_to()
内,进程A的堆栈看起来像(堆栈向下生长):
...
[mutex_lock()]
[schedule()]
[context_switch()] (Stack Top)
-
进程B开始运行 . 稍后,它会收到定时器中断,定时器中断处理程序决定进程B需要重新安排 .
-
从定时器中断返回(但在调用IRET之前)
preempt_schedule_irq()
被调用 . -
preempt_schedule_irq()
来电schedule()
. -
schedule()
决定上下文切换到进程A并调用context_switch()
. -
context_switch()
调用switch_mm()
来切换虚拟内存 . -
context_switch()
调用switch_to()
来切换堆栈 . 此时,进程B的堆栈如下所示:
...
[IRET return frame]
[ret_from_interrupt()]
[preempt_schedule_irq()]
[schedule()]
[context_switch()] (Stack top)
现在进程A正在运行,其堆栈已恢复 . 由于A中的context_switch()函数由于定时器中断而未被调用,因此进程A不会调用IRET并继续执行mutex_lock() . 这种情况可能会导致永久阻止定时器中断 .
我在这里想念的是什么?
2 回答
我不了解Linux,但在许多操作系统中,上下文切换通常由调度程序执行,而不是中断处理程序 . 如果中断没有导致挂起的上下文切换,它只会返回 . 如果需要中断触发的上下文切换,则保存当前状态,并通过调度程序退出中断(调度程序执行IRET) . 如果允许嵌套中断,则会变得更复杂,因为初始中断是进入调度程序的中断,无论哪个嵌套中断处理程序触发上下文切换条件 . 中断需要检查保存的状态以查看它是否是嵌套中断,如果不是,它可以禁用中断以防止在检查时发生嵌套中断,并可选择通过调度程序退出以执行上下文切换 . 如果中断是嵌套中断,则只需在需要时设置上下文切换标志,并依赖初始中断进行检查和上下文切换 .
通常,除非要发生上下文切换,否则不需要中断来在内核TCB中保存线程状态 .
调度程序还处理上下文切换由非中断条件触发的情况,例如互斥,信号量等 .
经济实用的时间,非linux特定的解释/例子:
线程A不必调用IRET - 内核代码调用IRET将执行返回给线程A,毕竟,这是它可能首先丢失它的一种方式 - 来自某些外围设备的硬件中断 .
通常,当线程A由于某些其他硬件中断或sycall而在之前丢失执行时,线程A的堆栈指针保存在内核TCB中,指向A堆栈上的IRET返回帧,然后切换到所有内部调度程序的内核堆栈古比斯 . 如果由于使用了特定的系统调用机制而不存在精确的IRET帧,则组装一个 . 当内核需要恢复A时,内核将带有线程A的存储SP和IRET的硬件SP重新加载到用户空间 . 已完成工作 - 已启用中断运行的恢复 .
然后内核失去了控制权 . 当它被下一个硬件中断/驱动程序或系统调用再次输入时,它可以将其内部SP设置为其自己的私有堆栈的顶部,因为它在调用之间不保留任何状态数据 .
这只是它可以工作的一种方式:)显然,确切的机制是依赖于ABI /架构 .