Linux内核中,在上下文切换后谁调用IRET?

3
我一直在尝试理解Linux内核中上下文切换的工作原理。在某些情况下(稍后会解释),似乎会导致中断后没有调用IRET指令(我确定我漏掉了什么!)。我假设在中断后调用IRET是非常必要的,因为在调用IRET之前无法获得相同的中断。我只关心在x86架构上运行的单处理器内核。
我认为可能导致所描述行为的情况如下:
- 运行在内核模式下的进程A自愿地调用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()。这种情况可能会导致计时器中断永久阻塞。

我在这里缺少什么?


我已经很久没有关注了,这是我喜欢的一个主题,请问你是否考虑过内核的定时器作为调度程序的一部分会发出IRET指令?如果现在太大而无法读取,下载Linux 内核 v.99b 是否会更容易些,毕竟这是最小的源代码下载? - t0mm13b
我理解在大多数情况下内核的计时器处理程序会发出IRET指令。但问题是调度程序并不总是由计时器调用。关于Linux Kernel v.99b,听起来是一个不错的地方!谢谢 :) - harshad shirwadkar
我不确定我是否理解你的问题,但似乎没有任何iret会丢失。由于mutex_lock()是一个系统调用,每当它被调用时,它都需要使用iret返回到用户空间(至少在使用软件中断int 0x80来调用系统调用时是这样)。因此,当进程A恢复时,它将简单地完成执行mutex_lock()和iret。 - zack
2个回答

1
经济上说真话的时候,非Linux特定的说明/示例:
线程A不必调用IRET-内核代码调用IRET将执行返回到线程A,毕竟,这可能是它在第一次丢失之前的某种方式-来自某些外围设备的硬件中断。
通常,当线程A由于某些其他硬件中断或系统调用而早期失去执行时,线程A的堆栈指针保存在内核TCB中,指向A堆栈上的IRET返回帧,然后切换到内核堆栈以进行所有内部调度程序等操作。如果由于使用了特定的系统调用机制而不存在精确的IRET帧,则会组装一个。当内核需要恢复A时,内核重新加载硬件SP以线程A存储的SP和IRET返回到用户空间。完成工作- A恢复运行,中断等被启用。
然后内核就失去了控制权。当下一个硬件中断/驱动程序或系统调用再次进入时,它可以将其内部SP设置为其自己的私有堆栈的顶部,因为它在调用之间不保留状态数据。
这只是使其工作的一种方式:)显然,确切的机制/是ABI/架构相关的。

谢谢你的回答。你的意思是说,在调用分发程序之前,IRET帧总是存在的吗?换句话说,如果不是从中断处理程序调用分发程序,那么是否会显式创建IRET帧? - harshad shirwadkar
1
这取决于中断/系统调用架构。如果系统调用是通过软件中断执行的(例如INT 80H),则适当的帧已经存在。如果由于某种原因需要执行IRET,并且正确的帧不存在(例如为了正确地为所有中断优先级启用处理器内部中断硬件),那么显式地组装一个帧相对容易:)类似于运行新线程的机制-它以前从未运行过,因此不能有任何帧,因此必须在其新堆栈上建立一个帧,以便可以IRET返回并运行它。 - Martin James

1
我不知道Linux,但在许多操作系统中,上下文切换通常由调度程序而非中断处理程序执行。如果中断未导致待处理的上下文切换,则该中断将直接返回。如果需要触发中断引起的上下文切换,则会保存当前状态,并通过调度程序退出中断(调度程序执行IRET)。如果允许嵌套中断,则情况会更加复杂,因为最初的中断是发送到调度程序的,无论哪个嵌套中断处理程序触发了上下文切换条件。中断需要检查保存的状态以查看它是否是嵌套中断,如果不是,则可以禁用中断以防止在进行检查并选择通过调度程序进行上下文切换时发生嵌套中断。如果中断是嵌套中断,则只需在需要时设置上下文切换标志,并依赖于最初的中断来进行检查和上下文切换。
通常,在内核TCB中,除非要发生上下文切换,否则不需要中断保存线程状态。
调度程序还处理由非中断条件触发的上下文切换情况,例如互斥量、信号量...。

谢谢您的回答,我同意如果IRET返回到调度程序,就不会有问题。但是,在经过Linux的上下文切换代码后,我非常确定Linux在中断处理程序返回之前执行上下文切换。此外,我并不是指中断处理程序执行上下文切换。我只是指在中断处理程序返回之前调用了调度程序。 - harshad shirwadkar
@harshadshirwadkar - 我更新了我的答案,注意中断处理程序通过调度程序“退出”(调度程序执行IRET指令)。稍后我会删除这个评论。 - rcgldr

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接