linux 内核中的 schedule()+switch_to() 函数实际上是如何工作的? [英] How does schedule()+switch_to() functions from linux kernel actually work?
问题描述
我试图了解 linux 内核中的调度过程实际上是如何工作的.我的问题不是关于调度算法.它是关于函数 schedule()
和 switch_to()
是如何工作的.
I'm trying to understand how the schedule process in linux kernel actually works. My question is not about the scheduling algorithm. Its about how the functions schedule()
and switch_to()
work.
我会试着解释一下.我看到了:
I'll try to explain. I saw that:
当进程用完时间片时,标志 need_resched
由 scheduler_tick()
设置.内核检查标志,看到它已设置,并调用 schedule()
(与问题 1 相关)切换到新进程.这个标志是一条消息,应该尽快调用调度,因为另一个进程值得运行.在返回用户空间或从中断返回时,检查 need_resched
标志.如果设置了,内核会在继续之前调用调度程序.
When a process runs out of time-slice, the flag need_resched
is set by scheduler_tick()
. The kernel checks the flag, sees that it is set, and calls schedule()
(pertinent to question 1) to switch to a new process. This flag is a message that schedule should be invoked as soon as possible because another process deserves to run.
Upon returning to user-space or returning from an interrupt, the need_resched
flag is checked. If it is set, the kernel invokes the scheduler before continuing.
查看内核源码(linux-2.6.10——《Linux内核开发,第二版》一书所基于的版本),我也看到有些代码可以调用schedule()
自动运行,赋予另一个进程运行的权利.我看到函数 switch_to()
是实际执行上下文切换的函数.我查看了一些与架构相关的代码,试图了解 switch_to()
实际在做什么.
Looking into the kernel source (linux-2.6.10 - version that the book "Linux Kernel Development, second edition" is based on), I also saw that some codes can call the schedule()
function voluntarily, giving another process the right to run.
I saw that the function switch_to()
is the one that actually does the context switch. I looked into some architecture dependent codes, trying to understand what switch_to()
was actually doing.
这种行为引发了一些我无法找到答案的问题:
That behavior raised some questions that I could not find the answers for :
当
switch_to()
结束时,当前运行的进程是什么?调用schedule()
的进程?或者下一个进程,那个被选中运行的进程?
When
switch_to()
finishes, what is the current running process? The process that calledschedule()
? Or the next process, the one that was picked to run?
当 schedule()
被中断调用时,当中断处理完成时(在某种 RTE 之后),要运行的选定进程开始运行?还是在那之前?
When schedule()
gets called by an interrupt, the selected process to run starts to run when the interrupt handling finishes (after some kind of RTE) ? Or before that?
如果schedule()
函数不能从中断中调用,标志-need_resched
什么时候设置?
If the schedule()
function can not be called from an interrupt, when is the flag- need_resched
set?
当定时器中断处理程序工作时,正在使用什么堆栈?
When the timer interrupt handler is working, what stack is being used?
不知道能不能说清楚.如果我不能,我希望我能在得到一些答案(或问题)后做到这一点.我已经查看了几个试图理解这个过程的来源.我有一本书Linux 内核开发,第二章",我也在使用它.我对 MIP 和 H8300 架构略知一二,如果这有助于解释的话.
I don't know if I could make myself clear. If I couldn't, I hope I can do this after some answers (or questions). I already looked at several sources trying to understand that process. I have the book "Linux Kernel Development, sec ed", and I'm using it too. I know a bit about MIPs and H8300 architecture, if that help to explain.
推荐答案
- 调用后
switch_to()
,内核堆栈切换到next
中命名的任务的堆栈.更改地址空间等在例如context_switch 中处理()
. schedule()
不能在原子上下文中调用,包括从中断中调用(请参阅schedule_debug()
).如果需要重新调度,则设置 TIF_NEED_RESCHED 任务标志,该标志在 中断返回路径.- 见 2.
- 我相信,对于默认的 8K 堆栈,中断由当前正在执行的任何内核堆栈处理.如果使用 4K 堆栈,我相信会有一个单独的中断堆栈(由于一些 x86 魔法而自动加载),但我并不完全确定这一点.
- After calling
switch_to()
, the kernel stack is switched to that of the task named innext
. Changing the address space, etc, is handled in egcontext_switch()
. schedule()
cannot be called in atomic context, including from an interrupt (see the check inschedule_debug()
). If a reschedule is needed, the TIF_NEED_RESCHED task flag is set, which is checked in the interrupt return path.- See 2.
- I believe that, with the default 8K stacks, Interrupts are handled with whatever kernel stack is currently executing. If 4K stacks are used, I believe there's a separate interrupt stack (automatically loaded thanks to some x86 magic), but I'm not completely certain on that point.
为了更详细一点,这里有一个实际的例子:
To be a bit more detailed, here's a practical example:
- 发生中断.CPU切换到一个中断trampoline例程,将中断号压入堆栈,然后jmps到common_interrupt
- common_interrupt 调用 do_IRQ,禁用抢占然后处理IRQ
- 在某些时候,会做出切换任务的决定.这可能来自定时器中断,或来自唤醒呼叫.无论哪种情况,都会调用 set_task_need_resched,设置TIF_NEED_RESCHED 任务标志.
- 最终,CPU从原始中断中的do_IRQ返回,继续执行IRQ 退出路径. 如果这个 IRQ 是从内核中调用的,它 检查是否设置了 TIF_NEED_RESCHED,如果设置了则调用 preempt_schedule_irq,它在执行
schedule()
时短暂启用中断. - 如果 IRQ 是从用户空间调用的,我们首先 在返回之前检查是否有任何需要做的事情.如果是这样,我们去retint_careful,它检查挂起的重新调度(并在需要时直接调用
schedule()
)以及检查挂起的信号,然后在retint_check
直到没有设置更重要的标志. - 最后,我们恢复GS并从中断处理程序.
- An interrupt occurs. The CPU switches to an interrupt trampoline routine, which pushes the interrupt number onto the stack, then jmps to common_interrupt
- common_interrupt calls do_IRQ, which disables preemption then handles the IRQ
- At some point, a decision is made to switch tasks. This may be from the timer interrupt, or from a wakeup call. In either case, set_task_need_resched is invoked, setting the TIF_NEED_RESCHED task flag.
- Eventually, the CPU returns from do_IRQ in the original interrupt, and proceeds to the IRQ exit path. If this IRQ was invoked from within the kernel, it checks whether TIF_NEED_RESCHED is set, and if so calls preempt_schedule_irq, which briefly enables interrupts while performing a
schedule()
. - If the IRQ was invoked from userspace, we first check whether there's anything that needs doing prior to returning. If so, we go to retint_careful, which checks both for a pending reschedule (and directly invokes
schedule()
if needed) as well as checking for pending signals, then goes back for another round atretint_check
until there's no more important flags set. - Finally, we restore GS and return from the interrupt handler.
至于switch_to()
;switch_to()
(在 x86-32 上)的作用是:
As for switch_to()
; what switch_to()
(on x86-32) does is:
- 保存 EIP(指令指针)和 ESP(堆栈指针)的当前值,以备稍后返回此任务时使用.
- 切换
current_task
的值.此时,current
现在指向新任务. - 切换到新的堆栈,然后将我们要切换到的任务保存的 EIP 推送到堆栈上.之后会进行返回,使用这个EIP作为返回地址;这就是它跳回到以前调用
switch_to()
的旧代码的方式 - 调用 __switch_to().此时,
current
指向新任务,我们在新任务的堆栈上,但其他各种 CPU 状态尚未更新.__switch_to()
处理诸如 FPU、段描述符、调试寄存器等的状态切换. - 从
__switch_to()
返回后,switch_to()
手动压入堆栈的返回地址被返回,将执行放回到switch_to()
之前的位置code>switch_to() 在新任务中.现在已完全恢复切换到的任务的执行.
- Save the current values of EIP (instruction pointer) and ESP (stack pointer) for when we return to this task at some point later.
- Switch the value of
current_task
. At this point,current
now points to the new task. - Switch to the new stack, then push the EIP saved by the task we're switching to onto the stack. Later, a return will be performed, using this EIP as the return address; this is how it jumps back to the old code that previously called
switch_to()
- Call __switch_to(). At this point,
current
points to the new task, and we're on the new task's stack, but various other CPU state hasn't been updated.__switch_to()
handles switching the state of things like the FPU, segment descriptors, debug registers, etc. - Upon return from
__switch_to()
, the return address thatswitch_to()
manually pushed onto the stack is returned to, placing execution back where it was prior to theswitch_to()
in the new task. Execution has now fully resumed on the switched-to task.
x86-64 非常相似,但由于 ABI 不同,必须做更多的状态保存/恢复.
x86-64 is very similar, but has to do slightly more saving/restoration of state due to the different ABI.
这篇关于linux 内核中的 schedule()+switch_to() 函数实际上是如何工作的?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!