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